diff --git a/src/citron/citron.qrc b/src/citron/citron.qrc
index a85fb4575..4f5ed3919 100644
--- a/src/citron/citron.qrc
+++ b/src/citron/citron.qrc
@@ -10,5 +10,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
../../dist/dice.svg
+ ../../dist/citron.svg
diff --git a/src/citron/game_list.cpp b/src/citron/game_list.cpp
index 9d06fbd79..bd13eae1a 100644
--- a/src/citron/game_list.cpp
+++ b/src/citron/game_list.cpp
@@ -1187,7 +1187,13 @@ void GameList::StartLaunchAnimation(const QModelIndex& item) {
QPixmap icon;
if (original_item) {
- icon = original_item->data(Qt::DecorationRole).value();
+ icon = original_item->data(GameListItemPath::HighResIconRole).value();
+ if (icon.isNull()) {
+ icon = original_item->data(Qt::DecorationRole).value();
+ } else {
+ // Apply rounded corners to the high-res icon
+ icon = CreateRoundIcon(icon, 256);
+ }
} else {
// Fallback for safety
icon = item.data(Qt::DecorationRole).value();
@@ -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);
diff --git a/src/citron/game_list_p.h b/src/citron/game_list_p.h
index 85a428d9e..dbbebdb1b 100644
--- a/src/citron/game_list_p.h
+++ b/src/citron/game_list_p.h
@@ -18,12 +18,13 @@
#include
#include
-#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& 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);