Merge pull request #8304 from AdmiralCurtiss/memcard-manager-icon-fix
Qt/GCMemcardManager: Fix icon animations displaying incorrectly.
This commit is contained in:
commit
4a613dad20
|
@ -473,6 +473,15 @@ std::string GCMemcard::DEntry_BIFlags(u8 index) const
|
|||
return flags;
|
||||
}
|
||||
|
||||
bool GCMemcard::DEntry_IsPingPong(u8 index) const
|
||||
{
|
||||
if (!m_valid || index >= DIRLEN)
|
||||
return false;
|
||||
|
||||
const int flags = GetActiveDirectory().m_dir_entries[index].m_banner_and_icon_flags;
|
||||
return (flags & 0b0000'0100) != 0;
|
||||
}
|
||||
|
||||
std::string GCMemcard::DEntry_FileName(u8 index) const
|
||||
{
|
||||
if (!m_valid || index >= DIRLEN)
|
||||
|
|
|
@ -438,6 +438,7 @@ public:
|
|||
std::string DEntry_GameCode(u8 index) const;
|
||||
std::string DEntry_Makercode(u8 index) const;
|
||||
std::string DEntry_BIFlags(u8 index) const;
|
||||
bool DEntry_IsPingPong(u8 index) const;
|
||||
std::string DEntry_FileName(u8 index) const;
|
||||
u32 DEntry_ModTime(u8 index) const;
|
||||
u32 DEntry_ImageOffset(u8 index) const;
|
||||
|
|
|
@ -31,11 +31,22 @@
|
|||
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
||||
|
||||
constexpr u32 BANNER_WIDTH = 96;
|
||||
constexpr u32 ANIM_FRAME_WIDTH = 32;
|
||||
constexpr u32 IMAGE_HEIGHT = 32;
|
||||
constexpr u32 BANNER_HEIGHT = 32;
|
||||
constexpr u32 ICON_WIDTH = 32;
|
||||
constexpr u32 ICON_HEIGHT = 32;
|
||||
constexpr u32 ANIM_MAX_FRAMES = 8;
|
||||
constexpr float ROW_HEIGHT = 28;
|
||||
|
||||
struct GCMemcardManager::IconAnimationData
|
||||
{
|
||||
// the individual frames
|
||||
std::vector<QPixmap> m_frames;
|
||||
|
||||
// vector containing a list of frame indices that indicate, for each time unit,
|
||||
// the frame that should be displayed when at that time unit
|
||||
std::vector<u8> m_frame_timing;
|
||||
};
|
||||
|
||||
GCMemcardManager::GCMemcardManager(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
CreateWidgets();
|
||||
|
@ -47,7 +58,9 @@ GCMemcardManager::GCMemcardManager(QWidget* parent) : QDialog(parent)
|
|||
m_timer = new QTimer(this);
|
||||
connect(m_timer, &QTimer::timeout, this, &GCMemcardManager::DrawIcons);
|
||||
|
||||
m_timer->start(1000 / 8);
|
||||
// individual frames of icon animations can stay on screen for 4, 8, or 12 frames at 60 FPS,
|
||||
// which means the fastest animation and common denominator is 15 FPS or 66 milliseconds per frame
|
||||
m_timer->start(1000 / 15);
|
||||
|
||||
// Make the dimensions more reasonable on startup
|
||||
resize(650, 500);
|
||||
|
@ -181,7 +194,9 @@ void GCMemcardManager::UpdateSlotTable(int slot)
|
|||
return s.substr(0, offset);
|
||||
};
|
||||
|
||||
for (int i = 0; i < memcard->GetNumFiles(); i++)
|
||||
const u8 num_files = memcard->GetNumFiles();
|
||||
m_slot_active_icons[slot].reserve(num_files);
|
||||
for (int i = 0; i < num_files; i++)
|
||||
{
|
||||
int file_index = memcard->GetFileIndex(i);
|
||||
table->setRowCount(i + 1);
|
||||
|
@ -199,18 +214,13 @@ void GCMemcardManager::UpdateSlotTable(int slot)
|
|||
banner->setData(Qt::DecorationRole, GetBannerFromSaveFile(file_index, slot));
|
||||
banner->setFlags(banner->flags() ^ Qt::ItemIsEditable);
|
||||
|
||||
auto frames = GetIconFromSaveFile(file_index, slot);
|
||||
auto icon_data = GetIconFromSaveFile(file_index, slot);
|
||||
auto* icon = new QTableWidgetItem;
|
||||
icon->setData(Qt::DecorationRole, frames[0]);
|
||||
icon->setData(Qt::DecorationRole, icon_data.m_frames[0]);
|
||||
|
||||
std::optional<DEntry> entry = memcard->GetDEntry(file_index);
|
||||
|
||||
// TODO: This is wrong, the animation speed is not static and is already correctly calculated in
|
||||
// GetIconFromSaveFile(), just not returned
|
||||
const u16 animation_speed = entry ? entry->m_animation_speed : 1;
|
||||
const auto speed = (((animation_speed >> 8) & 1) << 2) + (animation_speed & 1);
|
||||
|
||||
m_slot_active_icons[slot].push_back({speed, frames});
|
||||
m_slot_active_icons[slot].emplace_back(std::move(icon_data));
|
||||
|
||||
table->setItem(i, 0, banner);
|
||||
table->setItem(i, 1, create_item(title));
|
||||
|
@ -441,96 +451,94 @@ void GCMemcardManager::FixChecksums()
|
|||
|
||||
void GCMemcardManager::DrawIcons()
|
||||
{
|
||||
m_current_frame++;
|
||||
m_current_frame %= 15;
|
||||
|
||||
const auto column = 3;
|
||||
for (int slot = 0; slot < SLOT_COUNT; slot++)
|
||||
{
|
||||
int row = 0;
|
||||
for (auto& icon : m_slot_active_icons[slot])
|
||||
for (const auto& icon : m_slot_active_icons[slot])
|
||||
{
|
||||
int frame = (m_current_frame / 3 - icon.first) % icon.second.size();
|
||||
const u64 current_time_in_animation = m_current_frame % icon.m_frame_timing.size();
|
||||
const u8 current_frame = icon.m_frame_timing[current_time_in_animation];
|
||||
|
||||
auto* item = new QTableWidgetItem;
|
||||
item->setData(Qt::DecorationRole, icon.second[frame]);
|
||||
item->setData(Qt::DecorationRole, icon.m_frames[current_frame]);
|
||||
item->setFlags(item->flags() ^ Qt::ItemIsEditable);
|
||||
|
||||
m_slot_table[slot]->setItem(row, column, item);
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
++m_current_frame;
|
||||
}
|
||||
|
||||
QPixmap GCMemcardManager::GetBannerFromSaveFile(int file_index, int slot)
|
||||
{
|
||||
auto& memcard = m_slot_memcard[slot];
|
||||
|
||||
std::vector<u32> pxdata(BANNER_WIDTH * IMAGE_HEIGHT);
|
||||
std::vector<u32> pxdata(BANNER_WIDTH * BANNER_HEIGHT);
|
||||
|
||||
QImage image;
|
||||
if (memcard->ReadBannerRGBA8(file_index, pxdata.data()))
|
||||
{
|
||||
image = QImage(reinterpret_cast<u8*>(pxdata.data()), BANNER_WIDTH, IMAGE_HEIGHT,
|
||||
image = QImage(reinterpret_cast<u8*>(pxdata.data()), BANNER_WIDTH, BANNER_HEIGHT,
|
||||
QImage::Format_ARGB32);
|
||||
}
|
||||
|
||||
return QPixmap::fromImage(image);
|
||||
}
|
||||
|
||||
std::vector<QPixmap> GCMemcardManager::GetIconFromSaveFile(int file_index, int slot)
|
||||
GCMemcardManager::IconAnimationData GCMemcardManager::GetIconFromSaveFile(int file_index, int slot)
|
||||
{
|
||||
auto& memcard = m_slot_memcard[slot];
|
||||
|
||||
std::vector<u32> pxdata(BANNER_WIDTH * IMAGE_HEIGHT);
|
||||
std::vector<u8> anim_delay(ANIM_MAX_FRAMES);
|
||||
std::vector<u32> anim_data(ANIM_FRAME_WIDTH * IMAGE_HEIGHT * ANIM_MAX_FRAMES);
|
||||
std::vector<u32> anim_data(ICON_WIDTH * ICON_HEIGHT * ANIM_MAX_FRAMES);
|
||||
|
||||
std::vector<QPixmap> frame_pixmaps;
|
||||
IconAnimationData frame_data;
|
||||
|
||||
u32 num_frames = memcard->ReadAnimRGBA8(file_index, anim_data.data(), anim_delay.data());
|
||||
const u32 num_frames = memcard->ReadAnimRGBA8(file_index, anim_data.data(), anim_delay.data());
|
||||
|
||||
// Decode Save File Animation
|
||||
if (num_frames > 0)
|
||||
{
|
||||
u32 frames = BANNER_WIDTH / ANIM_FRAME_WIDTH;
|
||||
|
||||
if (num_frames < frames)
|
||||
frame_data.m_frames.reserve(num_frames);
|
||||
const u32 per_frame_offset = ICON_WIDTH * ICON_HEIGHT;
|
||||
for (u32 f = 0; f < num_frames; ++f)
|
||||
{
|
||||
frames = num_frames;
|
||||
|
||||
// Clear unused frame's pixels from the buffer.
|
||||
std::fill(pxdata.begin(), pxdata.end(), 0);
|
||||
}
|
||||
|
||||
for (u32 f = 0; f < frames; ++f)
|
||||
{
|
||||
for (u32 y = 0; y < IMAGE_HEIGHT; ++y)
|
||||
QImage img(reinterpret_cast<u8*>(&anim_data[f * per_frame_offset]), ICON_WIDTH, ICON_HEIGHT,
|
||||
QImage::Format_ARGB32);
|
||||
frame_data.m_frames.push_back(QPixmap::fromImage(img));
|
||||
for (int i = 0; i < anim_delay[f]; ++i)
|
||||
{
|
||||
for (u32 x = 0; x < ANIM_FRAME_WIDTH; ++x)
|
||||
{
|
||||
// NOTE: pxdata is stacked horizontal, anim_data is stacked vertical
|
||||
pxdata[y * BANNER_WIDTH + f * ANIM_FRAME_WIDTH + x] =
|
||||
anim_data[f * ANIM_FRAME_WIDTH * IMAGE_HEIGHT + y * IMAGE_HEIGHT + x];
|
||||
}
|
||||
frame_data.m_frame_timing.push_back(static_cast<u8>(f));
|
||||
}
|
||||
}
|
||||
QImage anims(reinterpret_cast<u8*>(pxdata.data()), BANNER_WIDTH, IMAGE_HEIGHT,
|
||||
QImage::Format_ARGB32);
|
||||
|
||||
for (u32 f = 0; f < frames; f++)
|
||||
const bool is_pingpong = memcard->DEntry_IsPingPong(file_index);
|
||||
if (is_pingpong && num_frames >= 3)
|
||||
{
|
||||
frame_pixmaps.push_back(
|
||||
QPixmap::fromImage(anims.copy(ANIM_FRAME_WIDTH * f, 0, ANIM_FRAME_WIDTH, IMAGE_HEIGHT)));
|
||||
// if the animation 'ping-pongs' between start and end then the animation frame order is
|
||||
// something like 'abcdcbabcdcba' instead of the usual 'abcdabcdabcd'
|
||||
// to display that correctly just append all except the first and last frame in reverse order
|
||||
// at the end of the animation
|
||||
for (u32 f = num_frames - 2; f > 0; --f)
|
||||
{
|
||||
for (int i = 0; i < anim_delay[f]; ++i)
|
||||
{
|
||||
frame_data.m_frame_timing.push_back(static_cast<u8>(f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No Animation found, use an empty placeholder instead.
|
||||
frame_pixmaps.push_back(QPixmap());
|
||||
frame_data.m_frames.emplace_back();
|
||||
frame_data.m_frame_timing.push_back(1);
|
||||
}
|
||||
|
||||
return frame_pixmaps;
|
||||
return frame_data;
|
||||
}
|
||||
|
||||
QString GCMemcardManager::GetErrorMessagesForErrorCode(const GCMemcardErrorCode& code)
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
#include <QDialog>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class GCMemcard;
|
||||
class GCMemcardErrorCode;
|
||||
|
||||
|
@ -34,6 +36,8 @@ public:
|
|||
static QString GetErrorMessagesForErrorCode(const GCMemcardErrorCode& code);
|
||||
|
||||
private:
|
||||
struct IconAnimationData;
|
||||
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
|
||||
|
@ -52,7 +56,8 @@ private:
|
|||
void DrawIcons();
|
||||
|
||||
QPixmap GetBannerFromSaveFile(int file_index, int slot);
|
||||
std::vector<QPixmap> GetIconFromSaveFile(int file_index, int slot);
|
||||
|
||||
IconAnimationData GetIconFromSaveFile(int file_index, int slot);
|
||||
|
||||
// Actions
|
||||
QPushButton* m_select_button;
|
||||
|
@ -65,7 +70,7 @@ private:
|
|||
|
||||
// Slots
|
||||
static constexpr int SLOT_COUNT = 2;
|
||||
std::array<std::vector<std::pair<int, std::vector<QPixmap>>>, SLOT_COUNT> m_slot_active_icons;
|
||||
std::array<std::vector<IconAnimationData>, SLOT_COUNT> m_slot_active_icons;
|
||||
std::array<std::unique_ptr<GCMemcard>, SLOT_COUNT> m_slot_memcard;
|
||||
std::array<QGroupBox*, SLOT_COUNT> m_slot_group;
|
||||
std::array<QLineEdit*, SLOT_COUNT> m_slot_file_edit;
|
||||
|
@ -74,7 +79,7 @@ private:
|
|||
std::array<QLabel*, SLOT_COUNT> m_slot_stat_label;
|
||||
|
||||
int m_active_slot;
|
||||
int m_current_frame;
|
||||
u64 m_current_frame = 0;
|
||||
|
||||
QDialogButtonBox* m_button_box;
|
||||
QTimer* m_timer;
|
||||
|
|
Loading…
Reference in New Issue