mirror of
https://github.com/stenzek/duckstation.git
synced 2026-02-04 05:04:33 +00:00
Qt: Use a delegate to lazily resize/render icon pixmaps
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user