Qt: Use a delegate to lazily resize/render icon pixmaps

pull/3527/head
Stenzek 2 months ago
parent 645344b8ff
commit e907bbf831
No known key found for this signature in database

@ -10,16 +10,21 @@
#include "common/assert.h"
#include "common/error.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/path.h"
#include "common/string_util.h"
#include <QtCore/QFileInfo>
#include <QtGui/QPainter>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QStyledItemDelegate>
#include "moc_memorycardeditorwindow.cpp"
LOG_CHANNEL(Host);
static constexpr char MEMORY_CARD_IMAGE_FILTER[] =
QT_TRANSLATE_NOOP("MemoryCardEditorWindow", "DuckStation Memory Card (*.mcd)");
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
}};
static constexpr int MEMORY_CARD_ICON_FRAME_DURATION_MS = 200;
namespace {
class MemoryCardEditorIconStyleDelegate final : public QStyledItemDelegate
{
public:
explicit MemoryCardEditorIconStyleDelegate(std::vector<MemoryCardImage::FileInfo>& files, u32& current_frame_index,
QWidget* parent)
: QStyledItemDelegate(parent), m_files(files), m_current_frame_index(current_frame_index)
{
}
~MemoryCardEditorIconStyleDelegate() = default;
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override
{
const QRect& rc = option.rect;
if (const QPixmap* icon_frame = getIconFrame(static_cast<size_t>(index.row()), m_current_frame_index, rc))
painter->drawPixmap(rc, *icon_frame);
}
void invalidateIconFrames()
{
m_icon_frames.clear();
m_icon_frames.resize(m_files.size());
}
const QPixmap* getIconFrame(size_t file_index, u32 frame_index, const QRect& rc) const
{
if (file_index >= m_icon_frames.size())
return nullptr;
const MemoryCardImage::FileInfo& fi = m_files[file_index];
if (fi.icon_frames.empty())
return nullptr;
std::vector<QPixmap>& frames = m_icon_frames[file_index];
if (frames.empty())
frames.resize(fi.icon_frames.size());
const size_t real_frame_index = frame_index % static_cast<u32>(frames.size());
QPixmap& pixmap = frames[real_frame_index];
if (pixmap.isNull())
{
const QWidget* pw = qobject_cast<const QWidget*>(parent());
const float dpr = pw ? QtUtils::GetDevicePixelRatioForWidget(pw) : 1.0f;
// doing this on the UI thread is a bit ehh, but whatever, they're small images.
const MemoryCardImage::IconFrame& frame = fi.icon_frames[real_frame_index];
const int pixmap_width = static_cast<int>(std::ceil(static_cast<qreal>(rc.width()) * dpr));
const int pixmap_height = static_cast<int>(std::ceil(static_cast<qreal>(rc.height()) * dpr));
const int icon_size = std::min(pixmap_width, pixmap_height);
const int xoffs =
std::max(static_cast<int>((static_cast<qreal>(pixmap_width - icon_size) * static_cast<qreal>(0.5)) / dpr), 0);
const int yoffs =
std::max(static_cast<int>((static_cast<qreal>(pixmap_height - icon_size) * static_cast<qreal>(0.5)) / dpr), 0);
QImage src_image = QImage(reinterpret_cast<const uchar*>(frame.pixels), MemoryCardImage::ICON_WIDTH,
MemoryCardImage::ICON_HEIGHT, QImage::Format_RGBA8888);
if (src_image.width() != icon_size || src_image.height() != icon_size)
src_image = src_image.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::FastTransformation);
src_image.setDevicePixelRatio(dpr);
pixmap = QPixmap(pixmap_width, pixmap_height);
pixmap.setDevicePixelRatio(dpr);
pixmap.fill(Qt::transparent);
QPainter painter;
if (painter.begin(&pixmap))
{
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.drawImage(xoffs, yoffs, src_image);
painter.end();
}
}
return &pixmap;
}
private:
std::vector<MemoryCardImage::FileInfo>& m_files;
mutable std::vector<std::vector<QPixmap>> m_icon_frames;
u32& m_current_frame_index;
};
} // namespace
MemoryCardEditorWindow::MemoryCardEditorWindow() : QWidget()
{
m_ui.setupUi(this);
@ -70,6 +158,10 @@ MemoryCardEditorWindow::MemoryCardEditorWindow() : QWidget()
m_ui.newCardB->setToolTip(new_card_hover_text);
m_ui.openCardA->setToolTip(open_card_hover_text);
m_ui.openCardB->setToolTip(open_card_hover_text);
m_animation_timer = new QTimer(this);
m_animation_timer->setInterval(MEMORY_CARD_ICON_FRAME_DURATION_MS);
connect(m_animation_timer, &QTimer::timeout, this, &MemoryCardEditorWindow::incrementAnimationFrame);
}
MemoryCardEditorWindow::~MemoryCardEditorWindow() = default;
@ -142,6 +234,11 @@ void MemoryCardEditorWindow::connectCardUi(Card* card, QDialogButtonBox* buttonB
void MemoryCardEditorWindow::connectUi()
{
m_ui.cardA->setItemDelegateForColumn(
0, new MemoryCardEditorIconStyleDelegate(m_card_a.files, m_current_frame_index, m_ui.cardA));
m_ui.cardB->setItemDelegateForColumn(
0, new MemoryCardEditorIconStyleDelegate(m_card_a.files, m_current_frame_index, m_ui.cardB));
connect(m_ui.cardA, &QTableWidget::itemSelectionChanged, this, &MemoryCardEditorWindow::onCardASelectionChanged);
connect(m_ui.cardA, &QTableWidget::customContextMenuRequested, this,
&MemoryCardEditorWindow::onCardContextMenuRequested);
@ -258,6 +355,7 @@ bool MemoryCardEditorWindow::loadCard(const QString& filename, Card* card)
updateCardTable(card);
updateCardBlocksFree(card);
updateButtonState();
updateAnimationTimerActive();
return true;
}
@ -274,46 +372,15 @@ static void setCardTableItemProperties(QTableWidgetItem* item, const MemoryCardI
void MemoryCardEditorWindow::updateCardTable(Card* card)
{
card->table->setRowCount(0);
card->files = MemoryCardImage::EnumerateFiles(card->data, true);
static_cast<MemoryCardEditorIconStyleDelegate*>(card->table->itemDelegateForColumn(0))->invalidateIconFrames();
for (const MemoryCardImage::FileInfo& fi : card->files)
{
const int row = card->table->rowCount();
card->table->insertRow(row);
if (!fi.icon_frames.empty())
{
std::shared_ptr<QVector<QPixmap>> pixmaps = std::make_shared<QVector<QPixmap>>();
for (const auto& icon_frame : fi.icon_frames) {
const QImage image(reinterpret_cast<const u8*>(icon_frame.pixels), MemoryCardImage::ICON_WIDTH,
MemoryCardImage::ICON_HEIGHT, QImage::Format_RGBA8888);
QPixmap pixmap = QPixmap::fromImage(image.copy()).scaledToHeight(
MemoryCardImage::ICON_HEIGHT * 2,
Qt::FastTransformation
);
pixmaps->append(pixmap);
}
QLabel* icon = new QLabel;
icon->setPixmap((*pixmaps).first());
card->table->setCellWidget(row, 0, icon);
card->table->resizeRowToContents(row);
card->table->resizeColumnToContents(0);
QTimer* timer = new QTimer(icon);
std::shared_ptr<int> frame = std::make_shared<int>(0);
connect(timer, &QTimer::timeout, icon, [=]() {
icon->setPixmap((*pixmaps)[*frame]);
*frame = (*frame + 1) % pixmaps->size();
});
timer->start(MEMORY_CARD_ICON_FRAME_DURATION_MS);
}
QString title_str(QString::fromStdString(fi.title));
if (fi.deleted)
title_str += tr(" (Deleted)");
@ -334,6 +401,51 @@ void MemoryCardEditorWindow::updateCardTable(Card* card)
}
}
void MemoryCardEditorWindow::updateAnimationTimerActive()
{
bool has_animation_frames = false;
for (const Card& card : {m_card_a, m_card_b})
{
for (const MemoryCardImage::FileInfo& fi : card.files)
{
if (fi.icon_frames.size() > 1)
{
has_animation_frames = true;
break;
}
}
if (has_animation_frames)
break;
}
if (m_animation_timer->isActive() != has_animation_frames)
{
INFO_LOG("Animation timer is now {}", has_animation_frames ? "active" : "inactive");
m_current_frame_index = 0;
if (has_animation_frames)
m_animation_timer->start();
else
m_animation_timer->stop();
}
}
void MemoryCardEditorWindow::incrementAnimationFrame()
{
m_current_frame_index++;
for (QTableWidget* table : {m_ui.cardA, m_ui.cardB})
{
const int row_count = table->rowCount();
if (row_count == 0)
continue;
emit table->model()->dataChanged(table->model()->index(0, 0), table->model()->index(row_count - 1, 0),
{Qt::DecorationRole});
}
}
void MemoryCardEditorWindow::updateCardBlocksFree(Card* card)
{
card->blocks_free = MemoryCardImage::GetFreeBlockCount(card->data);
@ -370,6 +482,7 @@ void MemoryCardEditorWindow::newCard(Card* card)
updateCardTable(card);
updateCardBlocksFree(card);
updateButtonState();
updateAnimationTimerActive();
saveCard(card);
}
@ -402,6 +515,7 @@ void MemoryCardEditorWindow::openCard(Card* card)
updateCardTable(card);
updateCardBlocksFree(card);
updateButtonState();
updateAnimationTimerActive();
}
void MemoryCardEditorWindow::saveCard(Card* card)
@ -613,6 +727,7 @@ void MemoryCardEditorWindow::importCard(Card* card)
updateCardTable(card);
updateCardBlocksFree(card);
updateButtonState();
updateAnimationTimerActive();
}
void MemoryCardEditorWindow::formatCard(Card* card)
@ -636,6 +751,7 @@ void MemoryCardEditorWindow::formatCard(Card* card)
updateCardTable(card);
updateCardBlocksFree(card);
updateButtonState();
updateAnimationTimerActive();
}
void MemoryCardEditorWindow::importSaveFile(Card* card)
@ -659,6 +775,7 @@ void MemoryCardEditorWindow::importSaveFile(Card* card)
setCardDirty(card);
updateCardTable(card);
updateCardBlocksFree(card);
updateAnimationTimerActive();
}
void MemoryCardEditorWindow::onCardContextMenuRequested(const QPoint& pos)

@ -8,6 +8,7 @@
#include "core/memory_card_image.h"
#include <QtCore/QTimer>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDialog>
#include <QtWidgets/QDialogButtonBox>
@ -40,6 +41,7 @@ private Q_SLOTS:
void doCopyFile();
void doDeleteFile();
void doUndeleteFile();
void incrementAnimationFrame();
private:
struct Card
@ -84,6 +86,8 @@ private:
std::tuple<Card*, const MemoryCardImage::FileInfo*> getSelectedFile();
void updateButtonState();
void updateAnimationTimerActive();
Ui::MemoryCardEditorDialog m_ui;
QPushButton* m_deleteFile;
QPushButton* m_undeleteFile;
@ -94,6 +98,9 @@ private:
Card m_card_a;
Card m_card_b;
u32 m_current_frame_index = 0;
QTimer* m_animation_timer = nullptr;
};
class MemoryCardRenameFileDialog final : public QDialog

@ -29,12 +29,6 @@
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>true</bool>
</attribute>
@ -186,12 +180,6 @@
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>true</bool>
</attribute>

Loading…
Cancel
Save