Merge pull request 'fix/properties-window' (#1) from fix/properties-window into main

Reviewed-on: https://git.citron-emu.org/Citron/Emulator/pulls/1
This commit is contained in:
Zephyron
2025-10-29 08:57:38 +00:00
3 changed files with 418 additions and 609 deletions

View File

@@ -14,14 +14,17 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <QAbstractButton> #include <QAbstractButton>
#include <QButtonGroup>
#include <QCheckBox> #include <QCheckBox>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QFileDialog> #include <QFileDialog>
#include <QFileInfo> #include <QFileInfo>
#include <QGraphicsPixmapItem>
#include <QMessageBox> #include <QMessageBox>
#include <QMetaObject> #include <QMetaObject>
#include <QProgressDialog> #include <QProgressDialog>
#include <QPushButton> #include <QPushButton>
#include <QResizeEvent>
#include <QScrollArea> #include <QScrollArea>
#include <QString> #include <QString>
#include <QTabBar> #include <QTabBar>
@@ -64,15 +67,20 @@
ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name_, ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name_,
std::vector<VkDeviceInfo::Record>& vk_device_records, std::vector<VkDeviceInfo::Record>& vk_device_records,
Core::System& system_) Core::System& system_)
: QDialog(parent), : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_},
ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, file_name{file_name_}, system{system_}, file_name{file_name_}, system{system_},
builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())}, builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())},
tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()} , tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()},
rainbow_timer{new QTimer(this)} { rainbow_timer{new QTimer(this)} {
ui->setupUi(this);
const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name)); const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name));
const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()) const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename())
: fmt::format("{:016X}", title_id); : fmt::format("{:016X}", title_id);
game_config = std::make_unique<QtConfig>(config_file_name, Config::ConfigType::PerGameConfig); game_config = std::make_unique<QtConfig>(config_file_name, Config::ConfigType::PerGameConfig);
// Create tab instances
addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this); addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this);
audio_tab = std::make_unique<ConfigureAudio>(system_, tab_group, *builder, this); audio_tab = std::make_unique<ConfigureAudio>(system_, tab_group, *builder, this);
cpu_tab = std::make_unique<ConfigureCpu>(system_, tab_group, *builder, this); cpu_tab = std::make_unique<ConfigureCpu>(system_, tab_group, *builder, this);
@@ -85,51 +93,55 @@ rainbow_timer{new QTimer(this)} {
linux_tab = std::make_unique<ConfigureLinuxTab>(system_, tab_group, *builder, this); linux_tab = std::make_unique<ConfigureLinuxTab>(system_, tab_group, *builder, this);
system_tab = std::make_unique<ConfigureSystem>(system_, tab_group, *builder, this); system_tab = std::make_unique<ConfigureSystem>(system_, tab_group, *builder, this);
ui->setupUi(this);
if (!UISettings::values.per_game_configure_geometry.isEmpty()) { if (!UISettings::values.per_game_configure_geometry.isEmpty()) {
restoreGeometry(UISettings::values.per_game_configure_geometry); restoreGeometry(UISettings::values.per_game_configure_geometry);
} }
ApplyStaticTheme(); UpdateTheme();
UpdateTheme(); // Run once to set initial colors
connect(rainbow_timer, &QTimer::timeout, this, &ConfigurePerGame::UpdateTheme); connect(rainbow_timer, &QTimer::timeout, this, &ConfigurePerGame::UpdateTheme);
setMinimumHeight(400); button_group = new QButtonGroup(this);
button_group->setExclusive(true);
layout()->setSizeConstraint(QLayout::SetDefaultConstraint); const auto add_tab = [&](QWidget* widget, const QString& title) {
auto button = new QPushButton(title, this);
button->setCheckable(true);
button->setObjectName(QStringLiteral("tabButton"));
button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
ui->tabWidget->addTab(addons_tab.get(), tr("Add-Ons")); ui->tabButtonsLayout->addWidget(button);
button_group->addButton(button);
QScrollArea* system_scroll_area = new QScrollArea(this); QScrollArea* scroll_area = new QScrollArea(this);
system_scroll_area->setWidgetResizable(true); scroll_area->setWidgetResizable(true);
system_scroll_area->setWidget(system_tab.get()); scroll_area->setWidget(widget);
ui->tabWidget->addTab(system_scroll_area, tr("System")); ui->stackedWidget->addWidget(scroll_area);
ui->tabWidget->addTab(cpu_tab.get(), tr("CPU")); connect(button, &QPushButton::clicked, this, [this, scroll_area]() {
ui->stackedWidget->setCurrentWidget(scroll_area);
});
};
QScrollArea* graphics_scroll_area = new QScrollArea(this); add_tab(addons_tab.get(), tr("Add-Ons"));
graphics_scroll_area->setWidgetResizable(true); add_tab(system_tab.get(), tr("System"));
graphics_scroll_area->setWidget(graphics_tab.get()); add_tab(cpu_tab.get(), tr("CPU"));
ui->tabWidget->addTab(graphics_scroll_area, tr("Graphics")); add_tab(graphics_tab.get(), tr("Graphics"));
add_tab(graphics_advanced_tab.get(), tr("Adv. Graphics"));
QScrollArea* graphics_advanced_scroll_area = new QScrollArea(this); add_tab(audio_tab.get(), tr("Audio"));
graphics_advanced_scroll_area->setWidgetResizable(true); add_tab(input_tab.get(), tr("Input Profiles"));
graphics_advanced_scroll_area->setWidget(graphics_advanced_tab.get());
ui->tabWidget->addTab(graphics_advanced_scroll_area, tr("Adv. Graphics"));
ui->tabWidget->addTab(audio_tab.get(), tr("Audio"));
ui->tabWidget->addTab(input_tab.get(), tr("Input Profiles"));
linux_tab->setVisible(false);
#ifdef __unix__ #ifdef __unix__
linux_tab->setVisible(true); add_tab(linux_tab.get(), tr("Linux"));
ui->tabWidget->addTab(linux_tab.get(), tr("Linux"));
#endif #endif
ui->tabButtonsLayout->addStretch();
if (auto first_button = qobject_cast<QPushButton*>(button_group->buttons().first())) {
first_button->setChecked(true);
first_button->click();
}
setFocusPolicy(Qt::ClickFocus); setFocusPolicy(Qt::ClickFocus);
setWindowTitle(tr("Properties")); setWindowTitle(tr("Properties"));
addons_tab->SetTitleId(title_id); addons_tab->SetTitleId(title_id);
scene = new QGraphicsScene; scene = new QGraphicsScene;
@@ -137,11 +149,9 @@ rainbow_timer{new QTimer(this)} {
if (system.IsPoweredOn()) { if (system.IsPoweredOn()) {
QPushButton* apply_button = ui->buttonBox->addButton(QDialogButtonBox::Apply); QPushButton* apply_button = ui->buttonBox->addButton(QDialogButtonBox::Apply);
connect(apply_button, &QAbstractButton::clicked, this, connect(apply_button, &QAbstractButton::clicked, this, &ConfigurePerGame::HandleApplyButtonClicked);
&ConfigurePerGame::HandleApplyButtonClicked);
} }
// Connect trim XCI button
connect(ui->trim_xci_button, &QPushButton::clicked, this, &ConfigurePerGame::OnTrimXCI); connect(ui->trim_xci_button, &QPushButton::clicked, this, &ConfigurePerGame::OnTrimXCI);
LoadConfiguration(); LoadConfiguration();
@@ -171,7 +181,6 @@ void ConfigurePerGame::ApplyConfiguration() {
system.ApplySettings(); system.ApplySettings();
Settings::LogSettings(); Settings::LogSettings();
game_config->SaveAllValues(); game_config->SaveAllValues();
} }
@@ -179,7 +188,6 @@ void ConfigurePerGame::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) { if (event->type() == QEvent::LanguageChange) {
RetranslateUI(); RetranslateUI();
} }
QDialog::changeEvent(event); QDialog::changeEvent(event);
} }
@@ -197,89 +205,46 @@ void ConfigurePerGame::LoadFromFile(FileSys::VirtualFile file_) {
LoadConfiguration(); LoadConfiguration();
} }
void ConfigurePerGame::ApplyStaticTheme() {
QString raw_stylesheet = property("templateStyleSheet").toString();
QString processed_stylesheet = raw_stylesheet;
QColor accent_color(Theme::GetAccentColor());
QString accent_color_hover = accent_color.lighter(115).name(QColor::HexRgb);
QString accent_color_pressed = accent_color.darker(120).name(QColor::HexRgb);
processed_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR%%"), accent_color.name(QColor::HexRgb));
processed_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR_HOVER%%"), accent_color_hover);
processed_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR_PRESSED%%"), accent_color_pressed);
setStyleSheet(processed_stylesheet);
// Pass the processed stylesheet to the child tabs ONCE
graphics_tab->SetTemplateStyleSheet(processed_stylesheet);
system_tab->SetTemplateStyleSheet(processed_stylesheet);
audio_tab->SetTemplateStyleSheet(processed_stylesheet);
cpu_tab->SetTemplateStyleSheet(processed_stylesheet);
graphics_advanced_tab->SetTemplateStyleSheet(processed_stylesheet);
}
void ConfigurePerGame::UpdateTheme() { void ConfigurePerGame::UpdateTheme() {
if (!UISettings::values.enable_rainbow_mode.GetValue()) { QString accent_color_str;
if (rainbow_timer->isActive()) { if (UISettings::values.enable_rainbow_mode.GetValue()) {
rainbow_timer->stop(); rainbow_hue += 0.003f;
ApplyStaticTheme();
}
return;
}
rainbow_hue += 0.003f; // Even slower color transition for better performance
if (rainbow_hue > 1.0f) { if (rainbow_hue > 1.0f) {
rainbow_hue = 0.0f; rainbow_hue = 0.0f;
} }
QColor accent_color = QColor::fromHsvF(rainbow_hue, 0.8f, 1.0f); QColor accent_color = QColor::fromHsvF(rainbow_hue, 0.8f, 1.0f);
QColor accent_color_hover = accent_color.lighter(115); accent_color_str = accent_color.name(QColor::HexRgb);
QColor accent_color_pressed = accent_color.darker(120);
// Cache color names to avoid repeated string operations
const QString accent_color_name = accent_color.name(QColor::HexRgb);
const QString accent_color_hover_name = accent_color_hover.name(QColor::HexRgb);
const QString accent_color_pressed_name = accent_color_pressed.name(QColor::HexRgb);
// Efficiently update only the necessary widgets
QString tab_style = QStringLiteral(
"QTabBar::tab:selected { background-color: %1; border-color: %1; }")
.arg(accent_color_name);
ui->tabWidget->tabBar()->setStyleSheet(tab_style);
QString button_style = QStringLiteral(
"QPushButton { background-color: %1; color: #ffffff; border: none; padding: 10px 20px; border-radius: 6px; font-weight: bold; min-height: 20px; }"
"QPushButton:hover { background-color: %2; }"
"QPushButton:pressed { background-color: %3; }")
.arg(accent_color_name)
.arg(accent_color_hover_name)
.arg(accent_color_pressed_name);
ui->buttonBox->button(QDialogButtonBox::Ok)->setStyleSheet(button_style);
ui->buttonBox->button(QDialogButtonBox::Cancel)->setStyleSheet(button_style);
if (auto* apply_button = ui->buttonBox->button(QDialogButtonBox::Apply)) {
apply_button->setStyleSheet(button_style);
}
// Apply rainbow mode to the Trim XCI button
ui->trim_xci_button->setStyleSheet(button_style);
// Create a temporary full stylesheet for the child tabs to update their internal widgets
QString child_stylesheet = property("templateStyleSheet").toString();
child_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR%%"), accent_color_name);
child_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR_HOVER%%"), accent_color_hover_name);
child_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR_PRESSED%%"), accent_color_pressed_name);
// Pass the updated stylesheet to the child tabs
graphics_tab->SetTemplateStyleSheet(child_stylesheet);
system_tab->SetTemplateStyleSheet(child_stylesheet);
audio_tab->SetTemplateStyleSheet(child_stylesheet);
cpu_tab->SetTemplateStyleSheet(child_stylesheet);
graphics_advanced_tab->SetTemplateStyleSheet(child_stylesheet);
if (!rainbow_timer->isActive()) { if (!rainbow_timer->isActive()) {
rainbow_timer->start(150); // Further optimized 150ms interval for better performance rainbow_timer->start(150);
} }
} else {
if (rainbow_timer->isActive()) {
rainbow_timer->stop();
}
accent_color_str = Theme::GetAccentColor();
}
QColor accent_color(accent_color_str);
const QString accent_color_hover = accent_color.lighter(115).name(QColor::HexRgb);
const QString accent_color_pressed = accent_color.darker(120).name(QColor::HexRgb);
static QString cached_template_style_sheet;
if (cached_template_style_sheet.isEmpty()) {
cached_template_style_sheet = property("templateStyleSheet").toString();
}
QString style_sheet = cached_template_style_sheet;
style_sheet.replace(QStringLiteral("%%ACCENT_COLOR%%"), accent_color_str);
style_sheet.replace(QStringLiteral("%%ACCENT_COLOR_HOVER%%"), accent_color_hover);
style_sheet.replace(QStringLiteral("%%ACCENT_COLOR_PRESSED%%"), accent_color_pressed);
setStyleSheet(style_sheet);
// This is the crucial part that passes the theme to the child tabs
graphics_tab->SetTemplateStyleSheet(style_sheet);
system_tab->SetTemplateStyleSheet(style_sheet);
audio_tab->SetTemplateStyleSheet(style_sheet);
cpu_tab->SetTemplateStyleSheet(style_sheet);
graphics_advanced_tab->SetTemplateStyleSheet(style_sheet);
} }
void ConfigurePerGame::LoadConfiguration() { void ConfigurePerGame::LoadConfiguration() {
@@ -313,39 +278,35 @@ void ConfigurePerGame::LoadConfiguration() {
ui->display_version->setText(QStringLiteral("1.0.0")); ui->display_version->setText(QStringLiteral("1.0.0"));
} }
bool has_icon = false;
if (control.second != nullptr) { if (control.second != nullptr) {
scene->clear();
QPixmap map;
const auto bytes = control.second->ReadAllBytes(); const auto bytes = control.second->ReadAllBytes();
map.loadFromData(bytes.data(), static_cast<u32>(bytes.size())); if (map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()))) {
has_icon = true;
scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), }
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
} else { } else {
std::vector<u8> bytes; std::vector<u8> bytes;
if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) { if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) {
scene->clear(); if (map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()))) {
has_icon = true;
QPixmap map;
map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()));
scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
} }
} }
}
if (has_icon) {
scene->clear();
scene->addPixmap(map);
ui->icon_view->fitInView(scene->itemsBoundingRect(), Qt::KeepAspectRatio);
}
ui->display_filename->setText(QString::fromStdString(file->GetName())); ui->display_filename->setText(QString::fromStdString(file->GetName()));
ui->display_format->setText( ui->display_format->setText(
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())));
const auto valueText = ReadableByteSize(file->GetSize()); const auto valueText = ReadableByteSize(file->GetSize());
ui->display_size->setText(valueText); ui->display_size->setText(valueText);
std::string base_build_id_hex; std::string base_build_id_hex;
std::string update_build_id_hex; std::string update_build_id_hex;
const auto file_type = loader->GetFileType(); const auto file_type = loader->GetFileType();
if (file_type == Loader::FileType::NSO) { if (file_type == Loader::FileType::NSO) {
@@ -553,17 +514,20 @@ void ConfigurePerGame::LoadConfiguration() {
} }
} }
void ConfigurePerGame::resizeEvent(QResizeEvent* event) {
QDialog::resizeEvent(event);
if (scene && !scene->items().isEmpty()) {
ui->icon_view->fitInView(scene->itemsBoundingRect(), Qt::KeepAspectRatio);
}
}
void ConfigurePerGame::OnTrimXCI() { void ConfigurePerGame::OnTrimXCI() {
// Use the stored file name from the constructor
if (file_name.empty()) { if (file_name.empty()) {
QMessageBox::warning(this, tr("Trim XCI File"), tr("No file path available.")); QMessageBox::warning(this, tr("Trim XCI File"), tr("No file path available."));
return; return;
} }
// Convert to filesystem path with proper Unicode support
const std::filesystem::path filepath = file_name; const std::filesystem::path filepath = file_name;
// Check if the file is an XCI file
const std::string extension = filepath.extension().string(); const std::string extension = filepath.extension().string();
if (extension != ".xci" && extension != ".XCI") { if (extension != ".xci" && extension != ".XCI") {
QMessageBox::warning(this, tr("Trim XCI File"), QMessageBox::warning(this, tr("Trim XCI File"),
@@ -571,14 +535,12 @@ void ConfigurePerGame::OnTrimXCI() {
return; return;
} }
// Check if file exists
if (!std::filesystem::exists(filepath)) { if (!std::filesystem::exists(filepath)) {
QMessageBox::warning(this, tr("Trim XCI File"), QMessageBox::warning(this, tr("Trim XCI File"),
tr("The game file no longer exists.")); tr("The game file no longer exists."));
return; return;
} }
// Initialize the trimmer
Common::XCITrimmer trimmer(filepath); Common::XCITrimmer trimmer(filepath);
if (!trimmer.IsValid()) { if (!trimmer.IsValid()) {
QMessageBox::warning(this, tr("Trim XCI File"), QMessageBox::warning(this, tr("Trim XCI File"),
@@ -592,7 +554,6 @@ void ConfigurePerGame::OnTrimXCI() {
return; return;
} }
// Show file information
const u64 current_size_mb = trimmer.GetFileSize() / (1024 * 1024); const u64 current_size_mb = trimmer.GetFileSize() / (1024 * 1024);
const u64 data_size_mb = trimmer.GetDataSize() / (1024 * 1024); const u64 data_size_mb = trimmer.GetDataSize() / (1024 * 1024);
const u64 savings_mb = trimmer.GetDiskSpaceSavings() / (1024 * 1024); const u64 savings_mb = trimmer.GetDiskSpaceSavings() / (1024 * 1024);
@@ -605,7 +566,6 @@ void ConfigurePerGame::OnTrimXCI() {
"This will remove unused space from the XCI file." "This will remove unused space from the XCI file."
).arg(current_size_mb).arg(data_size_mb).arg(savings_mb); ).arg(current_size_mb).arg(data_size_mb).arg(savings_mb);
// Create custom message box with three options
QMessageBox msgBox(this); QMessageBox msgBox(this);
msgBox.setWindowTitle(tr("Trim XCI File")); msgBox.setWindowTitle(tr("Trim XCI File"));
msgBox.setText(info_message); msgBox.setText(info_message);
@@ -641,31 +601,24 @@ void ConfigurePerGame::OnTrimXCI() {
Common::U16StringFromBuffer(output_filename.utf16(), output_filename.size())}; Common::U16StringFromBuffer(output_filename.utf16(), output_filename.size())};
} }
// Pre-translate strings for use in lambda
const QString checking_text = tr("Checking free space..."); const QString checking_text = tr("Checking free space...");
const QString copying_text = tr("Copying file..."); const QString copying_text = tr("Copying file...");
// Track last operation to detect changes
size_t last_total = 0; size_t last_total = 0;
QString current_operation; QString current_operation;
// Show progress dialog
QProgressDialog progress_dialog(tr("Preparing to trim XCI file..."), tr("Cancel"), 0, 100, this); QProgressDialog progress_dialog(tr("Preparing to trim XCI file..."), tr("Cancel"), 0, 100, this);
progress_dialog.setWindowTitle(tr("Trim XCI File")); progress_dialog.setWindowTitle(tr("Trim XCI File"));
progress_dialog.setWindowModality(Qt::WindowModal); progress_dialog.setWindowModality(Qt::WindowModal);
progress_dialog.setMinimumDuration(0); progress_dialog.setMinimumDuration(0);
progress_dialog.show(); progress_dialog.show();
// Progress callback
auto progress_callback = [&](size_t current, size_t total) { auto progress_callback = [&](size_t current, size_t total) {
if (total > 0) { if (total > 0) {
// Detect operation change (when total changes significantly)
if (total != last_total) { if (total != last_total) {
last_total = total; last_total = total;
if (current == 0 || current == total) { if (current == 0 || current == total) {
// Likely switched operations
if (total < current_size_mb * 1024 * 1024) { if (total < current_size_mb * 1024 * 1024) {
// Smaller total = checking padding
current_operation = checking_text; current_operation = checking_text;
} }
} }
@@ -674,7 +627,6 @@ void ConfigurePerGame::OnTrimXCI() {
const int percent = static_cast<int>((current * 100) / total); const int percent = static_cast<int>((current * 100) / total);
progress_dialog.setValue(percent); progress_dialog.setValue(percent);
// Update label text based on operation
if (!current_operation.isEmpty()) { if (!current_operation.isEmpty()) {
const QString current_mb = QString::number(current / (1024.0 * 1024.0), 'f', 1); const QString current_mb = QString::number(current / (1024.0 * 1024.0), 'f', 1);
const QString total_mb = QString::number(total / (1024.0 * 1024.0), 'f', 1); const QString total_mb = QString::number(total / (1024.0 * 1024.0), 'f', 1);
@@ -695,16 +647,13 @@ void ConfigurePerGame::OnTrimXCI() {
QCoreApplication::processEvents(); QCoreApplication::processEvents();
}; };
// Cancel callback
auto cancel_callback = [&]() -> bool { auto cancel_callback = [&]() -> bool {
return progress_dialog.wasCanceled(); return progress_dialog.wasCanceled();
}; };
// Perform the trim operation
const auto result = trimmer.Trim(progress_callback, cancel_callback, output_path); const auto result = trimmer.Trim(progress_callback, cancel_callback, output_path);
progress_dialog.close(); progress_dialog.close();
// Show result
if (result == Common::XCITrimmer::OperationOutcome::Successful) { if (result == Common::XCITrimmer::OperationOutcome::Successful) {
const QString success_message = is_save_as ? const QString success_message = is_save_as ?
tr("XCI file successfully trimmed and saved as:\n%1") tr("XCI file successfully trimmed and saved as:\n%1")

View File

@@ -10,6 +10,7 @@
#include <QDialog> #include <QDialog>
#include <QList> #include <QList>
#include <QPixmap>
#include "configuration/shared_widget.h" #include "configuration/shared_widget.h"
#include "core/file_sys/vfs/vfs_types.h" #include "core/file_sys/vfs/vfs_types.h"
@@ -19,12 +20,16 @@
#include "citron/configuration/qt_config.h" #include "citron/configuration/qt_config.h"
#include "citron/configuration/shared_translation.h" #include "citron/configuration/shared_translation.h"
class QButtonGroup;
class QGraphicsScene;
class QTimer;
namespace Core { namespace Core {
class System; class System;
} }
namespace InputCommon { namespace InputCommon {
class InputSubsystem; class InputSubsystem;
} }
class ConfigurePerGameAddons; class ConfigurePerGameAddons;
@@ -36,22 +41,14 @@ class ConfigureInputPerGame;
class ConfigureLinuxTab; class ConfigureLinuxTab;
class ConfigureSystem; class ConfigureSystem;
class QGraphicsScene;
class QStandardItem;
class QStandardItemModel;
class QTreeView;
class QVBoxLayout;
class QTimer; // Forward declaration for the timer
namespace Ui { namespace Ui {
class ConfigurePerGame; class ConfigurePerGame;
} }
class ConfigurePerGame : public QDialog { class ConfigurePerGame : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
// Cannot use std::filesystem::path due to https://bugreports.qt.io/browse/QTBUG-73263
explicit ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name_, explicit ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name_,
std::vector<VkDeviceInfo::Record>& vk_device_records, std::vector<VkDeviceInfo::Record>& vk_device_records,
Core::System& system_); Core::System& system_);
@@ -64,13 +61,14 @@ public slots:
void accept() override; void accept() override;
void OnTrimXCI(); void OnTrimXCI();
protected:
void resizeEvent(QResizeEvent* event) override;
private: private:
void changeEvent(QEvent* event) override; void changeEvent(QEvent* event) override;
void RetranslateUI(); void RetranslateUI();
void HandleApplyButtonClicked(); void HandleApplyButtonClicked();
void LoadConfiguration(); void LoadConfiguration();
// New, efficient theme update functions
void ApplyStaticTheme(); void ApplyStaticTheme();
void UpdateTheme(); void UpdateTheme();
@@ -97,4 +95,7 @@ private:
QTimer* rainbow_timer; QTimer* rainbow_timer;
float rainbow_hue = 0.0f; float rainbow_hue = 0.0f;
QButtonGroup* button_group;
QPixmap map;
}; };

View File

@@ -12,12 +12,12 @@
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>900</width> <width>640</width>
<height>0</height> <height>480</height>
</size> </size>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Dialog</string> <string>Properties</string>
</property> </property>
<property name="templateStyleSheet" stdset="0"> <property name="templateStyleSheet" stdset="0">
<string>QDialog { <string>QDialog {
@@ -96,100 +96,34 @@
width: 0px; width: 0px;
} }
QPushButton.tabButton { QPushButton#tabButton {
background-color: #383838; background-color: #383838;
color: #ffffff; color: #ffffff;
padding: 10px 14px; padding: 8px 18px;
margin: 2px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
min-width: 85px;
max-width: 160px;
font-weight: 500;
border: 1px solid #3d3d3d;
text-align: center;
}
QPushButton.tabButton:checked {
background-color: %%ACCENT_COLOR%%;
color: #ffffff;
font-weight: bold;
border-color: %%ACCENT_COLOR%%;
}
QPushButton.tabButton:hover:!checked {
background-color: #4d4d4d;
border-color: #5d5d5d;
}
QPushButton.tabButton:pressed {
background-color: %%ACCENT_COLOR_PRESSED%%;
}
QTabWidget {
background-color: #2b2b2b;
border: none;
}
QTabWidget::pane {
border: 1px solid #3d3d3d;
background-color: #2b2b2b;
border-radius: 8px;
margin: 0px;
padding: 0px;
}
QTabWidget::tab-bar {
alignment: left;
}
QTabBar {
background-color: #2b2b2b;
border: none;
}
QTabBar::tab {
background-color: #383838;
color: #ffffff;
padding: 8px 14px;
margin-right: 2px; margin-right: 2px;
border-top-left-radius: 8px; border-top-left-radius: 8px;
border-top-right-radius: 8px; border-top-right-radius: 8px;
min-width: 85px;
font-weight: 500;
border: 1px solid #3d3d3d;
border-bottom: none; border-bottom: none;
min-width: 100px;
font-weight: bold;
font-size: 10pt;
border: 1px solid #3d3d3d;
} }
QTabBar::tab:selected { QPushButton#tabButton:checked {
background-color: %%ACCENT_COLOR%%; background-color: %%ACCENT_COLOR%%;
color: #ffffff; color: #ffffff;
font-weight: bold; font-weight: bold;
border-color: %%ACCENT_COLOR%%; border-color: %%ACCENT_COLOR%%;
} }
QTabBar::tab:hover:!selected { QPushButton#tabButton:hover:!checked {
background-color: #4d4d4d; background-color: #4d4d4d;
border-color: #5d5d5d; border-color: #5d5d5d;
} }
QTabBar QToolButton { QPushButton#tabButton:pressed {
background-color: #383838; background-color: %%ACCENT_COLOR_PRESSED%%;
border: 1px solid #3d3d3d;
border-radius: 4px;
padding: 4px;
margin: 2px;
}
QTabBar QToolButton:hover {
background-color: #4d4d4d;
border-color: %%ACCENT_COLOR%%;
}
QTabBar::scroller {
width: 30px;
} }
QGroupBox { QGroupBox {
@@ -374,9 +308,19 @@
} }
QSlider::groove:horizontal { QSlider::groove:horizontal {
border: 1px solid #5d5d5d;
height: 8px; height: 8px;
background-color: #3d3d3d; background: #3d3d3d;
border-radius: 4px;
border: 1px solid #5d5d5d;
}
QSlider::sub-page:horizontal {
background: %%ACCENT_COLOR%%;
border-radius: 4px;
}
QSlider::add-page:horizontal {
background: #3d3d3d;
border-radius: 4px; border-radius: 4px;
} }
@@ -447,9 +391,6 @@
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="widgetResizable"> <property name="widgetResizable">
<bool>true</bool> <bool>true</bool>
</property> </property>
@@ -459,9 +400,15 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>318</width> <width>318</width>
<height>538</height> <height>600</height>
</rect> </rect>
</property> </property>
<property name="minimumSize">
<size>
<width>0</width>
<height>600</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5"> <layout class="QVBoxLayout" name="verticalLayout_5">
<property name="leftMargin"> <property name="leftMargin">
<number>0</number> <number>0</number>
@@ -484,7 +431,7 @@
<item alignment="Qt::AlignHCenter"> <item alignment="Qt::AlignHCenter">
<widget class="QGraphicsView" name="icon_view"> <widget class="QGraphicsView" name="icon_view">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
@@ -516,180 +463,97 @@
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="6" column="1"> <item row="6" column="1">
<widget class="QLineEdit" name="display_size"> <widget class="QLineEdit" name="display_size">
<property name="enabled"> <property name="enabled"><bool>true</bool></property>
<bool>true</bool> <property name="readOnly"><bool>true</bool></property>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="3" column="1">
<widget class="QLineEdit" name="display_version"> <widget class="QLineEdit" name="display_version">
<property name="enabled"> <property name="enabled"><bool>true</bool></property>
<bool>true</bool> <property name="readOnly"><bool>true</bool></property>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label"><property name="text"><string>Name</string></property></widget>
<property name="text">
<string>Name</string>
</property>
</widget>
</item> </item>
<item row="4" column="0"> <item row="4" column="0">
<widget class="QLabel" name="label_4"> <widget class="QLabel" name="label_4"><property name="text"><string>Title ID</string></property></widget>
<property name="text">
<string>Title ID</string>
</property>
</widget>
</item> </item>
<item row="4" column="1"> <item row="4" column="1">
<widget class="QLineEdit" name="display_title_id"> <widget class="QLineEdit" name="display_title_id">
<property name="enabled"> <property name="enabled"><bool>true</bool></property>
<bool>true</bool> <property name="readOnly"><bool>true</bool></property>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="7" column="1"> <item row="7" column="1">
<widget class="QLineEdit" name="display_filename"> <widget class="QLineEdit" name="display_filename">
<property name="enabled"> <property name="enabled"><bool>true</bool></property>
<bool>true</bool> <property name="readOnly"><bool>true</bool></property>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="5" column="1">
<widget class="QLineEdit" name="display_format"> <widget class="QLineEdit" name="display_format">
<property name="enabled"> <property name="enabled"><bool>true</bool></property>
<bool>true</bool> <property name="readOnly"><bool>true</bool></property>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="7" column="0"> <item row="7" column="0">
<widget class="QLabel" name="label_7"> <widget class="QLabel" name="label_7"><property name="text"><string>Filename</string></property></widget>
<property name="text">
<string>Filename</string>
</property>
</widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLineEdit" name="display_name"> <widget class="QLineEdit" name="display_name">
<property name="enabled"> <property name="enabled"><bool>true</bool></property>
<bool>true</bool> <property name="readOnly"><bool>true</bool></property>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="2" column="1">
<widget class="QLineEdit" name="display_developer"> <widget class="QLineEdit" name="display_developer">
<property name="enabled"> <property name="enabled"><bool>true</bool></property>
<bool>true</bool> <property name="readOnly"><bool>true</bool></property>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="5" column="0"> <item row="5" column="0">
<widget class="QLabel" name="label_5"> <widget class="QLabel" name="label_5"><property name="text"><string>Format</string></property></widget>
<property name="text">
<string>Format</string>
</property>
</widget>
</item> </item>
<item row="3" column="0"> <item row="3" column="0">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3"><property name="text"><string>Version</string></property></widget>
<property name="text">
<string>Version</string>
</property>
</widget>
</item> </item>
<item row="6" column="0"> <item row="6" column="0">
<widget class="QLabel" name="label_6"> <widget class="QLabel" name="label_6"><property name="text"><string>Size</string></property></widget>
<property name="text">
<string>Size</string>
</property>
</widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2"><property name="text"><string>Developer</string></property></widget>
<property name="text">
<string>Developer</string>
</property>
</widget>
</item> </item>
<item row="8" column="0"> <item row="8" column="0">
<widget class="QLabel" name="label_build_id"> <widget class="QLabel" name="label_build_id"><property name="text"><string>Base Build ID</string></property></widget>
<property name="text">
<string>Base Build ID</string>
</property>
</widget>
</item> </item>
<item row="8" column="1"> <item row="8" column="1">
<widget class="QLineEdit" name="display_build_id"> <widget class="QLineEdit" name="display_build_id">
<property name="enabled"> <property name="enabled"><bool>true</bool></property>
<bool>true</bool> <property name="readOnly"><bool>true</bool></property>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="9" column="0"> <item row="9" column="0">
<widget class="QLabel" name="label_update_build_id"> <widget class="QLabel" name="label_update_build_id"><property name="text"><string>Update Build ID</string></property></widget>
<property name="text">
<string>Update Build ID</string>
</property>
</widget>
</item> </item>
<item row="9" column="1"> <item row="9" column="1">
<widget class="QLineEdit" name="display_update_build_id"> <widget class="QLineEdit" name="display_update_build_id">
<property name="enabled"> <property name="enabled"><bool>true</bool></property>
<bool>true</bool> <property name="readOnly"><bool>true</bool></property>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="10" column="0" colspan="2"> <item row="10" column="0" colspan="2">
<widget class="QPushButton" name="trim_xci_button"> <widget class="QPushButton" name="trim_xci_button">
<property name="text"> <property name="text"><string>Trim XCI File</string></property>
<string>Trim XCI File</string> <property name="toolTip"><string>Remove unused space from XCI file to reduce file size</string></property>
</property>
<property name="toolTip">
<string>Remove unused space from XCI file to reduce file size</string>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation"><enum>Qt::Vertical</enum></property>
<enum>Qt::Vertical</enum> <property name="sizeHint" stdset="0"><size><width>20</width><height>40</height></size></property>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer> </spacer>
</item> </item>
</layout> </layout>
@@ -702,16 +566,19 @@
<item> <item>
<layout class="QVBoxLayout" name="VerticalLayout"> <layout class="QVBoxLayout" name="VerticalLayout">
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout_2"/> <widget class="QScrollArea" name="tabButtonsScrollArea">
</item>
<item>
<widget class="QScrollArea" name="tabScrollArea">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>60</height>
</size>
</property>
<property name="verticalScrollBarPolicy"> <property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum> <enum>Qt::ScrollBarAlwaysOff</enum>
</property> </property>
@@ -721,43 +588,35 @@
<property name="widgetResizable"> <property name="widgetResizable">
<bool>true</bool> <bool>true</bool>
</property> </property>
<widget class="QWidget" name="tabScrollAreaWidgetContents"> <widget class="QWidget" name="tabButtonsContainer">
<property name="geometry"> <layout class="QHBoxLayout" name="tabButtonsLayout">
<rect> <property name="spacing">
<x>0</x> <number>2</number>
<y>0</y>
<width>560</width>
<height>538</height>
</rect>
</property> </property>
<layout class="QVBoxLayout" name="tabScrollAreaLayout"> <property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
</widget>
</item>
<item> <item>
<widget class="QTabWidget" name="tabWidget"> <widget class="QStackedWidget" name="stackedWidget">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="currentIndex">
<number>-1</number>
</property>
<property name="usesScrollButtons">
<bool>false</bool>
</property>
<property name="documentMode">
<bool>false</bool>
</property>
<property name="tabsClosable">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget> </widget>
</item> </item>
</layout> </layout>