Merge pull request 'fix: Icon Blurriness & add Citron Logo' (#127) from fixup/icon-game-load into main

Reviewed-on: https://git.citron-emu.org/Citron/Emulator/pulls/127
This commit is contained in:
Collecting
2026-02-05 04:01:15 +01:00
3 changed files with 74 additions and 18 deletions

View File

@@ -10,5 +10,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
</qresource>
<qresource prefix="/">
<file alias="dist/dice.svg">../../dist/dice.svg</file>
<file alias="citron.svg">../../dist/citron.svg</file>
</qresource>
</RCC>

View File

@@ -1187,7 +1187,13 @@ void GameList::StartLaunchAnimation(const QModelIndex& item) {
QPixmap icon;
if (original_item) {
icon = original_item->data(GameListItemPath::HighResIconRole).value<QPixmap>();
if (icon.isNull()) {
icon = original_item->data(Qt::DecorationRole).value<QPixmap>();
} else {
// Apply rounded corners to the high-res icon
icon = CreateRoundIcon(icon, 256);
}
} else {
// Fallback for safety
icon = item.data(Qt::DecorationRole).value<QPixmap>();
@@ -1247,33 +1253,76 @@ void GameList::StartLaunchAnimation(const QModelIndex& item) {
zoom_anim->setEasingCurve(QEasingCurve::OutCubic);
auto* fly_fade_group = new QParallelAnimationGroup;
auto* effect = new QGraphicsOpacityEffect(animation_label);
animation_label->setGraphicsEffect(effect);
auto* icon_effect = new QGraphicsOpacityEffect(animation_label);
animation_label->setGraphicsEffect(icon_effect);
auto* fly_anim = new QPropertyAnimation(animation_label, "geometry");
fly_anim->setDuration(350);
fly_anim->setStartValue(zoom_end_geom);
fly_anim->setEndValue(fly_end_geom);
fly_anim->setEasingCurve(QEasingCurve::InQuad);
auto* fade_anim = new QPropertyAnimation(effect, "opacity");
fade_anim->setDuration(350);
fade_anim->setStartValue(1.0f);
fade_anim->setEndValue(0.0f);
fade_anim->setEasingCurve(QEasingCurve::InQuad);
auto* icon_fade_anim = new QPropertyAnimation(icon_effect, "opacity");
icon_fade_anim->setDuration(350);
icon_fade_anim->setStartValue(1.0f);
icon_fade_anim->setEndValue(0.0f);
icon_fade_anim->setEasingCurve(QEasingCurve::InQuad);
fly_fade_group->addAnimation(fly_anim);
fly_fade_group->addAnimation(fade_anim);
fly_fade_group->addAnimation(icon_fade_anim);
auto* main_group = new QSequentialAnimationGroup(animation_label);
// --- 4. CITRON LOGO TRANSITION ---
auto* logo_label = new QLabel(main_window);
QPixmap logo_pixmap(QStringLiteral(":/citron.svg"));
logo_label->setPixmap(
logo_pixmap.scaled(400, 400, Qt::KeepAspectRatio, Qt::SmoothTransformation));
logo_label->setFixedSize(400, 400);
logo_label->move(center_point.x() - 200, center_point.y() - 200);
logo_label->hide();
auto* logo_effect = new QGraphicsOpacityEffect(logo_label);
logo_label->setGraphicsEffect(logo_effect);
logo_effect->setOpacity(0.0f);
auto* logo_fade_in = new QPropertyAnimation(logo_effect, "opacity");
logo_fade_in->setDuration(500);
logo_fade_in->setStartValue(0.0f);
logo_fade_in->setEndValue(1.0f);
logo_fade_in->setEasingCurve(QEasingCurve::InOutQuad);
auto* logo_fade_out = new QPropertyAnimation(logo_effect, "opacity");
logo_fade_out->setDuration(500);
logo_fade_out->setStartValue(1.0f);
logo_fade_out->setEndValue(0.0f);
logo_fade_out->setEasingCurve(QEasingCurve::InOutQuad);
// Overlap the icon "fly-away" and the logo "fade-in"
auto* overlap_group = new QParallelAnimationGroup;
overlap_group->addAnimation(fly_fade_group);
auto* logo_fade_in_seq = new QSequentialAnimationGroup;
logo_fade_in_seq->addPause(100); // 100ms delay so it starts mid-fly
logo_fade_in_seq->addAnimation(logo_fade_in);
overlap_group->addAnimation(logo_fade_in_seq);
auto* main_group = new QSequentialAnimationGroup(this);
main_group->addAnimation(zoom_anim);
main_group->addPause(50);
main_group->addAnimation(fly_fade_group);
// When the icon animation finishes, launch the game and clean up.
// The black overlay will remain until OnEmulationEnded is called.
// Show logo once zoom is finished, just before fly/fade starts
connect(zoom_anim, &QPropertyAnimation::finished, [logo_label]() {
logo_label->show();
logo_label->raise();
});
main_group->addAnimation(overlap_group);
main_group->addPause(1000); // Shorter 1 second pause
main_group->addAnimation(logo_fade_out);
// When the animation finishes, launch the game and clean up.
connect(main_group, &QSequentialAnimationGroup::finished, this,
[this, file_path, title_id, animation_label]() {
[this, file_path, title_id, animation_label, logo_label]() {
search_field->clear();
emit GameChosen(file_path, title_id);
animation_label->deleteLater();
logo_label->deleteLater();
});
main_group->start(QAbstractAnimation::DeleteWhenStopped);

View File

@@ -18,12 +18,13 @@
#include <QString>
#include <QWidget>
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "citron/play_time_manager.h"
#include "citron/uisettings.h"
#include "citron/util/util.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/string_util.h"
enum class GameListItemType {
Game = QStandardItem::UserType + 1,
@@ -62,7 +63,8 @@ static QPixmap CreateRoundIcon(const QPixmap& pixmap, u32 size) {
painter.setRenderHint(QPainter::Antialiasing);
// Create a rounded rectangle clipping path
const int radius = size / 8; // Adjust this value to control roundness (size/8 gives subtle rounding)
const int radius =
size / 8; // Adjust this value to control roundness (size/8 gives subtle rounding)
QPainterPath path;
path.addRoundedRect(0, 0, size, size, radius, radius);
painter.setClipPath(path);
@@ -98,6 +100,7 @@ public:
static constexpr int FullPathRole = SortRole + 2;
static constexpr int ProgramIdRole = SortRole + 3;
static constexpr int FileTypeRole = SortRole + 4;
static constexpr int HighResIconRole = SortRole + 5;
GameListItemPath() = default;
GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data,
@@ -115,6 +118,9 @@ public:
picture = GetDefaultIcon(size);
}
// Store unscaled pixmap for high-quality animations
setData(picture, HighResIconRole);
// Create a round icon
QPixmap round_picture = CreateRoundIcon(picture, size);