Skip to content

Commit e907bbf

Browse files
committed
Qt: Use a delegate to lazily resize/render icon pixmaps
1 parent 645344b commit e907bbf

File tree

3 files changed

+158
-46
lines changed

3 files changed

+158
-46
lines changed

src/duckstation-qt/memorycardeditorwindow.cpp

Lines changed: 151 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,21 @@
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+
2328
static constexpr char MEMORY_CARD_IMAGE_FILTER[] =
2429
QT_TRANSLATE_NOOP("MemoryCardEditorWindow", "DuckStation Memory Card (*.mcd)");
2530
static 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
}};
3540
static 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+
37125
MemoryCardEditorWindow::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

75167
MemoryCardEditorWindow::~MemoryCardEditorWindow() = default;
@@ -142,6 +234,11 @@ void MemoryCardEditorWindow::connectCardUi(Card* card, QDialogButtonBox* buttonB
142234

143235
void 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
274372
void 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+
337449
void 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

407521
void 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

618733
void 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

641757
void 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

664781
void MemoryCardEditorWindow::onCardContextMenuRequested(const QPoint& pos)

src/duckstation-qt/memorycardeditorwindow.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "core/memory_card_image.h"
1010

11+
#include <QtCore/QTimer>
1112
#include <QtWidgets/QComboBox>
1213
#include <QtWidgets/QDialog>
1314
#include <QtWidgets/QDialogButtonBox>
@@ -40,6 +41,7 @@ private Q_SLOTS:
4041
void doCopyFile();
4142
void doDeleteFile();
4243
void doUndeleteFile();
44+
void incrementAnimationFrame();
4345

4446
private:
4547
struct Card
@@ -84,6 +86,8 @@ private Q_SLOTS:
8486
std::tuple<Card*, const MemoryCardImage::FileInfo*> getSelectedFile();
8587
void updateButtonState();
8688

89+
void updateAnimationTimerActive();
90+
8791
Ui::MemoryCardEditorDialog m_ui;
8892
QPushButton* m_deleteFile;
8993
QPushButton* m_undeleteFile;
@@ -94,6 +98,9 @@ private Q_SLOTS:
9498

9599
Card m_card_a;
96100
Card m_card_b;
101+
u32 m_current_frame_index = 0;
102+
103+
QTimer* m_animation_timer = nullptr;
97104
};
98105

99106
class MemoryCardRenameFileDialog final : public QDialog

src/duckstation-qt/memorycardeditorwindow.ui

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,6 @@
2929
<property name="selectionBehavior">
3030
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
3131
</property>
32-
<property name="iconSize">
33-
<size>
34-
<width>32</width>
35-
<height>32</height>
36-
</size>
37-
</property>
3832
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
3933
<bool>true</bool>
4034
</attribute>
@@ -186,12 +180,6 @@
186180
<property name="selectionBehavior">
187181
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
188182
</property>
189-
<property name="iconSize">
190-
<size>
191-
<width>32</width>
192-
<height>32</height>
193-
</size>
194-
</property>
195183
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
196184
<bool>true</bool>
197185
</attribute>

0 commit comments

Comments
 (0)