feat(ui): Fix .png & .svg rendering issue for game_list / Introduce Play Time editing in Custom Metadata

This commit is contained in:
Collecting
2026-02-15 20:09:58 -05:00
parent 637418dbaf
commit 659b23d642
8 changed files with 140 additions and 78 deletions

View File

@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2025 citron Emulator Project # SPDX-FileCopyrightText: 2026 citron Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
function(copy_citron_Qt6_deps target_dir) function(copy_citron_Qt6_deps target_dir)
@@ -13,11 +13,13 @@ function(copy_citron_Qt6_deps target_dir)
set(Qt6_PLATFORMS_DIR "${Qt6_DIR}/../../../plugins/platforms/") set(Qt6_PLATFORMS_DIR "${Qt6_DIR}/../../../plugins/platforms/")
set(Qt6_STYLES_DIR "${Qt6_DIR}/../../../plugins/styles/") set(Qt6_STYLES_DIR "${Qt6_DIR}/../../../plugins/styles/")
set(Qt6_IMAGEFORMATS_DIR "${Qt6_DIR}/../../../plugins/imageformats/") set(Qt6_IMAGEFORMATS_DIR "${Qt6_DIR}/../../../plugins/imageformats/")
set(Qt6_ICONENGINES_DIR "${Qt6_DIR}/../../../plugins/iconengines/")
set(Qt6_TLS_DIR "${Qt6_DIR}/../../../plugins/tls/") set(Qt6_TLS_DIR "${Qt6_DIR}/../../../plugins/tls/")
set(Qt6_RESOURCES_DIR "${Qt6_DIR}/../../../resources/") set(Qt6_RESOURCES_DIR "${Qt6_DIR}/../../../resources/")
set(PLATFORMS ${DLL_DEST}plugins/platforms/) set(PLATFORMS ${DLL_DEST}plugins/platforms/)
set(STYLES ${DLL_DEST}plugins/styles/) set(STYLES ${DLL_DEST}plugins/styles/)
set(IMAGEFORMATS ${DLL_DEST}plugins/imageformats/) set(IMAGEFORMATS ${DLL_DEST}plugins/imageformats/)
set(ICONENGINES ${DLL_DEST}plugins/iconengines/)
set(TLS ${DLL_DEST}tls/) set(TLS ${DLL_DEST}tls/)
if (MSVC) if (MSVC)
@@ -26,6 +28,7 @@ function(copy_citron_Qt6_deps target_dir)
Qt6Gui$<$<CONFIG:Debug>:d>.* Qt6Gui$<$<CONFIG:Debug>:d>.*
Qt6Widgets$<$<CONFIG:Debug>:d>.* Qt6Widgets$<$<CONFIG:Debug>:d>.*
Qt6Network$<$<CONFIG:Debug>:d>.* Qt6Network$<$<CONFIG:Debug>:d>.*
Qt6Svg$<$<CONFIG:Debug>:d>.*
) )
if (CITRON_USE_QT_MULTIMEDIA) if (CITRON_USE_QT_MULTIMEDIA)
windows_copy_files(${target_dir} ${Qt6_DLL_DIR} ${DLL_DEST} windows_copy_files(${target_dir} ${Qt6_DLL_DIR} ${DLL_DEST}
@@ -51,6 +54,11 @@ function(copy_citron_Qt6_deps target_dir)
windows_copy_files(citron ${Qt6_IMAGEFORMATS_DIR} ${IMAGEFORMATS} windows_copy_files(citron ${Qt6_IMAGEFORMATS_DIR} ${IMAGEFORMATS}
qjpeg$<$<CONFIG:Debug>:d>.* qjpeg$<$<CONFIG:Debug>:d>.*
qgif$<$<CONFIG:Debug>:d>.* qgif$<$<CONFIG:Debug>:d>.*
qpng$<$<CONFIG:Debug>:d>.*
qsvg$<$<CONFIG:Debug>:d>.*
)
windows_copy_files(citron ${Qt6_ICONENGINES_DIR} ${ICONENGINES}
qsvgicon$<$<CONFIG:Debug>:d>.*
) )
# Copy TLS plugins for SSL/HTTPS support (required for auto updater) # Copy TLS plugins for SSL/HTTPS support (required for auto updater)
windows_copy_files(citron ${Qt6_TLS_DIR} ${TLS} windows_copy_files(citron ${Qt6_TLS_DIR} ${TLS}

View File

@@ -1,3 +1,4 @@
// SPDX-FileCopyrightText: 2026 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <QFileDialog> #include <QFileDialog>
@@ -8,14 +9,20 @@
#include <QPushButton> #include <QPushButton>
#include "citron/custom_metadata.h" #include "citron/custom_metadata.h"
#include "citron/custom_metadata_dialog.h" #include "citron/custom_metadata_dialog.h"
#include "common/common_types.h"
#include "ui_custom_metadata_dialog.h" #include "ui_custom_metadata_dialog.h"
CustomMetadataDialog::CustomMetadataDialog(QWidget* parent, u64 program_id_, CustomMetadataDialog::CustomMetadataDialog(QWidget* parent, u64 program_id_,
const std::string& current_title) const std::string& current_title, u64 current_play_time)
: QDialog(parent), ui(std::make_unique<Ui::CustomMetadataDialog>()), program_id(program_id_) { : QDialog(parent), ui(std::make_unique<Ui::CustomMetadataDialog>()), program_id(program_id_) {
ui->setupUi(this); ui->setupUi(this);
ui->title_edit->setText(QString::fromStdString(current_title)); ui->title_edit->setText(QString::fromStdString(current_title));
const u64 hours = current_play_time / 3600;
const u64 minutes = (current_play_time % 3600) / 60;
ui->playtime_hours->setValue(static_cast<int>(hours));
ui->playtime_minutes->setValue(static_cast<int>(minutes));
if (auto current_icon_path = if (auto current_icon_path =
Citron::CustomMetadata::GetInstance().GetCustomIconPath(program_id)) { Citron::CustomMetadata::GetInstance().GetCustomIconPath(program_id)) {
icon_path = *current_icon_path; icon_path = *current_icon_path;
@@ -42,6 +49,12 @@ std::string CustomMetadataDialog::GetIconPath() const {
return icon_path; return icon_path;
} }
u64 CustomMetadataDialog::GetPlayTime() const {
const u64 hours = static_cast<u64>(ui->playtime_hours->value());
const u64 minutes = static_cast<u64>(ui->playtime_minutes->value());
return (hours * 3600) + (minutes * 60);
}
bool CustomMetadataDialog::WasReset() const { bool CustomMetadataDialog::WasReset() const {
return was_reset; return was_reset;
} }

View File

@@ -1,3 +1,4 @@
// SPDX-FileCopyrightText: 2026 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
@@ -16,12 +17,13 @@ class CustomMetadataDialog : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit CustomMetadataDialog(QWidget* parent, u64 program_id, explicit CustomMetadataDialog(QWidget* parent, u64 program_id, const std::string& current_title,
const std::string& current_title); u64 current_play_time);
~CustomMetadataDialog() override; ~CustomMetadataDialog() override;
[[nodiscard]] std::string GetTitle() const; [[nodiscard]] std::string GetTitle() const;
[[nodiscard]] std::string GetIconPath() const; [[nodiscard]] std::string GetIconPath() const;
[[nodiscard]] u64 GetPlayTime() const;
[[nodiscard]] bool WasReset() const; [[nodiscard]] bool WasReset() const;
private slots: private slots:

View File

@@ -1,4 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!--
SPDX-FileCopyrightText: 2026 citron Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->
<ui version="4.0"> <ui version="4.0">
<class>CustomMetadataDialog</class> <class>CustomMetadataDialog</class>
<widget class="QDialog" name="CustomMetadataDialog"> <widget class="QDialog" name="CustomMetadataDialog">
@@ -7,37 +11,64 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>400</width> <width>400</width>
<height>300</height> <height>350</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Edit Game Metadata</string> <string>Edit Game Metadata</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="mainLayout">
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QGridLayout" name="gridLayout">
<item> <item row="0" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Title:</string> <string>Title:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item row="0" column="1">
<widget class="QLineEdit" name="title_edit"/> <widget class="QLineEdit" name="title_edit"/>
</item> </item>
</layout> <item row="1" column="0">
</item> <widget class="QLabel" name="playtime_label">
<item> <property name="text">
<layout class="QHBoxLayout" name="horizontalLayout_2"> <string>Playtime:</string>
<item> </property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="playtime_inputs_layout">
<item>
<widget class="QSpinBox" name="playtime_hours">
<property name="suffix">
<string> Hours</string>
</property>
<property name="maximum">
<number>99999</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="playtime_minutes">
<property name="suffix">
<string> Minutes</string>
</property>
<property name="maximum">
<number>59</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
<string>Icon:</string> <string>Icon:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item row="2" column="1">
<widget class="QPushButton" name="select_icon_button"> <widget class="QPushButton" name="select_icon_button">
<property name="text"> <property name="text">
<string>Select Icon...</string> <string>Select Icon...</string>
@@ -46,74 +77,69 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QHBoxLayout" name="previewLayout">
<item>
<spacer name="leftSpacer">
<property name="orientation">
<set>Qt::Horizontal</set>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="icon_preview">
<property name="minimumSize">
<size>
<width>128</width>
<height>128</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>128</width>
<height>128</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="rightSpacer">
<property name="orientation">
<set>Qt::Horizontal</set>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="spacer_preview_top">
<property name="orientation"> <property name="orientation">
<set>Qt::Vertical</set> <set>Qt::Vertical</set>
</property> </property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>20</width> <width>20</width>
<height>40</height> <height>10</height>
</size> </size>
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<layout class="QHBoxLayout" name="preview_container_layout">
<item>
<spacer name="horizontalSpacer_left">
<property name="orientation">
<set>Qt::Horizontal</set>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="icon_preview">
<property name="minimumSize">
<size>
<width>128</width>
<height>128</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>128</width>
<height>128</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_right">
<property name="orientation">
<set>Qt::Horizontal</set>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<set>Qt::Vertical</set>
</property>
</spacer>
</item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation"> <property name="orientation">

View File

@@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2015 Citra Emulator Project // SPDX-FileCopyrightText: 2015 Citra Emulator Project
// SPDX-FileCopyrightText: 2025 citron Emulator Project // SPDX-FileCopyrightText: 2026 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <random> #include <random>
@@ -967,7 +967,7 @@ GameList::GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
// Surprise Me button - positioned after sort button // Surprise Me button - positioned after sort button
btn_surprise_me = new QToolButton(toolbar); btn_surprise_me = new QToolButton(toolbar);
QIcon surprise_icon(QStringLiteral(":/dist/dice.svg")); QIcon surprise_icon(QStringLiteral(":/dist/dice.svg"));
if (surprise_icon.isNull() || surprise_icon.availableSizes().isEmpty()) { if (surprise_icon.isNull()) {
// Fallback to theme icon or standard icon on Windows where SVG may not load // Fallback to theme icon or standard icon on Windows where SVG may not load
surprise_icon = QIcon::fromTheme(QStringLiteral("media-playlist-shuffle")); surprise_icon = QIcon::fromTheme(QStringLiteral("media-playlist-shuffle"));
if (surprise_icon.isNull()) { if (surprise_icon.isNull()) {
@@ -1623,7 +1623,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
QAction* properties = context_menu.addAction(tr("Properties")); QAction* properties = context_menu.addAction(tr("Properties"));
connect(edit_metadata, &QAction::triggered, [this, program_id, game_name] { connect(edit_metadata, &QAction::triggered, [this, program_id, game_name] {
CustomMetadataDialog dialog(this, program_id, game_name.toStdString()); const u64 current_play_time = play_time_manager.GetPlayTime(program_id);
CustomMetadataDialog dialog(this, program_id, game_name.toStdString(), current_play_time);
if (dialog.exec() == QDialog::Accepted) { if (dialog.exec() == QDialog::Accepted) {
auto& custom_metadata = Citron::CustomMetadata::GetInstance(); auto& custom_metadata = Citron::CustomMetadata::GetInstance();
if (dialog.WasReset()) { if (dialog.WasReset()) {
@@ -1634,6 +1635,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
if (!icon_path.empty()) { if (!icon_path.empty()) {
custom_metadata.SetCustomIcon(program_id, icon_path); custom_metadata.SetCustomIcon(program_id, icon_path);
} }
play_time_manager.SetPlayTime(program_id, dialog.GetPlayTime());
} }
if (main_window) { if (main_window) {
main_window->RefreshGameList(); main_window->RefreshGameList();

View File

@@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2015 Citra Emulator Project // SPDX-FileCopyrightText: 2015 Citra Emulator Project
// SPDX-FileCopyrightText: 2025 citron Emulator Project // SPDX-FileCopyrightText: 2026 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
@@ -232,7 +232,7 @@ private:
friend class GameListSearchField; friend class GameListSearchField;
const PlayTime::PlayTimeManager& play_time_manager; PlayTime::PlayTimeManager& play_time_manager;
Core::System& system; Core::System& system;
}; };

View File

@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-FileCopyrightText: 2026 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "citron/play_time_manager.h"
#include "common/alignment.h" #include "common/alignment.h"
#include "common/fs/file.h" #include "common/fs/file.h"
#include "common/fs/fs.h" #include "common/fs/fs.h"
@@ -9,7 +11,6 @@
#include "common/settings.h" #include "common/settings.h"
#include "common/thread.h" #include "common/thread.h"
#include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/acc/profile_manager.h"
#include "citron/play_time_manager.h"
namespace PlayTime { namespace PlayTime {
@@ -159,6 +160,14 @@ u64 PlayTimeManager::GetPlayTime(u64 program_id) const {
} }
} }
void PlayTimeManager::SetPlayTime(u64 program_id, u64 play_time) {
if (program_id == 0) {
return;
}
database[program_id] = play_time;
Save();
}
void PlayTimeManager::ResetProgramPlayTime(u64 program_id) { void PlayTimeManager::ResetProgramPlayTime(u64 program_id) {
database.erase(program_id); database.erase(program_id);
Save(); Save();

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-FileCopyrightText: 2026 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
@@ -30,6 +31,7 @@ public:
CITRON_NON_MOVEABLE(PlayTimeManager); CITRON_NON_MOVEABLE(PlayTimeManager);
u64 GetPlayTime(u64 program_id) const; u64 GetPlayTime(u64 program_id) const;
void SetPlayTime(u64 program_id, u64 play_time);
void ResetProgramPlayTime(u64 program_id); void ResetProgramPlayTime(u64 program_id);
void SetProgramId(u64 program_id); void SetProgramId(u64 program_id);
void Start(); void Start();