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>
<qresource prefix="/"> <qresource prefix="/">
<file alias="dist/dice.svg">../../dist/dice.svg</file> <file alias="dist/dice.svg">../../dist/dice.svg</file>
<file alias="citron.svg">../../dist/citron.svg</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@@ -1187,7 +1187,13 @@ void GameList::StartLaunchAnimation(const QModelIndex& item) {
QPixmap icon; QPixmap icon;
if (original_item) { if (original_item) {
icon = original_item->data(Qt::DecorationRole).value<QPixmap>(); 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 { } else {
// Fallback for safety // Fallback for safety
icon = item.data(Qt::DecorationRole).value<QPixmap>(); icon = item.data(Qt::DecorationRole).value<QPixmap>();
@@ -1247,33 +1253,76 @@ void GameList::StartLaunchAnimation(const QModelIndex& item) {
zoom_anim->setEasingCurve(QEasingCurve::OutCubic); zoom_anim->setEasingCurve(QEasingCurve::OutCubic);
auto* fly_fade_group = new QParallelAnimationGroup; auto* fly_fade_group = new QParallelAnimationGroup;
auto* effect = new QGraphicsOpacityEffect(animation_label); auto* icon_effect = new QGraphicsOpacityEffect(animation_label);
animation_label->setGraphicsEffect(effect); animation_label->setGraphicsEffect(icon_effect);
auto* fly_anim = new QPropertyAnimation(animation_label, "geometry"); auto* fly_anim = new QPropertyAnimation(animation_label, "geometry");
fly_anim->setDuration(350); fly_anim->setDuration(350);
fly_anim->setStartValue(zoom_end_geom); fly_anim->setStartValue(zoom_end_geom);
fly_anim->setEndValue(fly_end_geom); fly_anim->setEndValue(fly_end_geom);
fly_anim->setEasingCurve(QEasingCurve::InQuad); fly_anim->setEasingCurve(QEasingCurve::InQuad);
auto* fade_anim = new QPropertyAnimation(effect, "opacity"); auto* icon_fade_anim = new QPropertyAnimation(icon_effect, "opacity");
fade_anim->setDuration(350); icon_fade_anim->setDuration(350);
fade_anim->setStartValue(1.0f); icon_fade_anim->setStartValue(1.0f);
fade_anim->setEndValue(0.0f); icon_fade_anim->setEndValue(0.0f);
fade_anim->setEasingCurve(QEasingCurve::InQuad); icon_fade_anim->setEasingCurve(QEasingCurve::InQuad);
fly_fade_group->addAnimation(fly_anim); 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->addAnimation(zoom_anim);
main_group->addPause(50); main_group->addPause(50);
main_group->addAnimation(fly_fade_group);
// When the icon animation finishes, launch the game and clean up. // Show logo once zoom is finished, just before fly/fade starts
// The black overlay will remain until OnEmulationEnded is called. 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, 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(); search_field->clear();
emit GameChosen(file_path, title_id); emit GameChosen(file_path, title_id);
animation_label->deleteLater(); animation_label->deleteLater();
logo_label->deleteLater();
}); });
main_group->start(QAbstractAnimation::DeleteWhenStopped); main_group->start(QAbstractAnimation::DeleteWhenStopped);

View File

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