1010#include " common/assert.h"
1111#include " common/error.h"
1212#include " common/file_system.h"
13+ #include " common/log.h"
1314#include " common/path.h"
1415#include " common/string_util.h"
1516
1617#include < QtCore/QFileInfo>
18+ #include < QtGui/QPainter>
1719#include < QtWidgets/QFileDialog>
1820#include < QtWidgets/QMenu>
1921#include < QtWidgets/QMessageBox>
22+ #include < QtWidgets/QStyledItemDelegate>
2023
2124#include " moc_memorycardeditorwindow.cpp"
2225
26+ LOG_CHANNEL (Host);
27+
2328static constexpr char MEMORY_CARD_IMAGE_FILTER[] =
2429 QT_TRANSLATE_NOOP (" MemoryCardEditorWindow" , " DuckStation Memory Card (*.mcd)" );
2530static constexpr char MEMORY_CARD_IMPORT_FILTER[] = QT_TRANSLATE_NOOP(
@@ -34,6 +39,89 @@ static constexpr std::array<std::pair<ConsoleRegion, const char*>, 3> MEMORY_CAR
3439}};
3540static constexpr int MEMORY_CARD_ICON_FRAME_DURATION_MS = 200 ;
3641
42+ namespace {
43+ class MemoryCardEditorIconStyleDelegate final : public QStyledItemDelegate
44+ {
45+ public:
46+ explicit MemoryCardEditorIconStyleDelegate (std::vector<MemoryCardImage::FileInfo>& files, u32 & current_frame_index,
47+ QWidget* parent)
48+ : QStyledItemDelegate(parent), m_files(files), m_current_frame_index(current_frame_index)
49+ {
50+ }
51+ ~MemoryCardEditorIconStyleDelegate () = default ;
52+
53+ void paint (QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override
54+ {
55+ const QRect& rc = option.rect ;
56+ if (const QPixmap* icon_frame = getIconFrame (static_cast <size_t >(index.row ()), m_current_frame_index, rc))
57+ painter->drawPixmap (rc, *icon_frame);
58+ }
59+
60+ void invalidateIconFrames ()
61+ {
62+ m_icon_frames.clear ();
63+ m_icon_frames.resize (m_files.size ());
64+ }
65+
66+ const QPixmap* getIconFrame (size_t file_index, u32 frame_index, const QRect& rc) const
67+ {
68+ if (file_index >= m_icon_frames.size ())
69+ return nullptr ;
70+
71+ const MemoryCardImage::FileInfo& fi = m_files[file_index];
72+ if (fi.icon_frames .empty ())
73+ return nullptr ;
74+
75+ std::vector<QPixmap>& frames = m_icon_frames[file_index];
76+ if (frames.empty ())
77+ frames.resize (fi.icon_frames .size ());
78+
79+ const size_t real_frame_index = frame_index % static_cast <u32 >(frames.size ());
80+ QPixmap& pixmap = frames[real_frame_index];
81+ if (pixmap.isNull ())
82+ {
83+ const QWidget* pw = qobject_cast<const QWidget*>(parent ());
84+ const float dpr = pw ? QtUtils::GetDevicePixelRatioForWidget (pw) : 1 .0f ;
85+
86+ // doing this on the UI thread is a bit ehh, but whatever, they're small images.
87+ const MemoryCardImage::IconFrame& frame = fi.icon_frames [real_frame_index];
88+ const int pixmap_width = static_cast <int >(std::ceil (static_cast <qreal>(rc.width ()) * dpr));
89+ const int pixmap_height = static_cast <int >(std::ceil (static_cast <qreal>(rc.height ()) * dpr));
90+ const int icon_size = std::min (pixmap_width, pixmap_height);
91+ const int xoffs =
92+ std::max (static_cast <int >((static_cast <qreal>(pixmap_width - icon_size) * static_cast <qreal>(0.5 )) / dpr), 0 );
93+ const int yoffs =
94+ std::max (static_cast <int >((static_cast <qreal>(pixmap_height - icon_size) * static_cast <qreal>(0.5 )) / dpr), 0 );
95+
96+ QImage src_image = QImage (reinterpret_cast <const uchar*>(frame.pixels ), MemoryCardImage::ICON_WIDTH,
97+ MemoryCardImage::ICON_HEIGHT, QImage::Format_RGBA8888);
98+ if (src_image.width () != icon_size || src_image.height () != icon_size)
99+ src_image = src_image.scaled (icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::FastTransformation);
100+ src_image.setDevicePixelRatio (dpr);
101+
102+ pixmap = QPixmap (pixmap_width, pixmap_height);
103+ pixmap.setDevicePixelRatio (dpr);
104+ pixmap.fill (Qt::transparent);
105+
106+ QPainter painter;
107+ if (painter.begin (&pixmap))
108+ {
109+ painter.setCompositionMode (QPainter::CompositionMode_Source);
110+ painter.drawImage (xoffs, yoffs, src_image);
111+ painter.end ();
112+ }
113+ }
114+
115+ return &pixmap;
116+ }
117+
118+ private:
119+ std::vector<MemoryCardImage::FileInfo>& m_files;
120+ mutable std::vector<std::vector<QPixmap>> m_icon_frames;
121+ u32 & m_current_frame_index;
122+ };
123+ } // namespace
124+
37125MemoryCardEditorWindow::MemoryCardEditorWindow () : QWidget()
38126{
39127 m_ui.setupUi (this );
@@ -70,6 +158,10 @@ MemoryCardEditorWindow::MemoryCardEditorWindow() : QWidget()
70158 m_ui.newCardB ->setToolTip (new_card_hover_text);
71159 m_ui.openCardA ->setToolTip (open_card_hover_text);
72160 m_ui.openCardB ->setToolTip (open_card_hover_text);
161+
162+ m_animation_timer = new QTimer (this );
163+ m_animation_timer->setInterval (MEMORY_CARD_ICON_FRAME_DURATION_MS);
164+ connect (m_animation_timer, &QTimer::timeout, this , &MemoryCardEditorWindow::incrementAnimationFrame);
73165}
74166
75167MemoryCardEditorWindow::~MemoryCardEditorWindow () = default ;
@@ -142,6 +234,11 @@ void MemoryCardEditorWindow::connectCardUi(Card* card, QDialogButtonBox* buttonB
142234
143235void MemoryCardEditorWindow::connectUi ()
144236{
237+ m_ui.cardA ->setItemDelegateForColumn (
238+ 0 , new MemoryCardEditorIconStyleDelegate (m_card_a.files , m_current_frame_index, m_ui.cardA ));
239+ m_ui.cardB ->setItemDelegateForColumn (
240+ 0 , new MemoryCardEditorIconStyleDelegate (m_card_a.files , m_current_frame_index, m_ui.cardB ));
241+
145242 connect (m_ui.cardA , &QTableWidget::itemSelectionChanged, this , &MemoryCardEditorWindow::onCardASelectionChanged);
146243 connect (m_ui.cardA , &QTableWidget::customContextMenuRequested, this ,
147244 &MemoryCardEditorWindow::onCardContextMenuRequested);
@@ -258,6 +355,7 @@ bool MemoryCardEditorWindow::loadCard(const QString& filename, Card* card)
258355 updateCardTable (card);
259356 updateCardBlocksFree (card);
260357 updateButtonState ();
358+ updateAnimationTimerActive ();
261359 return true ;
262360}
263361
@@ -274,46 +372,15 @@ static void setCardTableItemProperties(QTableWidgetItem* item, const MemoryCardI
274372void MemoryCardEditorWindow::updateCardTable (Card* card)
275373{
276374 card->table ->setRowCount (0 );
277-
278375 card->files = MemoryCardImage::EnumerateFiles (card->data , true );
376+
377+ static_cast <MemoryCardEditorIconStyleDelegate*>(card->table ->itemDelegateForColumn (0 ))->invalidateIconFrames ();
378+
279379 for (const MemoryCardImage::FileInfo& fi : card->files )
280380 {
281381 const int row = card->table ->rowCount ();
282382 card->table ->insertRow (row);
283383
284- if (!fi.icon_frames .empty ())
285- {
286- std::shared_ptr<QVector<QPixmap>> pixmaps = std::make_shared<QVector<QPixmap>>();
287-
288- for (const auto & icon_frame : fi.icon_frames ) {
289- const QImage image (reinterpret_cast <const u8 *>(icon_frame.pixels ), MemoryCardImage::ICON_WIDTH,
290- MemoryCardImage::ICON_HEIGHT, QImage::Format_RGBA8888);
291-
292- QPixmap pixmap = QPixmap::fromImage (image.copy ()).scaledToHeight (
293- MemoryCardImage::ICON_HEIGHT * 2 ,
294- Qt::FastTransformation
295- );
296-
297- pixmaps->append (pixmap);
298- }
299-
300- QLabel* icon = new QLabel;
301- icon->setPixmap ((*pixmaps).first ());
302-
303- card->table ->setCellWidget (row, 0 , icon);
304- card->table ->resizeRowToContents (row);
305- card->table ->resizeColumnToContents (0 );
306-
307- QTimer* timer = new QTimer (icon);
308- std::shared_ptr<int > frame = std::make_shared<int >(0 );
309-
310- connect (timer, &QTimer::timeout, icon, [=]() {
311- icon->setPixmap ((*pixmaps)[*frame]);
312- *frame = (*frame + 1 ) % pixmaps->size ();
313- });
314- timer->start (MEMORY_CARD_ICON_FRAME_DURATION_MS);
315- }
316-
317384 QString title_str (QString::fromStdString (fi.title ));
318385 if (fi.deleted )
319386 title_str += tr (" (Deleted)" );
@@ -334,6 +401,51 @@ void MemoryCardEditorWindow::updateCardTable(Card* card)
334401 }
335402}
336403
404+ void MemoryCardEditorWindow::updateAnimationTimerActive ()
405+ {
406+ bool has_animation_frames = false ;
407+ for (const Card& card : {m_card_a, m_card_b})
408+ {
409+ for (const MemoryCardImage::FileInfo& fi : card.files )
410+ {
411+ if (fi.icon_frames .size () > 1 )
412+ {
413+ has_animation_frames = true ;
414+ break ;
415+ }
416+ }
417+
418+ if (has_animation_frames)
419+ break ;
420+ }
421+
422+ if (m_animation_timer->isActive () != has_animation_frames)
423+ {
424+ INFO_LOG (" Animation timer is now {}" , has_animation_frames ? " active" : " inactive" );
425+
426+ m_current_frame_index = 0 ;
427+ if (has_animation_frames)
428+ m_animation_timer->start ();
429+ else
430+ m_animation_timer->stop ();
431+ }
432+ }
433+
434+ void MemoryCardEditorWindow::incrementAnimationFrame ()
435+ {
436+ m_current_frame_index++;
437+
438+ for (QTableWidget* table : {m_ui.cardA , m_ui.cardB })
439+ {
440+ const int row_count = table->rowCount ();
441+ if (row_count == 0 )
442+ continue ;
443+
444+ emit table->model ()->dataChanged (table->model ()->index (0 , 0 ), table->model ()->index (row_count - 1 , 0 ),
445+ {Qt::DecorationRole});
446+ }
447+ }
448+
337449void MemoryCardEditorWindow::updateCardBlocksFree (Card* card)
338450{
339451 card->blocks_free = MemoryCardImage::GetFreeBlockCount (card->data );
@@ -370,6 +482,7 @@ void MemoryCardEditorWindow::newCard(Card* card)
370482 updateCardTable (card);
371483 updateCardBlocksFree (card);
372484 updateButtonState ();
485+ updateAnimationTimerActive ();
373486 saveCard (card);
374487}
375488
@@ -402,6 +515,7 @@ void MemoryCardEditorWindow::openCard(Card* card)
402515 updateCardTable (card);
403516 updateCardBlocksFree (card);
404517 updateButtonState ();
518+ updateAnimationTimerActive ();
405519}
406520
407521void MemoryCardEditorWindow::saveCard (Card* card)
@@ -613,6 +727,7 @@ void MemoryCardEditorWindow::importCard(Card* card)
613727 updateCardTable (card);
614728 updateCardBlocksFree (card);
615729 updateButtonState ();
730+ updateAnimationTimerActive ();
616731}
617732
618733void MemoryCardEditorWindow::formatCard (Card* card)
@@ -636,6 +751,7 @@ void MemoryCardEditorWindow::formatCard(Card* card)
636751 updateCardTable (card);
637752 updateCardBlocksFree (card);
638753 updateButtonState ();
754+ updateAnimationTimerActive ();
639755}
640756
641757void MemoryCardEditorWindow::importSaveFile (Card* card)
@@ -659,6 +775,7 @@ void MemoryCardEditorWindow::importSaveFile(Card* card)
659775 setCardDirty (card);
660776 updateCardTable (card);
661777 updateCardBlocksFree (card);
778+ updateAnimationTimerActive ();
662779}
663780
664781void MemoryCardEditorWindow::onCardContextMenuRequested (const QPoint& pos)
0 commit comments