gui: themes and qss refactor

# Conflicts:
#	.github/workflows/c-cpp.yml
#	.github/workflows/codeql-analysis.yml
#	vita3k/gui-qt/src/controls_dialog.cpp
This commit is contained in:
KorewaWatchful
2026-05-21 15:05:47 -04:00
committed by Gamid
parent cf37c74444
commit 85cb48046f
51 changed files with 6300 additions and 730 deletions
+3 -1
View File
@@ -42,7 +42,9 @@ sudo apt -y install \
libpipewire-0.3-dev \
libwayland-dev \
libdecor-0-dev \
liburing-dev
liburing-dev \
libgstreamer-plugins-bad1.0-0 \
libgstreamer-plugins-good1.0-0
if [[ "$APPIMAGE_MODE" == "with-appimage" ]]; then
curl -sLO "https://github.com/linuxdeploy/linuxdeploy/releases/latest/download/linuxdeploy-${ARCH}.AppImage"
+3
View File
@@ -134,6 +134,7 @@ jobs:
curl:p
zlib:p
qt6-base:p
qt6-multimedia:p
qt6-svg:p
qt6-tools:p
@@ -146,6 +147,7 @@ jobs:
if: matrix.platform == 'windows-x64'
with:
version: ${{ env.QT_VERSION }}
modules: qtmultimedia
host: windows
target: desktop
arch: win64_msvc2022_64
@@ -158,6 +160,7 @@ jobs:
if: matrix.platform != 'windows-arm64' && matrix.platform != 'windows-x64'
with:
version: ${{ env.QT_VERSION }}
modules: qtmultimedia
cache: true
install-deps: ${{ matrix.qt_install_deps }}
+1
View File
@@ -59,6 +59,7 @@ jobs:
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
modules: qtmultimedia
cache: true
install-deps: 'true'
+2 -1
View File
@@ -4,7 +4,7 @@ add_library(vita3k_qt6 INTERFACE)
set(VITA3K_QT_MIN_VER 6.11.0)
set(VITA3K_QT_COMPONENTS Core Gui Widgets Network Concurrent Svg LinguistTools)
set(VITA3K_QT_COMPONENTS Core Gui Widgets Network Concurrent Svg Multimedia LinguistTools)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
list(APPEND VITA3K_QT_COMPONENTS GuiPrivate)
endif()
@@ -39,6 +39,7 @@ target_link_libraries(vita3k_qt6 INTERFACE
Qt6::Network
Qt6::Concurrent
Qt6::Svg
Qt6::Multimedia
)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND TARGET Qt6::GuiPrivate)
+14 -3
View File
@@ -226,6 +226,8 @@ if(NOT ANDROID)
"${CMAKE_SOURCE_DIR}/i18n/qt/*.ts")
set(VITA3K_QT_QM_DIR "${CMAKE_BINARY_DIR}/qt_translations")
set(VITA3K_QT_QM_FILES "")
set(VITA3K_GUI_CONFIG_THEME_QSS "${CMAKE_CURRENT_SOURCE_DIR}/gui-qt/resources/themes/waves/theme.qss")
set(VITA3K_GUI_CONFIG_THEME_BG "${CMAKE_CURRENT_SOURCE_DIR}/gui-qt/resources/themes/waves/shell_background.svg")
foreach(ts_file IN LISTS VITA3K_QT_TS_FILES)
get_filename_component(ts_name "${ts_file}" NAME_WE)
@@ -285,9 +287,12 @@ if(APPLE)
add_custom_command(
TARGET vita3k
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:vita3k>/../Resources/gui-configs"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/../data" "$<TARGET_FILE_DIR:vita3k>/../Resources/data"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${VITA3K_QT_QM_DIR}" "$<TARGET_FILE_DIR:vita3k>/../Resources/translations"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/shaders-builtin/" "$<TARGET_FILE_DIR:vita3k>/../Resources/shaders-builtin")
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/shaders-builtin/" "$<TARGET_FILE_DIR:vita3k>/../Resources/shaders-builtin"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${VITA3K_GUI_CONFIG_THEME_QSS}" "$<TARGET_FILE_DIR:vita3k>/../Resources/gui-configs/Waves.qss"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${VITA3K_GUI_CONFIG_THEME_BG}" "$<TARGET_FILE_DIR:vita3k>/../Resources/gui-configs/shell_background.svg")
if(USE_DISCORD_RICH_PRESENCE)
if(ARCHITECTURE STREQUAL "x86_64")
set(discord_dylib "${CMAKE_BINARY_DIR}/external/discord_game_sdk/lib/x86_64/discord_game_sdk.dylib")
@@ -352,10 +357,13 @@ elseif(LINUX)
add_custom_command(
TARGET vita3k
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:vita3k>/gui-configs"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/../data" "$<TARGET_FILE_DIR:vita3k>/data"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${VITA3K_QT_QM_DIR}" "$<TARGET_FILE_DIR:vita3k>/translations"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/shaders-builtin" "$<TARGET_FILE_DIR:vita3k>/shaders-builtin"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/icons" "$<TARGET_FILE_DIR:vita3k>/icons")
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/icons" "$<TARGET_FILE_DIR:vita3k>/icons"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${VITA3K_GUI_CONFIG_THEME_QSS}" "$<TARGET_FILE_DIR:vita3k>/gui-configs/Waves.qss"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${VITA3K_GUI_CONFIG_THEME_BG}" "$<TARGET_FILE_DIR:vita3k>/gui-configs/shell_background.svg")
if(USE_DISCORD_RICH_PRESENCE)
add_custom_command(
TARGET vita3k
@@ -370,10 +378,13 @@ elseif(WIN32)
add_custom_command(
TARGET vita3k
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:vita3k>/gui-configs"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/../data" "$<TARGET_FILE_DIR:vita3k>/data"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${VITA3K_QT_QM_DIR}" "$<TARGET_FILE_DIR:vita3k>/translations"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/shaders-builtin" "$<TARGET_FILE_DIR:vita3k>/shaders-builtin"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/icons" "$<TARGET_FILE_DIR:vita3k>/icons")
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/icons" "$<TARGET_FILE_DIR:vita3k>/icons"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${VITA3K_GUI_CONFIG_THEME_QSS}" "$<TARGET_FILE_DIR:vita3k>/gui-configs/Waves.qss"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${VITA3K_GUI_CONFIG_THEME_BG}" "$<TARGET_FILE_DIR:vita3k>/gui-configs/shell_background.svg")
if(USE_DISCORD_RICH_PRESENCE)
add_custom_command(
TARGET vita3k
+8 -2
View File
@@ -24,7 +24,10 @@ add_library(gui-qt STATIC
src/qt_utils.cpp
src/settings_dialog.cpp
src/settings_dialog_tooltips.cpp
src/stylesheets.cpp
src/theme_manager.cpp
src/theme_surface.cpp
src/vita_theme.cpp
src/vita_themes_dialog.cpp
src/trophy_collection_dialog.cpp
src/update_manager.cpp
src/gui_language.cpp
@@ -66,7 +69,10 @@ add_library(gui-qt STATIC
include/gui-qt/qt_utils.h
include/gui-qt/settings_dialog.h
include/gui-qt/settings_dialog_tooltips.h
include/gui-qt/stylesheets.h
include/gui-qt/theme_manager.h
include/gui-qt/theme_surface.h
include/gui-qt/vita_theme.h
include/gui-qt/vita_themes_dialog.h
include/gui-qt/trophy_collection_dialog.h
include/gui-qt/update_manager.h
include/gui-qt/gui_language.h
@@ -65,6 +65,7 @@ public:
bool handle_sdl_event(const SDL_Event &event);
protected:
void changeEvent(QEvent *event) override;
void closeEvent(QCloseEvent *event) override;
void done(int result) override;
void keyPressEvent(QKeyEvent *event) override;
@@ -216,6 +217,7 @@ private:
void refresh_controller_tabs();
void refresh_controller_page_labels(int index);
void refresh_controller_art();
void set_controller_page_enabled(int index, bool enabled);
void set_controller_page_view(int index, bool show_bindings);
void start_controller_capture(int tab_index, int binding_index);
+6 -3
View File
@@ -28,7 +28,6 @@ const QString Settings = QStringLiteral("CurrentSettings");
const QString LightStylesheet = QStringLiteral("light");
const QString DarkStylesheet = QStringLiteral("dark");
const QString NoStylesheet = QStringLiteral("none");
const QString main_window = QStringLiteral("MainWindow");
const QString apps_list = QStringLiteral("GameList");
@@ -38,6 +37,7 @@ const QString trophy = QStringLiteral("Trophy");
const QString controls = QStringLiteral("Controls");
const QString settings = QStringLiteral("Settings");
const QString user_mgmt = QStringLiteral("UserManagement");
const QString vita_themes = QStringLiteral("VitaThemes");
const QString meta = QStringLiteral("Meta");
const GuiSave m_currentStylesheet = GuiSave(meta, QStringLiteral("currentStylesheet"), DarkStylesheet);
@@ -86,6 +86,11 @@ const GuiSave sd_lastTab = GuiSave(settings, QStringLiteral("lastTab"), 0);
const GuiSave um_geometry = GuiSave(user_mgmt, QStringLiteral("geometry"), QByteArray());
const GuiSave vt_geometry = GuiSave(vita_themes, QStringLiteral("geometry"), QByteArray());
const GuiSave vt_appliedSelection = GuiSave(vita_themes, QStringLiteral("appliedSelection"), QString());
const GuiSave vt_bgmEnabled = GuiSave(vita_themes, QStringLiteral("bgmEnabled"), false);
const GuiSave vt_bgmVolume = GuiSave(vita_themes, QStringLiteral("bgmVolume"), 50);
} // namespace gui
class GuiSettings : public GuiSettingsBase {
@@ -93,6 +98,4 @@ class GuiSettings : public GuiSettingsBase {
public:
explicit GuiSettings(const QString &settings_dir, QObject *parent = nullptr);
QStringList get_stylesheet_entries() const;
};
+5 -5
View File
@@ -24,11 +24,13 @@
#include <QEvent>
#include <QLineEdit>
#include <QPlainTextEdit>
#include <QPointer>
#include <QTextCursor>
#include <QTimer>
#include <QToolButton>
#include <QWidget>
#include <deque>
class ThemeSurface;
class LogWidget : public custom_dock_widget {
Q_OBJECT
@@ -51,8 +53,6 @@ private:
void on_log_message(const QString &msg, int level);
void drain_pending_messages();
void append_log_entry(const QString &msg, int level, bool preserve_scroll = true);
QColor color_for_level(int level) const;
void trim_entries();
void show_search_bar();
void hide_search_bar();
void find_text(bool backwards = false);
@@ -63,11 +63,11 @@ private:
int effective_buffer_size(int requested_size) const;
QPlainTextEdit *m_log;
QWidget *m_search_container = nullptr;
QPointer<ThemeSurface> m_search_container;
QLineEdit *m_search_edit = nullptr;
QToolButton *m_search_prev_button = nullptr;
QToolButton *m_search_next_button = nullptr;
QToolButton *m_search_close_button = nullptr;
QTextCursor m_current_search_cursor;
std::deque<std::pair<QString, int>> m_entries;
QTimer *m_drain_timer = nullptr;
};
+27 -7
View File
@@ -22,6 +22,7 @@
#include <emuenv/state.h>
#include <QMainWindow>
#include <QPixmap>
#include <QPointer>
#include <QTimer>
@@ -43,9 +44,11 @@ class GameCompatibility;
class QWindow;
class QWidget;
class QLineEdit;
class QPaintEvent;
class QPushButton;
class QSlider;
class QToolBar;
class QVariantAnimation;
class TrophyCollectionDialog;
class CtrlKeyboardFilter;
class DebugWidget;
@@ -54,6 +57,8 @@ class UserManagementDialog;
class UpdateManager;
class ControlsDialog;
class SettingsDialog;
class ThemeManager;
class VitaThemesDialog;
namespace app {
struct SettingsCommitResult;
}
@@ -77,6 +82,7 @@ protected:
void closeEvent(QCloseEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void dropEvent(QDropEvent *event) override;
void paintEvent(QPaintEvent *event) override;
private slots:
void on_about_action_triggered();
@@ -91,6 +97,7 @@ private slots:
void pump_sdl_events();
void open_trophy_collection();
void open_user_management();
void open_vita_themes();
void show_live_area(const std::string &title_id);
void on_live_area_play();
void on_live_area_closed();
@@ -155,15 +162,19 @@ private:
void open_debug_widget(int tab);
void apply_log_gui_settings();
void repaint_gui();
void init_default_style();
void init_first_run_stylesheet();
void apply_stylesheet();
void apply_theme_background_presentation();
void clear_theme_background_presentation();
void rescale_theme_background_pixmaps();
void advance_theme_background_presentation();
EmuEnvState &emuenv;
app::AppSessionController m_app_session;
std::unique_ptr<Ui::MainWindow> m_ui;
std::shared_ptr<GuiSettings> m_gui_settings;
std::shared_ptr<PersistentSettings> m_persistent_settings;
std::unique_ptr<ThemeManager> m_theme_manager;
QMainWindow *m_dock_host = nullptr;
AppsList *m_apps_list_widget = nullptr;
@@ -173,25 +184,34 @@ private:
QWindow *m_game_container = nullptr;
QTimer *m_sdl_pump_timer = nullptr;
TrophyCollectionDialog *m_trophy_dialog = nullptr;
UserManagementDialog *m_user_mgmt_dialog = nullptr;
QPointer<TrophyCollectionDialog> m_trophy_dialog;
QPointer<UserManagementDialog> m_user_mgmt_dialog;
CtrlKeyboardFilter *m_kb_filter = nullptr;
DebugWidget *m_debug_widget = nullptr;
QPointer<DebugWidget> m_debug_widget;
LiveAreaWidget *m_live_area_widget = nullptr;
UpdateManager *m_update_manager = nullptr;
ControlsDialog *m_controls_dialog = nullptr;
SettingsDialog *m_settings_dialog = nullptr;
QPointer<ControlsDialog> m_controls_dialog;
QPointer<SettingsDialog> m_settings_dialog;
QPointer<VitaThemesDialog> m_vita_themes_dialog;
std::string m_live_area_title_id;
QLineEdit *m_search_bar = nullptr;
QSlider *m_icon_size_slider = nullptr;
QWidget *m_slider_container = nullptr;
std::vector<QPointer<QWidget>> m_auxiliary_windows;
QTimer *m_theme_background_cycle_timer = nullptr;
QVariantAnimation *m_theme_background_fade_animation = nullptr;
std::vector<QPixmap> m_theme_background_original_pixmaps;
std::vector<QPixmap> m_theme_background_scaled_pixmaps;
QSize m_theme_background_scaled_size;
int m_theme_background_cycle_interval_ms = 15000;
int m_theme_background_current_index = 0;
int m_theme_background_next_index = -1;
qreal m_theme_background_fade_progress = 0.0;
bool m_is_app_closing = false;
bool m_fullscreen = false;
bool m_app_selected = false;
bool m_admin_privileged = false;
QString m_default_style;
#if USE_DISCORD
bool m_discord_rich_presence_old = false;
#endif
+11 -11
View File
@@ -50,8 +50,18 @@ void open_dir(const fs::path &path);
fs::path to_fs_path(const QString &path);
QString to_qt_path(const fs::path &path);
QString sanitize_relative_path_reference(const QString &path);
QString resolve_relative_path_in_root(const QString &relative_path, const QString &root_path);
QColor get_foreground_color(QWidget *widget = nullptr);
QColor foreground_color(QWidget *widget = nullptr);
QColor muted_text_color();
QColor toolbar_icon_tint();
QColor theme_role_color(
const QString &role,
QPalette::ColorRole color_role = QPalette::WindowText);
QString format_help_html(const QString &title, const QString &body);
void invalidate_theme_cache();
void refresh_theme_state(QWidget *widget);
QIcon get_colorized_icon(const QIcon &icon,
const QColor &source_color,
@@ -59,16 +69,6 @@ QIcon get_colorized_icon(const QIcon &icon,
bool dark_mode_active();
QColor get_label_color(
const QString &object_name,
const QColor &fallback_light,
const QColor &fallback_dark,
QPalette::ColorRole color_role = QPalette::WindowText);
QColor get_label_color(
const QColor &fallback_light,
const QColor &fallback_dark);
MessageDialogResult show_message_box(
QWidget *parent,
QMessageBox::Icon icon,
@@ -30,6 +30,7 @@
struct EmuEnvState;
class GuiSettings;
class SettingsDialogTooltips;
class ThemeManager;
enum class SettingsTab : int {
Core = 0,
@@ -54,6 +55,7 @@ class SettingsDialog final : public QDialog {
public:
explicit SettingsDialog(EmuEnvState &emuenv,
std::shared_ptr<GuiSettings> gui_settings,
ThemeManager &theme_manager,
const std::string &app_path = {},
int initial_tab = 0);
~SettingsDialog();
@@ -69,6 +71,7 @@ public:
Q_SIGNALS:
void gui_stylesheet_request();
void gui_theme_music_settings_request();
void gui_log_settings_request();
void storage_path_changed();
void ui_language_request(const QString &locale_tag);
@@ -78,6 +81,7 @@ private slots:
void on_save_clicked();
void on_apply_clicked();
void on_close_clicked();
void refresh_themes();
private:
bool apply_pending_storage_path();
@@ -103,7 +107,7 @@ private:
void mark_dirty();
void set_pending_pref_path(const fs::path &pref_path);
void set_description(QWidget *tab, const QString &title, const QString &text);
void add_stylesheets();
void add_stylesheets(const QString &preferred_name = {});
void apply_stylesheet(bool reset = false);
void closeEvent(QCloseEvent *event) override;
@@ -114,6 +118,7 @@ private:
EmuEnvState &emuenv;
std::shared_ptr<GuiSettings> m_gui_settings;
ThemeManager &m_theme_manager;
std::unique_ptr<SettingsDialogTooltips> m_tooltips;
std::string m_app_path;
int m_initial_tab;
@@ -60,6 +60,8 @@ public:
QString memory_mapping;
QString audio_backend;
QString audio_volume;
QString theme_music_enable;
QString theme_music_volume;
QString ngs_enable;
QString front_camera;
QString back_camera;
@@ -0,0 +1,133 @@
// Vita3K emulator project
// Copyright (C) 2026 Vita3K team
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#pragma once
#include <gui-qt/vita_theme.h>
#include <util/fs.h>
#include <QList>
#include <QObject>
#include <QPalette>
#include <QString>
#include <QStringList>
#include <memory>
#include <optional>
#include <vector>
class GuiSettings;
class QAudioOutput;
class QMediaPlayer;
namespace gui {
struct ThemeEntry {
QString name;
QString display_name;
QString qss_source;
QString base_path;
};
struct VitaThemeSelection {
QString theme_id;
QString background_id;
int readability = 55;
bool normalize_font_colors = true;
bool cycle_enabled = false;
int cycle_interval_seconds = 15;
QStringList cycle_background_ids;
};
VitaThemeSelection normalize_vita_theme_selection(
const VitaThemeInfo &theme,
VitaThemeSelection selection);
struct ThemeBackgroundPresentation {
QStringList image_paths;
int interval_ms = 0;
bool animated = false;
};
struct AppliedThemeState {
bool is_vita_theme = false;
ThemeBackgroundPresentation background;
};
} // namespace gui
class ThemeManager final : public QObject {
Q_OBJECT
public:
explicit ThemeManager(std::shared_ptr<GuiSettings> gui_settings,
QObject *parent = nullptr);
QList<gui::ThemeEntry> available_themes() const;
bool apply_theme(const QString &name = {}, bool force_reload = false);
void set_pref_path(const fs::path &pref_path);
void set_vita_themes_root(const fs::path &themes_root);
void refresh_vita_theme_catalog();
const std::vector<gui::VitaThemeInfo> &installed_vita_themes(bool force_reload = false) const;
void ensure_generated_theme_ready(const fs::path &themes_root);
std::optional<gui::VitaThemeSelection> applied_vita_theme_selection() const;
bool apply_vita_theme_selection(
const gui::VitaThemeSelection &selection,
bool force_reload = true);
std::optional<gui::AppliedThemeState> current_applied_theme_state() const;
void clear_applied_vita_theme();
void refresh_vita_theme_bgm();
void set_vita_theme_bgm_blocked(bool blocked);
Q_SIGNALS:
void theme_state_changed();
void theme_catalog_changed();
private:
void save_applied_selection(const gui::VitaThemeSelection &selection) const;
void switch_to_fallback_theme(const QString &reason);
QString requested_theme_name() const;
const gui::VitaThemeInfo *find_installed_vita_theme(const QString &theme_id, bool force_reload = false) const;
std::optional<gui::ThemeEntry> builtin_theme(const QString &name) const;
std::optional<gui::ThemeEntry> generated_vita_theme() const;
QList<gui::ThemeEntry> custom_themes() const;
std::optional<gui::ThemeEntry> resolve_theme(const QString &name) const;
std::optional<gui::AppliedThemeState> build_applied_theme_state(const gui::ThemeEntry &theme, const QString &stylesheet) const;
bool apply_theme_entry(const gui::ThemeEntry &theme, bool force_reload);
bool apply_theme_entry(const gui::ThemeEntry &theme, const QString &stylesheet, bool force_reload);
std::optional<QString> load_stylesheet(const gui::ThemeEntry &theme) const;
std::optional<QPalette> parse_palette(const QString &stylesheet) const;
QString resolve_stylesheet_urls(const QString &stylesheet, const gui::ThemeEntry &theme) const;
QString resolve_stylesheet_url(const QString &url, const gui::ThemeEntry &theme) const;
fs::path generated_vita_theme_path(const QString &theme_id) const;
QString generated_vita_theme_display_name(const QString &theme_id) const;
void ensure_fusion_style() const;
void stop_vita_theme_bgm();
std::shared_ptr<GuiSettings> m_gui_settings;
QString m_current_theme_name;
fs::path m_pref_path;
fs::path m_vita_themes_root;
QAudioOutput *m_vita_theme_bgm_audio_output = nullptr;
QMediaPlayer *m_vita_theme_bgm_player = nullptr;
QString m_current_vita_theme_bgm_source_path;
std::optional<gui::AppliedThemeState> m_applied_theme_state;
mutable std::vector<gui::VitaThemeInfo> m_installed_vita_theme_cache;
mutable bool m_vita_theme_cache_loaded = false;
bool m_vita_theme_bgm_blocked = false;
};
@@ -17,11 +17,12 @@
#pragma once
#include <QString>
#include <QWidget>
namespace gui::stylesheets {
class ThemeSurface : public QWidget {
public:
explicit ThemeSurface(QWidget *parent = nullptr);
extern const QString light_style_sheet;
extern const QString dark_style_sheet;
} // namespace gui::stylesheets
protected:
void paintEvent(QPaintEvent *event) override;
};
+93
View File
@@ -0,0 +1,93 @@
// Vita3K emulator project
// Copyright (C) 2026 Vita3K team
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#pragma once
#include <util/fs.h>
#include <cstddef>
#include <cstdint>
#include <ctime>
#include <optional>
#include <string>
#include <vector>
namespace gui {
enum class VitaThemeBackgroundSource {
Home,
Lockscreen,
};
struct VitaThemeColor {
std::uint8_t red = 0;
std::uint8_t green = 0;
std::uint8_t blue = 0;
std::uint8_t alpha = 255;
};
struct VitaThemeBackgroundOption {
std::string id;
std::string label;
fs::path asset_path;
VitaThemeBackgroundSource source = VitaThemeBackgroundSource::Home;
std::optional<VitaThemeColor> primary_text_color;
};
struct VitaThemePalette {
std::optional<VitaThemeColor> information_bar_color;
std::optional<VitaThemeColor> information_indicator_color;
std::optional<VitaThemeColor> notify_background_color;
std::optional<VitaThemeColor> notify_border_color;
std::optional<VitaThemeColor> notify_font_color;
std::optional<VitaThemeColor> date_color;
};
struct VitaThemeInfo {
std::string theme_id;
std::string content_id;
std::string title;
std::string provider;
std::string content_version;
fs::path installed_path;
std::optional<fs::path> bgm_path;
fs::path package_thumbnail_path;
std::uintmax_t installed_size = 0;
std::time_t updated_time = 0;
std::vector<VitaThemeBackgroundOption> background_options;
VitaThemePalette palette;
};
std::optional<VitaThemeInfo> load_vita_theme(
const fs::path &theme_dir,
const std::string &preferred_locale = {});
std::vector<VitaThemeInfo> enumerate_installed_vita_themes(
const fs::path &themes_root,
const std::string &preferred_locale = {});
std::optional<fs::path> synthesize_vita_theme_qss(
const VitaThemeInfo &theme,
const std::string &background_option_id,
int readability_percent,
bool normalize_font_colors,
const std::vector<fs::path> &background_image_paths,
int cycle_interval_seconds,
bool cycle_enabled,
const fs::path &pref_path,
const fs::path &gui_settings_dir);
} // namespace gui
@@ -0,0 +1,149 @@
// Vita3K emulator project
// Copyright (C) 2026 Vita3K team
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#pragma once
#include <gui-qt/theme_manager.h>
#include <gui-qt/vita_theme.h>
#include <QDialog>
#include <memory>
#include <optional>
#include <vector>
class GuiSettings;
class ThemeManager;
class QListWidget;
class QLabel;
class QPushButton;
class QWidget;
class QHBoxLayout;
class QVBoxLayout;
class QSlider;
class QCheckBox;
class QSpinBox;
class QCloseEvent;
class QResizeEvent;
class QEvent;
class QButtonGroup;
class QRadioButton;
class QTextBrowser;
class QTreeWidget;
class QTreeWidgetItem;
class VitaThemesDialog final : public QDialog {
Q_OBJECT
public:
explicit VitaThemesDialog(
std::shared_ptr<GuiSettings> gui_settings,
ThemeManager &theme_manager,
QWidget *parent = nullptr);
public Q_SLOTS:
void reload();
protected:
void closeEvent(QCloseEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
bool eventFilter(QObject *watched, QEvent *event) override;
private:
struct BackgroundRow {
QString id;
QTreeWidgetItem *item = nullptr;
QRadioButton *primary_radio = nullptr;
QCheckBox *cycle_checkbox = nullptr;
};
void build_ui();
void restore_persistent_state();
void save_persistent_state() const;
void repopulate_theme_list(const QString &preferred_theme_id = {});
void apply_theme_selection(int row);
void clear_selection_state();
gui::VitaThemeSelection selection_from_ui() const;
void load_selection_into_ui(
gui::VitaThemeSelection selection,
std::optional<QString> preview_background_id = std::nullopt,
bool follow_primary_preview = false,
bool reload_theme_ui = false);
void load_theme_into_ui(const gui::VitaThemeInfo *theme);
void sync_background_rows();
void sync_preview();
void sync_metadata();
void sync_actions();
void sync_cycle_controls();
void set_selected_background(const QString &background_id);
void set_selected_cycle_backgrounds(const QStringList &background_ids);
void update_preview_pixmap();
void update_package_thumbnail();
void update_readability_label();
void apply_selected_theme();
void delete_selected_theme();
void open_selected_theme_folder() const;
void register_description(QWidget *widget, const QString &title, const QString &description);
void set_description(const QString &title, const QString &text);
void reset_description();
QString default_description() const;
void set_preview_background(const QString &background_id);
QString resolve_preview_background_id(
const gui::VitaThemeInfo &theme,
const QString &background_id) const;
const gui::VitaThemeInfo *current_theme() const;
const gui::VitaThemeBackgroundOption *current_background() const;
const gui::VitaThemeBackgroundOption *current_preview_background() const;
std::shared_ptr<GuiSettings> m_gui_settings;
ThemeManager &m_theme_manager;
std::vector<gui::VitaThemeInfo> m_themes;
QListWidget *m_theme_list = nullptr;
QLabel *m_package_thumbnail = nullptr;
QLabel *m_theme_title = nullptr;
QLabel *m_theme_provider = nullptr;
QLabel *m_preview = nullptr;
QTreeWidget *m_background_list = nullptr;
QCheckBox *m_cycle_checkbox = nullptr;
QSpinBox *m_cycle_interval_spinbox = nullptr;
QLabel *m_cycle_summary_value = nullptr;
QLabel *m_version_value = nullptr;
QLabel *m_theme_id_value = nullptr;
QLabel *m_size_value = nullptr;
QLabel *m_updated_value = nullptr;
QLabel *m_background_value = nullptr;
QLabel *m_cycle_value = nullptr;
QSlider *m_readability_slider = nullptr;
QLabel *m_readability_value = nullptr;
QCheckBox *m_normalize_fonts_checkbox = nullptr;
QPushButton *m_refresh_button = nullptr;
QPushButton *m_apply_button = nullptr;
QPushButton *m_delete_button = nullptr;
QPushButton *m_open_folder_button = nullptr;
QTextBrowser *m_help_text = nullptr;
QButtonGroup *m_primary_background_group = nullptr;
QString m_loaded_theme_id;
std::vector<BackgroundRow> m_background_rows;
QString m_selected_background_id;
QString m_preview_background_id;
QString m_loaded_preview_background_id;
QStringList m_selected_cycle_background_ids;
QPixmap m_preview_pixmap;
};
@@ -0,0 +1,285 @@
/*!PALETTE
* dark: dark
* window: #2d2d30
* window-text: #f3f3f3
* base: #1e1e1e
* alternate-base: #262629
* tooltip-base: #2d2d30
* tooltip-text: #f3f3f3
* text: #f3f3f3
* button: #2d2d30
* button-text: #f3f3f3
* bright-text: #ffffff
* link: #429cff
* highlight: #21ffffff
* inactive-highlight: #21ffffff
* disabled-highlight: #14ffffff
* highlight-text: #ffffff
* inactive-highlight-text: #ffffff
* disabled-highlight-text: #c8c8c8
* placeholder-text: #a0a0a0
* disabled-button-text: #737373
* disabled-window-text: #737373
* disabled-text: #737373
*/
QWidget { color: #e0e0e0; background-color: #353535; }
QWidget:disabled { color: #777777; }
QMainWindow { background-color: #353535; }
QMainWindow#main_window { background-color: #353535; }
QWidget#mw_frame { background-color: #353535; }
QMainWindow#mw_dock_host { background: transparent; }
QDialog { background-color: #353535; }
QMenuBar { background-color: #353535; color: #e0e0e0; }
QMenuBar::item { background: transparent; padding: 4px 8px; }
QMenuBar::item:selected { background: #4a4a4a; }
QMenu { background-color: #353535; color: #e0e0e0; border: 1px solid #555555; }
QMenu::item:selected { background-color: rgba(255, 255, 255, 0.12); color: #ffffff; }
QMenu::item:disabled { color: #808080; }
QMenu::separator { height: 1px; background: #555555; margin: 4px 8px; }
QToolBar#mw_toolbar { background-color: #353535; border: none; border-top: 1px solid #555555; }
QToolBar#mw_toolbar::separator { background-color: rgba(100, 100, 100, 235); width: 0.125em; margin-top: 0.250em; margin-bottom: 0.250em; }
QToolButton { color: #e0e0e0; background: transparent; border: 1px solid transparent; padding: 3px; }
QToolButton:hover { background-color: #4a4a4a; border: 1px solid #666666; }
QToolButton:pressed { background-color: rgba(255, 255, 255, 0.12); }
QToolButton:disabled { color: #808080; }
QToolButton#welcome_firmware_button { background-color: #353535; color: #e0e0e0; border: 1px solid #555555; border-radius: 2px; padding: 4px 32px 4px 12px; text-align: left; }
QToolButton#welcome_firmware_button:hover { background-color: #4a4a4a; border-color: #666666; }
QToolButton#welcome_firmware_button:pressed { background-color: rgba(255, 255, 255, 0.12); }
QToolButton#welcome_firmware_button::menu-button { subcontrol-origin: padding; subcontrol-position: top right; width: 24px; border-left: 1px solid #555555; border-top-right-radius: 2px; border-bottom-right-radius: 2px; background: #303030; }
QToolButton#welcome_firmware_button::menu-button:hover { background: #444444; border-left-color: #666666; }
QToolButton#welcome_firmware_button::menu-button:pressed { background: rgba(255, 255, 255, 0.12); }
QLabel[themeRole="toolbarIconTint"] { color: #f3f3f3; }
QLabel[themeRole="mutedText"] { color: #a8a8a8; }
QPushButton { background-color: #353535; color: #e0e0e0; border: 1px solid #555555; border-radius: 2px; padding: 4px 16px; min-height: 1.2em; }
QPushButton:hover { background-color: #4a4a4a; border-color: #666666; }
QPushButton:pressed { background-color: rgba(255, 255, 255, 0.12); }
QPushButton:disabled { color: #808080; background-color: #303030; border-color: #444444; }
QPushButton:default { border-color: rgba(255, 255, 255, 0.24); }
QDialog#vita_themes_dialog { color: #e0e0e0; }
QDialog#vita_themes_dialog QLabel,
QDialog#vita_themes_dialog QCheckBox,
QDialog#vita_themes_dialog QRadioButton { color: #e0e0e0; background: transparent; }
QDialog#vita_themes_dialog QLabel[themeRole="mutedText"] { color: #a8a8a8; }
QDialog#vita_themes_dialog QWidget[themeRole="backgroundCell"] { background: transparent; }
QDialog#vita_themes_dialog QCheckBox[themeRole="backgroundCellControl"],
QDialog#vita_themes_dialog QRadioButton[themeRole="backgroundCellControl"] { spacing: 0px; padding: 0px; margin: 0px; }
QDialog#vita_themes_dialog QListWidget#vitaThemesList {
background-color: #2a2a2a;
color: #e0e0e0;
border: none;
border-radius: 10px;
padding: 6px;
outline: none;
}
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item {
background: transparent;
border: none;
border-radius: 10px;
padding: 8px 12px;
margin: 0px;
}
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item:selected,
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item:selected:active,
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item:selected:!active { background-color: rgba(255, 255, 255, 0.12); color: #ffffff; }
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item:hover:!selected { background-color: #3a3a3a; }
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList {
background-color: #2a2a2a;
color: #e0e0e0;
border: 1px solid #555555;
border-radius: 8px;
padding: 2px;
outline: none;
}
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item {
padding: 5px 8px;
border-radius: 0px;
margin: 0px;
}
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item:selected,
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item:selected:active,
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item:selected:!active { background-color: rgba(255, 255, 255, 0.12); color: #ffffff; }
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item:hover:!selected { background-color: #3a3a3a; }
QDialog#vita_themes_dialog QTextBrowser#vitaThemesHelpText {
background-color: #2a2a2a;
color: #e0e0e0;
border: none;
border-radius: 10px;
padding: 3px 4px;
}
QDialog#vita_themes_dialog QScrollArea { background: transparent; border: none; }
QDialog#vita_themes_dialog QCheckBox::indicator {
width: 12px;
height: 12px;
padding: 0px;
margin: 0px;
border-radius: 3px;
border: 1px solid #666666;
background-color: #2a2a2a;
}
QDialog#vita_themes_dialog QCheckBox::indicator:hover { border-color: #888888; background-color: #323232; }
QDialog#vita_themes_dialog QCheckBox::indicator:checked,
QDialog#vita_themes_dialog QCheckBox::indicator:indeterminate {
background-color: rgba(255, 255, 255, 0.78);
border-color: rgba(255, 255, 255, 0.82);
image: url(":/icons/check_dark.svg");
}
QDialog#vita_themes_dialog QCheckBox::indicator:disabled { border-color: #4a4a4a; background-color: #252525; }
QDialog#vita_themes_dialog QCheckBox::indicator:checked:disabled,
QDialog#vita_themes_dialog QCheckBox::indicator:indeterminate:disabled {
background-color: rgba(255, 255, 255, 0.38);
border-color: rgba(255, 255, 255, 0.42);
}
QDialog#vita_themes_dialog QRadioButton::indicator {
width: 12px;
height: 12px;
padding: 0px;
margin: 0px;
border-radius: 6px;
border: 1px solid #666666;
background-color: #2a2a2a;
}
QDialog#vita_themes_dialog QRadioButton::indicator:hover { border-color: #888888; background-color: #323232; }
QDialog#vita_themes_dialog QRadioButton::indicator:checked {
background-color: rgba(255, 255, 255, 0.78);
border-color: rgba(255, 255, 255, 0.82);
image: url(":/icons/radio_dot_dark.svg");
}
QDialog#vita_themes_dialog QRadioButton::indicator:disabled { border-color: #4a4a4a; background-color: #252525; }
QDialog#vita_themes_dialog QRadioButton::indicator:checked:disabled {
background-color: rgba(255, 255, 255, 0.38);
border-color: rgba(255, 255, 255, 0.42);
}
QDialog#controls_dialog QToolButton[modeButton="true"] { background: transparent; border: none; border-radius: 10px; padding: 6px 12px; }
QDialog#controls_dialog QToolButton[modeButton="true"]:hover { background-color: rgba(255, 255, 255, 0.08); }
QDialog#controls_dialog QToolButton[modeButton="true"]:checked { background-color: rgba(255, 255, 255, 0.14); }
QDialog#controls_dialog QToolButton[modeButton="true"]:pressed { background-color: rgba(255, 255, 255, 0.20); }
QDialog#controls_dialog QPushButton { background-color: #303030; color: #e0e0e0; border: none; border-radius: 6px; padding: 6px 12px; outline: none; }
QDialog#controls_dialog QPushButton:hover { background-color: #3a3a3a; border: none; }
QDialog#controls_dialog QPushButton:pressed { background-color: #464646; border: none; }
QDialog#controls_dialog QPushButton:disabled { color: #808080; background-color: #2c2c2c; border: none; }
QDialog#controls_dialog QPushButton[bindButton="true"] { background-color: #2f2f2f; border-radius: 6px; padding: 6px 12px; }
QDialog#controls_dialog QPushButton[bindButton="true"]:hover { background-color: #393939; }
QDialog#controls_dialog QPushButton[bindButton="true"]:pressed { background-color: #454545; }
QDialog#controls_dialog QPushButton[bindButton="true"][captureActive="true"] { background-color: rgba(255, 255, 255, 0.18); color: #ffffff; }
QDialog#controls_dialog QPushButton[bindButton="true"][captureActive="true"]:hover { background-color: rgba(255, 255, 255, 0.24); }
QDialog#controls_dialog QPushButton[bindButton="true"][captureActive="true"]:pressed { background-color: rgba(255, 255, 255, 0.30); }
QStatusBar#emulator_status_bar { background-color: #353535; color: #e0e0e0; }
QStatusBar#emulator_status_bar::item { border: none; }
QStatusBar#emulator_status_bar QPushButton#status_button { border: 1px solid transparent; padding: 2px 8px; font-weight: bold; font-size: 11px; background: transparent; border-radius: 6px; color: #e0e0e0; }
QStatusBar#emulator_status_bar QPushButton#status_button:hover { border: 1px solid #666666; background: rgba(255, 255, 255, 0.04); }
QStatusBar#emulator_status_bar QPushButton#status_button:pressed { border: 1px solid #888888; background: rgba(255, 255, 255, 0.07); }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="renderer_vulkan"] { color: #ff4444; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="renderer_opengl"] { color: #4488ff; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="high"] { color: #ff8800; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="standard"] { color: #aaaaaa; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="neutral"] { color: #cccccc; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="audio"] { color: #88dd44; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="muted"] { color: #777777; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="update"] { color: #6de089; }
QLineEdit { background-color: #2a2a2a; color: #e0e0e0; border: 1px solid #555555; border-radius: 2px; padding: 2px 4px; selection-background-color: rgba(255, 255, 255, 0.18); selection-color: #ffffff; }
QLineEdit:focus { border-color: rgba(255, 255, 255, 0.24); }
QLineEdit:disabled { color: #777777; background-color: #303030; border-color: #444444; }
QComboBox { background-color: #353535; color: #e0e0e0; border: 1px solid #555555; border-radius: 2px; padding: 2px 28px 2px 6px; }
QComboBox:hover { border-color: #666666; }
QComboBox:disabled { color: #777777; background-color: #303030; border-color: #444444; }
QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 24px; border: none; background: transparent; }
QComboBox::down-arrow { image: url("chevron_down_light.svg"); width: 10px; height: 6px; }
QComboBox::down-arrow:on { top: 1px; }
QComboBox QAbstractItemView { background-color: #2a2a2a; color: #e0e0e0; border: 1px solid #555555; selection-background-color: rgba(255, 255, 255, 0.12); selection-color: #ffffff; }
QSpinBox, QDoubleSpinBox { background-color: #2a2a2a; color: #e0e0e0; border: 1px solid #555555; border-radius: 2px; padding: 2px 24px 2px 6px; selection-background-color: rgba(255, 255, 255, 0.18); selection-color: #ffffff; }
QSpinBox:disabled, QDoubleSpinBox:disabled { color: #777777; background-color: #303030; border-color: #444444; }
QSpinBox::up-button, QDoubleSpinBox::up-button { subcontrol-origin: border; subcontrol-position: top right; width: 18px; border: none; background: transparent; }
QSpinBox::down-button, QDoubleSpinBox::down-button { subcontrol-origin: border; subcontrol-position: bottom right; width: 18px; border: none; background: transparent; }
QSpinBox::up-arrow, QDoubleSpinBox::up-arrow { image: url("chevron_up_light.svg"); width: 10px; height: 6px; }
QSpinBox::down-arrow, QDoubleSpinBox::down-arrow { image: url("chevron_down_light.svg"); width: 10px; height: 6px; }
QCheckBox { color: #e0e0e0; spacing: 6px; }
QCheckBox:disabled { color: #777777; }
QRadioButton { color: #e0e0e0; spacing: 6px; }
QRadioButton:disabled { color: #777777; }
QGroupBox { color: #e0e0e0; border: 1px solid #555555; border-radius: 4px; margin-top: 0.5em; padding-top: 0.6em; }
QGroupBox:disabled { color: #7f7f7f; border-color: #4a4a4a; }
QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; padding: 0 4px; color: #e0e0e0; }
QLabel { color: #e0e0e0; background: transparent; }
QLabel:disabled { color: #777777; }
QTableWidget, QTableView { alternate-background-color: #3a3a3a; background-color: #2a2a2a; border: none; color: #e0e0e0; gridline-color: #444444; }
QTableWidget:disabled, QTableView:disabled { color: #777777; background-color: #303030; }
QTableCornerButton::section { background-color: #353535; border: 1px solid #555555; }
QTableView::item { border-left: 0.063em solid #2a2a2a; border-right: 0.063em solid #2a2a2a; padding-left: 0.313em; }
QTableView::item:selected { background-color: rgba(255, 255, 255, 0.12); color: #ffffff; }
QTableWidget#apps_list_table::item:selected, QTableView#apps_list_table::item:selected { background-color: rgba(255, 255, 255, 0.12); color: #f0f0f0; border-left: 0.063em solid rgba(255, 255, 255, 0); border-right: 0.063em solid rgba(255, 255, 255, 0); }
QTableView#apps_list_table QHeaderView::section, QTableWidget#apps_list_table QHeaderView::section { padding-top: 0; padding-bottom: 0; }
QTableView#trophy_app_table::item:selected, QTableView#trophy_table::item:selected { background-color: rgba(255, 255, 255, 0.13); color: #ffffff; border-left: 0.063em solid rgba(255, 255, 255, 0); border-right: 0.063em solid rgba(255, 255, 255, 0); }
QHeaderView::section { background-color: #353535; color: #ffffff; padding-left: .5em; padding-right: .5em; padding-top: .4em; padding-bottom: -.1em; border: 0.063em solid #2a2a2a; }
QHeaderView::section:hover { background: #4a4a4a; }
QDockWidget { background: transparent; color: #ffffff; }
QDockWidget#apps_list[floating="true"],
QDockWidget#logger[floating="true"] { background-color: #353535; }
QDockWidget::title { background: #353535; border: none; padding-top: 0.2em; padding-left: 0.2em; }
QDockWidget::close-button, QDockWidget::float-button { background-color: #353535; border: none; }
QDockWidget::close-button:hover, QDockWidget::float-button:hover { background-color: #4a4a4a; }
QWidget#apps_list_frame { background-color: #353535; }
QWidget#log_surface { background-color: #353535; }
QWidget#apps_list_viewport { background-color: #2a2a2a; }
QWidget#log_frame_viewport { background-color: #2a2a2a; }
QPlainTextEdit#log_frame { background-color: #2a2a2a; color: #ffffff; }
QWidget#log_search_container { background-color: #2a2a2a; border: 1px solid #555555; border-radius: 4px; }
QWidget#toolbar_icon_size_container { background-color: #353535; }
QSlider#toolbar_icon_size_slider { background: transparent; }
QSlider#toolbar_icon_size_slider::groove:horizontal { background: transparent; height: 4px; border-radius: 2px; }
QSlider#toolbar_icon_size_slider::sub-page:horizontal { background: rgba(255, 255, 255, 0.82); height: 4px; border-radius: 2px; }
QSlider#toolbar_icon_size_slider::add-page:horizontal { background: #555555; height: 4px; border-radius: 2px; }
QSlider#toolbar_icon_size_slider::handle:horizontal { background: #f3f3f3; width: 14px; margin: -5px 0; border-radius: 7px; }
QSlider#toolbar_icon_size_slider::handle:horizontal:hover { background: #ffffff; }
QLabel[themeRole="logFatal"] { color: #ff66ff; }
QLabel[themeRole="logError"] { color: #ff8080; }
QLabel[themeRole="logWarning"] { color: #ffd27f; }
QLabel[themeRole="logNotice"] { color: #ffffff; }
QLabel[themeRole="logDebug"] { color: #8bb9ff; }
QLabel[themeRole="logTrace"] { color: #ffffff; }
QLabel[themeRole="sectionHeader"] { font-weight: 600; margin-top: 8px; }
QLabel[severity="success"] { border-radius: 10px; padding: 4px 10px; background-color: #255a2e; color: #b9f5bf; }
QLabel[severity="error"] { border-radius: 10px; padding: 4px 10px; background-color: #6d2a25; color: #ffb1aa; }
QTabWidget::pane { border: 1px solid #555555; background-color: #353535; }
QTabBar::tab { background-color: #2d2d2d; color: #e0e0e0; padding: 6px 14px; border: 1px solid #555555; border-bottom: none; border-top-left-radius: 3px; border-top-right-radius: 3px; }
QTabBar::tab:selected { background-color: #353535; }
QTabBar::tab:!selected { margin-top: 2px; }
QTabBar::tab:hover:!selected { background-color: #3a3a3a; }
QListWidget { background-color: #2a2a2a; color: #e0e0e0; border: 1px solid #555555; }
QListWidget:disabled { color: #777777; background-color: #303030; border-color: #444444; }
QListWidget::item:selected { background-color: rgba(255, 255, 255, 0.12); color: #ffffff; }
QListWidget::item:hover:!selected { background-color: #3a3a3a; }
QDialog[class="settings-window"] QListWidget#settingsCategory { background-color: #2a2a2a; border: none; border-radius: 10px; padding: 6px; outline: none; }
QDialog[class="settings-window"] QListWidget#settingsCategory::item { background: transparent; border: none; border-radius: 10px; padding: 8px 12px; margin: 0px; }
QDialog[class="settings-window"] QListWidget#settingsCategory::item:selected,
QDialog[class="settings-window"] QListWidget#settingsCategory::item:selected:active,
QDialog[class="settings-window"] QListWidget#settingsCategory::item:selected:!active { background-color: rgba(255, 255, 255, 0.12); color: #ffffff; }
QDialog[class="settings-window"] QListWidget#settingsCategory::item:hover:!selected { background-color: #3a3a3a; }
QDialog[class="settings-window"] QTextBrowser#helpText { background-color: #2a2a2a; color: #e0e0e0; border: none; border-radius: 10px; padding: 3px 4px; }
QScrollBar:vertical { background: #2a2a2a; width: 14px; margin: 0; }
QScrollBar::handle:vertical { background: #5a5a5a; min-height: 20px; border-radius: 3px; margin: 2px; }
QScrollBar::handle:vertical:hover { background: #6a6a6a; }
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; }
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: none; }
QScrollBar:horizontal { background: #2a2a2a; height: 14px; margin: 0; }
QScrollBar::handle:horizontal { background: #5a5a5a; min-width: 20px; border-radius: 3px; margin: 2px; }
QScrollBar::handle:horizontal:hover { background: #6a6a6a; }
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0px; }
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: none; }
QAbstractScrollArea::corner { background: #2a2a2a; }
QSlider::groove:horizontal { background: transparent; height: 4px; border-radius: 2px; }
QSlider::sub-page:horizontal { background: rgba(255, 255, 255, 0.82); height: 4px; border-radius: 2px; }
QSlider::add-page:horizontal { background: #555555; height: 4px; border-radius: 2px; }
QSlider::handle:horizontal { background: #c0c0c0; width: 14px; margin: -5px 0; border-radius: 7px; }
QSlider::handle:horizontal:hover { background: #ffffff; }
QSlider::groove:vertical { background: transparent; width: 4px; border-radius: 2px; }
QSlider::sub-page:vertical { background: rgba(255, 255, 255, 0.82); width: 4px; border-radius: 2px; }
QSlider::add-page:vertical { background: #555555; width: 4px; border-radius: 2px; }
QSlider::handle:vertical { background: #c0c0c0; height: 14px; margin: 0 -5px; border-radius: 7px; }
QSlider::handle:vertical:hover { background: #ffffff; }
QProgressBar { background-color: #2a2a2a; color: #e0e0e0; border: 1px solid #555555; text-align: center; border-radius: 2px; }
QProgressBar::chunk { background-color: #6a6a6a; border-radius: 2px; }
QSplitter::handle { background-color: #555555; }
QToolTip { background-color: #353535; color: #e0e0e0; border: 1px solid #555555; padding: 2px; }
QPlainTextEdit, QTextEdit, QTextBrowser { background-color: #2a2a2a; color: #e0e0e0; border: 1px solid #555555; }
QPlainTextEdit:disabled, QTextEdit:disabled, QTextBrowser:disabled { color: #777777; background-color: #303030; border-color: #444444; }
QDialogButtonBox QPushButton { min-width: 80px; }
@@ -0,0 +1,281 @@
/*!PALETTE
* dark: light
* window: #f5f5f5
* window-text: #202020
* base: #ffffff
* alternate-base: #f2f2f2
* tooltip-base: #f7f7f7
* tooltip-text: #202020
* text: #202020
* button: #ffffff
* button-text: #202020
* bright-text: #ffffff
* link: #148aff
* highlight: #148aff
* inactive-highlight: #148aff
* disabled-highlight: #40148aff
* highlight-text: #ffffff
* inactive-highlight-text: #ffffff
* disabled-highlight-text: #d0d0d0
* placeholder-text: #8a8a8a
* disabled-button-text: #8a8a8a
* disabled-window-text: #8a8a8a
* disabled-text: #8a8a8a
*/
QWidget { color: #202020; background-color: #f5f5f5; }
QWidget:disabled { color: #8a8a8a; }
QMainWindow { background-color: #f5f5f5; }
QMainWindow#main_window { background-color: #f5f5f5; }
QWidget#mw_frame { background-color: #f5f5f5; }
QMainWindow#mw_dock_host { background: transparent; }
QDialog { background-color: #f5f5f5; }
QMenuBar { background-color: #f0f0f0; color: #202020; }
QMenuBar::item { background: transparent; padding: 4px 8px; }
QMenuBar::item:selected { background: #e1e1e1; }
QMenu { background-color: #f7f7f7; color: #202020; border: 1px solid #c6c6c6; }
QMenu::item:selected { background-color: #d6e8ff; color: #202020; }
QMenu::item:disabled { color: #808080; }
QMenu::separator { height: 1px; background: #d0d0d0; margin: 4px 8px; }
QToolBar#mw_toolbar { background-color: #f0f0f0; border: none; border-top: 1px solid #cfcfcf; }
QToolBar#mw_toolbar::separator { background-color: rgba(207, 207, 207, 235); width: 0.125em; margin-top: 0.250em; margin-bottom: 0.250em; }
QToolButton { color: #202020; background: transparent; border: 1px solid transparent; padding: 3px; }
QToolButton:hover { background-color: #e8e8e8; border: 1px solid #bdbdbd; }
QToolButton:pressed { background-color: #d8d8d8; color: #202020; }
QToolButton:disabled { color: #808080; }
QToolButton#welcome_firmware_button { background-color: #ffffff; color: #202020; border: 1px solid #b8b8b8; border-radius: 2px; padding: 4px 32px 4px 12px; text-align: left; }
QToolButton#welcome_firmware_button:hover { background-color: #f3f3f3; border-color: #9f9f9f; }
QToolButton#welcome_firmware_button:pressed { background-color: #e3e3e3; }
QToolButton#welcome_firmware_button::menu-button { subcontrol-origin: padding; subcontrol-position: top right; width: 24px; border-left: 1px solid #b8b8b8; border-top-right-radius: 2px; border-bottom-right-radius: 2px; background: #f3f3f3; }
QToolButton#welcome_firmware_button::menu-button:hover { background: #e8e8e8; border-left-color: #9f9f9f; }
QToolButton#welcome_firmware_button::menu-button:pressed { background: #dcdcdc; }
QLabel[themeRole="toolbarIconTint"] { color: #5b5b5b; }
QLabel[themeRole="mutedText"] { color: #5b5b5b; }
QPushButton { background-color: #ffffff; color: #202020; border: 1px solid #b8b8b8; border-radius: 2px; padding: 4px 16px; min-height: 1.2em; }
QPushButton:hover { background-color: #f3f3f3; border-color: #9f9f9f; }
QPushButton:pressed { background-color: #e3e3e3; }
QPushButton:disabled { color: #8a8a8a; background-color: #efefef; border-color: #d4d4d4; }
QPushButton:default { border-color: #148aff; }
QDialog#vita_themes_dialog { color: #202020; }
QDialog#vita_themes_dialog QLabel,
QDialog#vita_themes_dialog QCheckBox,
QDialog#vita_themes_dialog QRadioButton { color: #202020; background: transparent; }
QDialog#vita_themes_dialog QLabel[themeRole="mutedText"] { color: #5b5b5b; }
QDialog#vita_themes_dialog QWidget[themeRole="backgroundCell"] { background: transparent; }
QDialog#vita_themes_dialog QCheckBox[themeRole="backgroundCellControl"],
QDialog#vita_themes_dialog QRadioButton[themeRole="backgroundCellControl"] { spacing: 0px; padding: 0px; margin: 0px; }
QDialog#vita_themes_dialog QListWidget#vitaThemesList {
background-color: #ffffff;
color: #202020;
border: none;
border-radius: 10px;
padding: 6px;
outline: none;
}
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item {
background: transparent;
border: none;
border-radius: 10px;
padding: 8px 12px;
margin: 0px;
}
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item:selected,
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item:selected:active,
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item:selected:!active { background-color: #148aff; color: #ffffff; }
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item:hover:!selected { background-color: #f2f2f2; }
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList {
background-color: #ffffff;
color: #202020;
border: 1px solid #c8c8c8;
border-radius: 8px;
padding: 2px;
outline: none;
}
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item {
padding: 5px 8px;
border-radius: 0px;
margin: 0px;
}
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item:selected,
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item:selected:active,
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item:selected:!active { background-color: #148aff; color: #ffffff; }
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item:hover:!selected { background-color: #f2f2f2; }
QDialog#vita_themes_dialog QTextBrowser#vitaThemesHelpText {
background-color: #ffffff;
color: #202020;
border: none;
border-radius: 10px;
padding: 3px 4px;
}
QDialog#vita_themes_dialog QScrollArea { background: transparent; border: none; }
QDialog#vita_themes_dialog QCheckBox::indicator {
width: 12px;
height: 12px;
padding: 0px;
margin: 0px;
border-radius: 3px;
border: 1px solid #b8b8b8;
background-color: #ffffff;
}
QDialog#vita_themes_dialog QCheckBox::indicator:hover { border-color: #148aff; background-color: #f2f8ff; }
QDialog#vita_themes_dialog QCheckBox::indicator:checked,
QDialog#vita_themes_dialog QCheckBox::indicator:indeterminate {
background-color: #148aff;
border-color: #148aff;
image: url(":/icons/check_light.svg");
}
QDialog#vita_themes_dialog QCheckBox::indicator:disabled { border-color: #d0d0d0; background-color: #f0f0f0; }
QDialog#vita_themes_dialog QCheckBox::indicator:checked:disabled,
QDialog#vita_themes_dialog QCheckBox::indicator:indeterminate:disabled {
background-color: rgba(20, 138, 255, 0.25);
border-color: rgba(20, 138, 255, 0.35);
}
QDialog#vita_themes_dialog QRadioButton::indicator {
width: 12px;
height: 12px;
padding: 0px;
margin: 0px;
border-radius: 6px;
border: 1px solid #b8b8b8;
background-color: #ffffff;
}
QDialog#vita_themes_dialog QRadioButton::indicator:hover { border-color: #148aff; background-color: #f2f8ff; }
QDialog#vita_themes_dialog QRadioButton::indicator:checked {
background-color: #148aff;
border-color: #148aff;
image: url(":/icons/radio_dot_light.svg");
}
QDialog#vita_themes_dialog QRadioButton::indicator:disabled { border-color: #d0d0d0; background-color: #f0f0f0; }
QDialog#vita_themes_dialog QRadioButton::indicator:checked:disabled {
background-color: rgba(20, 138, 255, 0.25);
border-color: rgba(20, 138, 255, 0.35);
}
QDialog#controls_dialog QToolButton[modeButton="true"] { background: transparent; border: none; border-radius: 10px; padding: 6px 12px; }
QDialog#controls_dialog QToolButton[modeButton="true"]:hover { background-color: rgba(20, 138, 255, 0.08); }
QDialog#controls_dialog QToolButton[modeButton="true"]:checked { background-color: rgba(20, 138, 255, 0.14); }
QDialog#controls_dialog QToolButton[modeButton="true"]:pressed { background-color: rgba(20, 138, 255, 0.18); }
QDialog#controls_dialog QPushButton { background-color: #ececec; color: #202020; border: none; border-radius: 6px; padding: 6px 12px; outline: none; }
QDialog#controls_dialog QPushButton:hover { background-color: #e3e3e3; border: none; }
QDialog#controls_dialog QPushButton:pressed { background-color: #d8d8d8; border: none; }
QDialog#controls_dialog QPushButton:disabled { color: #8a8a8a; background-color: #e4e4e4; border: none; }
QDialog#controls_dialog QPushButton[bindButton="true"] { background-color: #e6e6e6; border-radius: 6px; padding: 6px 12px; }
QDialog#controls_dialog QPushButton[bindButton="true"]:hover { background-color: #dddddd; }
QDialog#controls_dialog QPushButton[bindButton="true"]:pressed { background-color: #d2d2d2; }
QDialog#controls_dialog QPushButton[bindButton="true"][captureActive="true"] { background-color: #148aff; color: #ffffff; }
QDialog#controls_dialog QPushButton[bindButton="true"][captureActive="true"]:hover { background-color: #2a97ff; }
QDialog#controls_dialog QPushButton[bindButton="true"][captureActive="true"]:pressed { background-color: #0f74d6; }
QStatusBar#emulator_status_bar { background-color: #f0f0f0; color: #202020; }
QStatusBar#emulator_status_bar::item { border: none; }
QStatusBar#emulator_status_bar QPushButton#status_button { border: 1px solid transparent; padding: 2px 8px; font-weight: bold; font-size: 11px; background: transparent; border-radius: 6px; color: #202020; }
QStatusBar#emulator_status_bar QPushButton#status_button:hover { border: 1px solid #a0a0a0; background: rgba(0, 0, 0, 0.03); }
QStatusBar#emulator_status_bar QPushButton#status_button:pressed { border: 1px solid #8a8a8a; background: rgba(0, 0, 0, 0.06); }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="renderer_vulkan"] { color: #ff4444; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="renderer_opengl"] { color: #4488ff; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="high"] { color: #ff8800; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="standard"] { color: #aaaaaa; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="neutral"] { color: #5f5f5f; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="audio"] { color: #4b9b2f; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="muted"] { color: #777777; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="update"] { color: #1f8f4e; }
QLineEdit { background-color: #ffffff; color: #202020; border: 1px solid #b8b8b8; border-radius: 2px; padding: 2px 4px; selection-background-color: #148aff; selection-color: #ffffff; }
QLineEdit:focus { border-color: #148aff; }
QLineEdit:disabled { color: #8a8a8a; background-color: #f0f0f0; border-color: #d0d0d0; }
QComboBox { background-color: #ffffff; color: #202020; border: 1px solid #b8b8b8; border-radius: 2px; padding: 2px 28px 2px 6px; }
QComboBox:hover { border-color: #9f9f9f; }
QComboBox:disabled { color: #8a8a8a; background-color: #f0f0f0; border-color: #d0d0d0; }
QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 24px; border: none; background: transparent; }
QComboBox::down-arrow { image: url("chevron_down_dark.svg"); width: 10px; height: 6px; }
QComboBox::down-arrow:on { top: 1px; }
QComboBox QAbstractItemView { background-color: #ffffff; color: #202020; border: 1px solid #b8b8b8; selection-background-color: #148aff; selection-color: #ffffff; }
QSpinBox, QDoubleSpinBox { background-color: #ffffff; color: #202020; border: 1px solid #b8b8b8; border-radius: 2px; padding: 2px 24px 2px 6px; selection-background-color: #148aff; selection-color: #ffffff; }
QSpinBox:disabled, QDoubleSpinBox:disabled { color: #8a8a8a; background-color: #f0f0f0; border-color: #d0d0d0; }
QSpinBox::up-button, QDoubleSpinBox::up-button { subcontrol-origin: border; subcontrol-position: top right; width: 18px; border: none; background: transparent; }
QSpinBox::down-button, QDoubleSpinBox::down-button { subcontrol-origin: border; subcontrol-position: bottom right; width: 18px; border: none; background: transparent; }
QSpinBox::up-arrow, QDoubleSpinBox::up-arrow { image: url("chevron_up_dark.svg"); width: 10px; height: 6px; }
QSpinBox::down-arrow, QDoubleSpinBox::down-arrow { image: url("chevron_down_dark.svg"); width: 10px; height: 6px; }
QCheckBox { color: #202020; spacing: 6px; }
QCheckBox:disabled { color: #8a8a8a; }
QRadioButton { color: #202020; spacing: 6px; }
QRadioButton:disabled { color: #8a8a8a; }
QGroupBox { color: #202020; border: 1px solid #c8c8c8; border-radius: 4px; margin-top: 0.5em; padding-top: 0.6em; }
QGroupBox:disabled { color: #9a9a9a; border-color: #d8d8d8; }
QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; padding: 0 4px; color: #202020; }
QLabel { color: #202020; background: transparent; }
QLabel:disabled { color: #8a8a8a; }
QTableWidget, QTableView { alternate-background-color: #f2f2f2; background-color: #ffffff; border: none; color: #202020; gridline-color: #e0e0e0; }
QTableWidget:disabled, QTableView:disabled { color: #8a8a8a; background-color: #f0f0f0; }
QTableCornerButton::section { background-color: #ececec; border: 1px solid #d4d4d4; }
QTableView::item { border-left: 0.063em solid #ffffff; border-right: 0.063em solid #ffffff; padding-left: 0.313em; }
QTableView::item:selected { background-color: #148aff; color: #ffffff; }
QTableWidget#apps_list_table::item:selected, QTableView#apps_list_table::item:selected { background-color: rgba(84, 140, 188, 48); color: #202020; border-left: 0.063em solid rgba(84, 140, 188, 0); border-right: 0.063em solid rgba(84, 140, 188, 0); }
QTableView#apps_list_table QHeaderView::section, QTableWidget#apps_list_table QHeaderView::section { padding-top: 0; padding-bottom: 0; }
QTableView#trophy_app_table::item:selected, QTableView#trophy_table::item:selected { background-color: rgba(84, 140, 188, 48); color: #202020; border-left: 0.063em solid rgba(84, 140, 188, 0); border-right: 0.063em solid rgba(84, 140, 188, 0); }
QHeaderView::section { background-color: #ececec; color: #202020; padding-left: .5em; padding-right: .5em; padding-top: .4em; padding-bottom: -.1em; border: 0.063em solid #ffffff; }
QHeaderView::section:hover { background: #e0e0e0; }
QDockWidget { background: transparent; color: #202020; }
QDockWidget#apps_list[floating="true"],
QDockWidget#logger[floating="true"] { background-color: #f5f5f5; }
QDockWidget::title { background: #ececec; border: none; padding-top: 0.2em; padding-left: 0.2em; }
QDockWidget::close-button, QDockWidget::float-button { background-color: #ececec; border: none; }
QDockWidget::close-button:hover, QDockWidget::float-button:hover { background-color: #e0e0e0; }
QWidget#apps_list_frame { background-color: #f5f5f5; }
QWidget#log_surface { background-color: #f5f5f5; }
QWidget#apps_list_viewport { background-color: #ffffff; }
QWidget#log_frame_viewport { background-color: #ffffff; }
QPlainTextEdit#log_frame { background-color: #ffffff; color: #202020; }
QWidget#log_search_container { background-color: #ffffff; border: 1px solid #c8c8c8; border-radius: 4px; }
QWidget#toolbar_icon_size_container { background-color: #f0f0f0; }
QSlider#toolbar_icon_size_slider { background: transparent; }
QSlider#toolbar_icon_size_slider::groove:horizontal { background: transparent; height: 4px; border-radius: 2px; }
QSlider#toolbar_icon_size_slider::sub-page:horizontal { background: #7f7f7f; height: 4px; border-radius: 2px; }
QSlider#toolbar_icon_size_slider::add-page:horizontal { background: #c8c8c8; height: 4px; border-radius: 2px; }
QSlider#toolbar_icon_size_slider::handle:horizontal { background: #5b5b5b; width: 14px; margin: -5px 0; border-radius: 7px; }
QSlider#toolbar_icon_size_slider::handle:horizontal:hover { background: #404040; }
QLabel[themeRole="logFatal"] { color: #ff00ff; }
QLabel[themeRole="logError"] { color: #C02F1D; }
QLabel[themeRole="logWarning"] { color: #BA8745; }
QLabel[themeRole="logNotice"] { color: #000000; }
QLabel[themeRole="logDebug"] { color: #2467c8; }
QLabel[themeRole="logTrace"] { color: #808080; }
QLabel[themeRole="sectionHeader"] { font-weight: 600; margin-top: 8px; }
QLabel[severity="success"] { border-radius: 10px; padding: 4px 10px; background-color: #d9f2dd; color: #1f7a1f; }
QLabel[severity="error"] { border-radius: 10px; padding: 4px 10px; background-color: #f7dcd9; color: #a32a1c; }
QTabWidget::pane { border: 1px solid #c8c8c8; background-color: #f5f5f5; }
QTabBar::tab { background-color: #e9e9e9; color: #202020; padding: 6px 14px; border: 1px solid #c8c8c8; border-bottom: none; border-top-left-radius: 3px; border-top-right-radius: 3px; }
QTabBar::tab:selected { background-color: #f5f5f5; }
QTabBar::tab:!selected { margin-top: 2px; }
QTabBar::tab:hover:!selected { background-color: #dfdfdf; }
QListWidget { background-color: #ffffff; color: #202020; border: 1px solid #c8c8c8; }
QListWidget:disabled { color: #8a8a8a; background-color: #f0f0f0; border-color: #d6d6d6; }
QListWidget::item:selected { background-color: #148aff; color: #ffffff; }
QListWidget::item:hover:!selected { background-color: #f2f2f2; }
QDialog[class="settings-window"] QListWidget#settingsCategory { background-color: #ffffff; border: none; border-radius: 10px; padding: 6px; outline: none; }
QDialog[class="settings-window"] QListWidget#settingsCategory::item { background: transparent; border: none; border-radius: 10px; padding: 8px 12px; margin: 0px; }
QDialog[class="settings-window"] QListWidget#settingsCategory::item:selected,
QDialog[class="settings-window"] QListWidget#settingsCategory::item:selected:active,
QDialog[class="settings-window"] QListWidget#settingsCategory::item:selected:!active { background-color: #148aff; color: #ffffff; }
QDialog[class="settings-window"] QListWidget#settingsCategory::item:hover:!selected { background-color: #f2f2f2; }
QDialog[class="settings-window"] QTextBrowser#helpText { background-color: #ffffff; color: #202020; border: none; border-radius: 10px; padding: 3px 4px; }
QScrollBar:vertical { background: #f0f0f0; width: 14px; margin: 0; }
QScrollBar::handle:vertical { background: #b0b0b0; min-height: 20px; border-radius: 3px; margin: 2px; }
QScrollBar::handle:vertical:hover { background: #989898; }
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; }
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: none; }
QScrollBar:horizontal { background: #f0f0f0; height: 14px; margin: 0; }
QScrollBar::handle:horizontal { background: #b0b0b0; min-width: 20px; border-radius: 3px; margin: 2px; }
QScrollBar::handle:horizontal:hover { background: #989898; }
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0px; }
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: none; }
QAbstractScrollArea::corner { background: #f0f0f0; }
QSlider::groove:horizontal { background: #c8c8c8; height: 4px; border-radius: 2px; }
QSlider::handle:horizontal { background: #7f7f7f; width: 14px; margin: -5px 0; border-radius: 7px; }
QSlider::handle:horizontal:hover { background: #666666; }
QSlider::groove:vertical { background: #c8c8c8; width: 4px; border-radius: 2px; }
QSlider::handle:vertical { background: #7f7f7f; height: 14px; margin: 0 -5px; border-radius: 7px; }
QSlider::handle:vertical:hover { background: #666666; }
QProgressBar { background-color: #ffffff; color: #202020; border: 1px solid #c8c8c8; text-align: center; border-radius: 2px; }
QProgressBar::chunk { background-color: #148aff; border-radius: 2px; }
QSplitter::handle { background-color: #d4d4d4; }
QToolTip { background-color: #f7f7f7; color: #202020; border: 1px solid #c8c8c8; padding: 2px; }
QPlainTextEdit, QTextEdit, QTextBrowser { background-color: #ffffff; color: #202020; border: 1px solid #c8c8c8; }
QPlainTextEdit:disabled, QTextEdit:disabled, QTextBrowser:disabled { color: #8a8a8a; background-color: #f0f0f0; border-color: #d0d0d0; }
QDialogButtonBox QPushButton { min-width: 80px; }
@@ -0,0 +1,664 @@
/*!VITA3K_THEME
background-images: {{generated_background_images}}
background-interval: {{generated_background_interval_seconds}}
background-animated: {{generated_background_animated}}
*/
/*!PALETTE
* dark: {{palette_dark}}
* window: {{window_hex}}
* window-text: {{text_hex}}
* base: {{base_hex}}
* alternate-base: {{alternate_base_hex}}
* tooltip-base: {{base_hex}}
* tooltip-text: {{text_hex}}
* text: {{text_hex}}
* button: {{button_hex}}
* button-text: {{button_text_hex}}
* bright-text: {{button_bright_text_hex}}
* link: {{accent_hex}}
* highlight: {{highlight_argb}}
* inactive-highlight: {{inactive_highlight_argb}}
* disabled-highlight: {{disabled_highlight_argb}}
* highlight-text: {{highlight_text_hex}}
* inactive-highlight-text: {{highlight_text_hex}}
* disabled-highlight-text: {{overlay_disabled_text_hex}}
* placeholder-text: {{muted_text_hex}}
* disabled-button-text: {{disabled_text_hex}}
* disabled-window-text: {{disabled_text_hex}}
* disabled-text: {{disabled_text_hex}}
*/
QWidget { color: {{text_hex}}; background: transparent; }
QWidget:disabled { color: {{disabled_text_hex}}; }
QMainWindow { background-color: {{window_hex}}; }
QMainWindow#main_window {
background-color: {{window_hex}};
}
QWidget#mw_frame,
QMainWindow#mw_dock_host {
background: transparent;
}
QDialog { background-color: {{dialog_bg}}; }
QMenuBar {
background-color: {{menu_bar_bg}};
color: {{text_hex}};
border-bottom: 1px solid {{menu_bar_border}};
}
QMenuBar::item { background: transparent; padding: 4px 8px; }
QMenuBar::item:selected { background: {{menu_bar_item_hover_bg}}; }
QMenu {
background-color: {{menu_bg}};
color: {{text_hex}};
border: 1px solid {{menu_border}};
}
QMenu::item:selected { background-color: {{menu_selected_bg}}; color: {{selected_text_hex}}; }
QMenu::item:disabled { color: {{disabled_text_hex}}; }
QMenu::separator { height: 1px; background: {{menu_separator_bg}}; margin: 4px 8px; }
QToolBar#mw_toolbar {
background-color: {{toolbar_bg}};
border: none;
border-top: 1px solid {{toolbar_border_top}};
border-bottom: 1px solid {{toolbar_border_bottom}};
}
QToolBar#mw_toolbar::separator {
background-color: {{toolbar_separator_bg}};
width: 0.125em;
margin-top: 0.250em;
margin-bottom: 0.250em;
}
QToolBar#mw_toolbar > QWidget { background: transparent; }
QToolButton {
color: {{text_hex}};
background: transparent;
border: 1px solid transparent;
padding: 3px;
}
QToolButton:hover { background-color: {{tool_button_hover_bg}}; border: 1px solid {{tool_button_hover_border}}; }
QToolButton:pressed { background-color: {{tool_button_pressed_bg}}; }
QToolButton:disabled { color: {{disabled_text_hex}}; }
QToolButton#welcome_firmware_button {
background-color: {{welcome_button_bg}};
color: {{text_hex}};
border: 1px solid {{welcome_button_border}};
border-radius: 2px;
padding: 4px 32px 4px 12px;
text-align: left;
}
QToolButton#welcome_firmware_button:hover {
background-color: {{welcome_button_hover_bg}};
border-color: {{welcome_button_hover_border}};
}
QToolButton#welcome_firmware_button:pressed { background-color: {{welcome_button_pressed_bg}}; }
QToolButton#welcome_firmware_button::menu-button {
subcontrol-origin: padding;
subcontrol-position: top right;
width: 24px;
border-left: 1px solid {{welcome_menu_button_border}};
background: {{welcome_menu_button_bg}};
}
QToolButton#welcome_firmware_button::menu-button:hover {
background: {{welcome_menu_button_hover_bg}};
border-left-color: {{welcome_menu_button_hover_border}};
}
QToolButton#welcome_firmware_button::menu-button:pressed { background: {{welcome_menu_button_pressed_bg}}; }
QLabel[themeRole="toolbarIconTint"] { color: {{toolbar_icon_tint_hex}}; }
QLabel[themeRole="mutedText"] { color: {{muted_text_hex}}; }
QPushButton {
background-color: {{push_button_bg}};
color: {{text_hex}};
border: 1px solid {{push_button_border}};
border-radius: 2px;
padding: 4px 16px;
min-height: 1.2em;
}
QPushButton:hover { background-color: {{push_button_hover_bg}}; border-color: {{push_button_hover_border}}; }
QPushButton:pressed { background-color: {{push_button_pressed_bg}}; color: {{push_button_pressed_text_hex}}; }
QPushButton:disabled {
color: {{disabled_text_hex}};
background-color: {{push_button_disabled_bg}};
border-color: {{push_button_disabled_border}};
}
QPushButton:default { border-color: {{push_button_default_border}}; }
QDialog#vita_themes_dialog { color: {{dialog_text_hex}}; }
QDialog#vita_themes_dialog QLabel,
QDialog#vita_themes_dialog QCheckBox,
QDialog#vita_themes_dialog QRadioButton { color: {{dialog_text_hex}}; background: transparent; }
QDialog#vita_themes_dialog QLabel[themeRole="mutedText"] { color: {{dialog_muted_text_hex}}; }
QDialog#vita_themes_dialog QWidget[themeRole="backgroundCell"] { background: transparent; }
QDialog#vita_themes_dialog QCheckBox[themeRole="backgroundCellControl"],
QDialog#vita_themes_dialog QRadioButton[themeRole="backgroundCellControl"] { spacing: 0px; padding: 0px; margin: 0px; }
QDialog#vita_themes_dialog QListWidget#vitaThemesList {
background-color: {{vita_list_bg}};
color: {{dialog_text_hex}};
border: none;
border-radius: 10px;
padding: 6px;
outline: none;
}
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item {
background: transparent;
border: none;
border-radius: 10px;
padding: 8px 12px;
margin: 0px;
}
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item:selected,
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item:selected:active,
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item:selected:!active { background-color: {{vita_list_selected_bg}}; color: {{dialog_selected_text_hex}}; }
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item:hover:!selected { background-color: {{vita_list_hover_bg}}; }
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList {
background-color: {{background_list_bg}};
color: {{dialog_text_hex}};
border: 1px solid {{background_list_border}};
border-radius: 8px;
padding: 2px;
outline: none;
}
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item {
padding: 5px 8px;
border-radius: 0px;
margin: 0px;
}
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item:selected,
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item:selected:active,
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item:selected:!active { background-color: {{background_list_selected_bg}}; color: {{dialog_selected_text_hex}}; }
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item:hover:!selected { background-color: {{background_list_hover_bg}}; }
QDialog#vita_themes_dialog QTextBrowser#vitaThemesHelpText {
background-color: {{vita_help_bg}};
color: {{dialog_text_hex}};
border: none;
border-radius: 10px;
padding: 3px 4px;
}
QDialog#vita_themes_dialog QScrollArea { background: transparent; border: none; }
QDialog#vita_themes_dialog QCheckBox::indicator {
width: 12px;
height: 12px;
padding: 0px;
margin: 0px;
border-radius: 3px;
border: 1px solid {{indicator_border}};
background-color: {{indicator_bg}};
}
QDialog#vita_themes_dialog QCheckBox::indicator:hover { border-color: {{indicator_hover_border}}; background-color: {{indicator_hover_bg}}; }
QDialog#vita_themes_dialog QCheckBox::indicator:checked,
QDialog#vita_themes_dialog QCheckBox::indicator:indeterminate {
background-color: {{indicator_checked_bg}};
border-color: {{indicator_checked_border}};
image: url(":/icons/check_{{check_icon_suffix}}.svg");
}
QDialog#vita_themes_dialog QCheckBox::indicator:disabled { border-color: {{indicator_disabled_border}}; background-color: {{indicator_disabled_bg}}; }
QDialog#vita_themes_dialog QCheckBox::indicator:checked:disabled,
QDialog#vita_themes_dialog QCheckBox::indicator:indeterminate:disabled {
background-color: {{indicator_checked_disabled_bg}};
border-color: {{indicator_checked_disabled_border}};
}
QDialog#vita_themes_dialog QRadioButton::indicator {
width: 12px;
height: 12px;
padding: 0px;
margin: 0px;
border-radius: 6px;
border: 1px solid {{radio_indicator_border}};
background-color: {{radio_indicator_bg}};
}
QDialog#vita_themes_dialog QRadioButton::indicator:hover { border-color: {{radio_indicator_hover_border}}; background-color: {{radio_indicator_hover_bg}}; }
QDialog#vita_themes_dialog QRadioButton::indicator:checked {
background-color: {{radio_checked_bg}};
border-color: {{radio_checked_border}};
image: url(":/icons/radio_dot_{{check_icon_suffix}}.svg");
}
QDialog#vita_themes_dialog QRadioButton::indicator:disabled { border-color: {{indicator_disabled_border}}; background-color: {{indicator_disabled_bg}}; }
QDialog#vita_themes_dialog QRadioButton::indicator:checked:disabled {
background-color: {{radio_checked_disabled_bg}};
border-color: {{radio_checked_disabled_border}};
}
QDialog#controls_dialog QToolButton[modeButton="true"] {
background: transparent;
border: none;
border-radius: 10px;
padding: 6px 12px;
}
QDialog#controls_dialog QToolButton[modeButton="true"]:hover { background-color: {{controls_mode_hover_bg}}; }
QDialog#controls_dialog QToolButton[modeButton="true"]:checked { background-color: {{controls_mode_checked_bg}}; color: {{dialog_mode_checked_text_hex}}; }
QDialog#controls_dialog QToolButton[modeButton="true"]:pressed { background-color: {{controls_mode_pressed_bg}}; color: {{dialog_mode_checked_text_hex}}; }
QDialog#controls_dialog QPushButton {
background-color: {{controls_button_bg}};
color: {{dialog_text_hex}};
border: none;
border-radius: 6px;
padding: 6px 12px;
outline: none;
}
QDialog#controls_dialog QPushButton:hover { background-color: {{controls_button_hover_bg}}; border: none; }
QDialog#controls_dialog QPushButton:pressed { background-color: {{controls_button_pressed_bg}}; border: none; }
QDialog#controls_dialog QPushButton:disabled { color: {{dialog_disabled_text_hex}}; background-color: {{controls_button_disabled_bg}}; border: none; }
QDialog#controls_dialog QPushButton[bindButton="true"] {
background-color: {{controls_bind_bg}};
border-radius: 6px;
padding: 6px 12px;
}
QDialog#controls_dialog QPushButton[bindButton="true"]:hover { background-color: {{controls_bind_hover_bg}}; }
QDialog#controls_dialog QPushButton[bindButton="true"]:pressed { background-color: {{controls_bind_pressed_bg}}; }
QDialog#controls_dialog QPushButton[bindButton="true"][captureActive="true"] {
background-color: {{controls_capture_bg}};
color: {{dialog_selected_text_hex}};
}
QDialog#controls_dialog QPushButton[bindButton="true"][captureActive="true"]:hover { background-color: {{controls_capture_hover_bg}}; }
QDialog#controls_dialog QPushButton[bindButton="true"][captureActive="true"]:pressed { background-color: {{controls_capture_pressed_bg}}; }
QStatusBar#emulator_status_bar {
background-color: {{status_bar_bg}};
color: {{text_hex}};
border-top: 1px solid {{status_bar_border}};
}
QStatusBar#emulator_status_bar::item { border: none; }
QStatusBar#emulator_status_bar QPushButton#status_button {
border: 1px solid transparent;
padding: 2px 8px;
font-weight: bold;
font-size: 11px;
background: transparent;
border-radius: 6px;
color: {{text_hex}};
}
QStatusBar#emulator_status_bar QPushButton#status_button:hover { border: 1px solid {{status_button_hover_border}}; background: {{status_button_hover_bg}}; }
QStatusBar#emulator_status_bar QPushButton#status_button:pressed { border: 1px solid {{status_button_pressed_border}}; background: {{status_button_pressed_bg}}; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="renderer_vulkan"] { color: {{renderer_vulkan_hex}}; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="renderer_opengl"] { color: {{renderer_opengl_hex}}; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="high"] { color: {{status_high_hex}}; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="standard"] { color: {{status_standard_hex}}; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="neutral"] { color: {{status_neutral_hex}}; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="audio"] { color: {{status_audio_hex}}; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="muted"] { color: {{status_muted_hex}}; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="update"] { color: {{status_update_hex}}; }
QLineEdit {
background-color: {{line_edit_bg}};
color: {{text_hex}};
border: 1px solid {{line_edit_border}};
border-radius: 2px;
padding: 2px 4px;
selection-background-color: {{line_edit_selection_bg}};
selection-color: {{selected_text_hex}};
}
QLineEdit:focus { border-color: {{line_edit_focus_border}}; }
QLineEdit:disabled { color: {{disabled_text_hex}}; background-color: {{line_edit_disabled_bg}}; border-color: {{line_edit_disabled_border}}; }
QLineEdit#mw_searchbar {
background: transparent;
border: 1px solid {{main_search_border}};
border-radius: 10px;
color: {{text_hex}};
padding: 5px 12px;
selection-background-color: {{main_search_selection_bg}};
selection-color: {{selected_text_hex}};
}
QLineEdit#mw_searchbar:focus {
background: {{main_search_focus_bg}};
border-color: {{main_search_focus_border}};
}
QComboBox {
background-color: {{combo_bg}};
color: {{text_hex}};
border: 1px solid {{combo_border}};
border-radius: 2px;
padding: 2px 28px 2px 6px;
}
QComboBox:hover { border-color: {{combo_hover_border}}; }
QComboBox:disabled { color: {{disabled_text_hex}}; background-color: {{combo_disabled_bg}}; border-color: {{combo_disabled_border}}; }
QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 24px; border: none; background: transparent; }
QComboBox::down-arrow { image: url(":/icons/chevron_down_{{icon_suffix}}.svg"); width: 10px; height: 6px; }
QComboBox::down-arrow:on { top: 1px; }
QComboBox QAbstractItemView {
background-color: {{combo_popup_bg}};
color: {{text_hex}};
border: 1px solid {{combo_popup_border}};
selection-background-color: {{combo_popup_selection_bg}};
selection-color: {{selected_text_hex}};
}
QSpinBox, QDoubleSpinBox {
background-color: {{spin_bg}};
color: {{text_hex}};
border: 1px solid {{spin_border}};
border-radius: 2px;
padding: 2px 24px 2px 6px;
selection-background-color: {{spin_selection_bg}};
selection-color: {{selected_text_hex}};
}
QSpinBox:disabled, QDoubleSpinBox:disabled { color: {{disabled_text_hex}}; background-color: {{spin_disabled_bg}}; border-color: {{spin_disabled_border}}; }
QSpinBox::up-button, QDoubleSpinBox::up-button { subcontrol-origin: border; subcontrol-position: top right; width: 18px; border: none; background: transparent; }
QSpinBox::down-button, QDoubleSpinBox::down-button { subcontrol-origin: border; subcontrol-position: bottom right; width: 18px; border: none; background: transparent; }
QSpinBox::up-arrow, QDoubleSpinBox::up-arrow { image: url(":/icons/chevron_up_{{icon_suffix}}.svg"); width: 10px; height: 6px; }
QSpinBox::down-arrow, QDoubleSpinBox::down-arrow { image: url(":/icons/chevron_down_{{icon_suffix}}.svg"); width: 10px; height: 6px; }
QCheckBox { color: {{text_hex}}; spacing: 6px; }
QCheckBox:disabled { color: {{disabled_text_hex}}; }
QRadioButton { color: {{text_hex}}; spacing: 6px; }
QRadioButton:disabled { color: {{disabled_text_hex}}; }
QDialog#controls_dialog,
QDialog[class="settings-window"] { color: {{dialog_text_hex}}; }
QDialog#controls_dialog QLabel,
QDialog#controls_dialog QCheckBox,
QDialog#controls_dialog QRadioButton,
QDialog#controls_dialog QGroupBox,
QDialog[class="settings-window"] QLabel,
QDialog[class="settings-window"] QCheckBox,
QDialog[class="settings-window"] QRadioButton,
QDialog[class="settings-window"] QGroupBox { color: {{dialog_text_hex}}; background: transparent; }
QDialog#controls_dialog QToolButton[modeButton="true"] { color: {{dialog_text_hex}}; }
QCheckBox::indicator,
QGroupBox::indicator,
QListView::indicator,
QListWidget::indicator {
width: 12px;
height: 12px;
padding: 0px;
margin: 0px;
border-radius: 3px;
border: 1px solid {{indicator_border}};
background-color: {{indicator_bg}};
}
QCheckBox::indicator:hover,
QGroupBox::indicator:hover,
QListView::indicator:hover,
QListWidget::indicator:hover { border-color: {{indicator_hover_border}}; background-color: {{indicator_hover_bg}}; }
QCheckBox::indicator:checked,
QCheckBox::indicator:indeterminate,
QGroupBox::indicator:checked,
QGroupBox::indicator:indeterminate,
QListView::indicator:checked,
QListView::indicator:indeterminate,
QListWidget::indicator:checked,
QListWidget::indicator:indeterminate {
background-color: {{indicator_checked_bg}};
border-color: {{indicator_checked_border}};
image: url(":/icons/check_{{check_icon_suffix}}.svg");
}
QCheckBox::indicator:disabled,
QGroupBox::indicator:disabled,
QListView::indicator:disabled,
QListWidget::indicator:disabled { border-color: {{indicator_disabled_border}}; background-color: {{indicator_disabled_bg}}; }
QCheckBox::indicator:checked:disabled,
QCheckBox::indicator:indeterminate:disabled,
QGroupBox::indicator:checked:disabled,
QGroupBox::indicator:indeterminate:disabled,
QListView::indicator:checked:disabled,
QListView::indicator:indeterminate:disabled,
QListWidget::indicator:checked:disabled,
QListWidget::indicator:indeterminate:disabled {
background-color: {{indicator_checked_disabled_bg}};
border-color: {{indicator_checked_disabled_border}};
}
QRadioButton::indicator {
width: 12px;
height: 12px;
border-radius: 6px;
border: 1px solid {{radio_indicator_border}};
background-color: {{radio_indicator_bg}};
}
QRadioButton::indicator:hover { border-color: {{radio_indicator_hover_border}}; background-color: {{radio_indicator_hover_bg}}; }
QRadioButton::indicator:checked {
background-color: {{radio_checked_bg}};
border-color: {{radio_checked_border}};
image: url(":/icons/radio_dot_{{check_icon_suffix}}.svg");
}
QRadioButton::indicator:disabled { border-color: {{indicator_disabled_border}}; background-color: {{indicator_disabled_bg}}; }
QRadioButton::indicator:checked:disabled {
background-color: {{radio_checked_disabled_bg}};
border-color: {{radio_checked_disabled_border}};
}
QGroupBox {
color: {{text_hex}};
border: 1px solid {{group_border}};
border-radius: 4px;
margin-top: 0.5em;
padding-top: 0.6em;
}
QGroupBox:disabled { color: {{disabled_text_hex}}; border-color: {{group_disabled_border}}; }
QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; padding: 0 4px; color: {{text_hex}}; }
QLabel { color: {{text_hex}}; background: transparent; }
QLabel:disabled { color: {{disabled_text_hex}}; }
QDockWidget#apps_list,
QDockWidget#logger {
background: transparent;
color: {{selected_text_hex}};
}
QDockWidget#apps_list::title,
QDockWidget#logger::title {
background: {{dock_title_bg}};
border: none;
padding-top: 0.2em;
padding-left: 0.2em;
}
QDockWidget#apps_list::close-button,
QDockWidget#apps_list::float-button,
QDockWidget#logger::close-button,
QDockWidget#logger::float-button {
background-color: {{dock_title_bg}};
border: none;
}
QDockWidget#apps_list::close-button:hover,
QDockWidget#apps_list::float-button:hover,
QDockWidget#logger::close-button:hover,
QDockWidget#logger::float-button:hover {
background-color: {{dock_button_hover_bg}};
}
QDockWidget#apps_list[floating="true"],
QDockWidget#logger[floating="true"] {
background-color: {{window_hex}};
}
QWidget#apps_list_frame,
QWidget#log_surface {
background-color: {{apps_surface_bg}};
}
QTableWidget#apps_list_table,
QTableView#apps_list_table,
QWidget#apps_list_viewport,
QPlainTextEdit#log_frame,
QWidget#log_frame_viewport { background: transparent; }
QTableWidget, QTableView {
alternate-background-color: {{alt_row_bg}};
background-color: transparent;
border: none;
color: {{text_hex}};
gridline-color: {{table_gridline}};
}
QTableWidget:disabled, QTableView:disabled { color: {{disabled_text_hex}}; background-color: {{table_disabled_bg}}; }
QTableCornerButton::section { background-color: {{table_corner_bg}}; border: 1px solid {{table_corner_border}}; }
QTableView::item { border-left: 0.063em solid transparent; border-right: 0.063em solid transparent; padding-left: 0.313em; }
QTableView::item:selected { background-color: {{table_selected_bg}}; color: {{selected_text_hex}}; }
QTableWidget#apps_list_table::item:selected, QTableView#apps_list_table::item:selected {
background-color: {{table_selected_bg}};
color: {{selected_text_hex}};
border-left: 0.063em solid transparent;
border-right: 0.063em solid transparent;
}
QTableView#apps_list_table QHeaderView,
QTableWidget#apps_list_table QHeaderView {
background-color: {{header_bg}};
}
QTableView#apps_list_table QHeaderView::section,
QTableWidget#apps_list_table QHeaderView::section {
background-color: {{header_bg}};
color: {{selected_text_hex}};
border: 0.063em solid {{header_border}};
padding-top: 0;
padding-bottom: 0;
}
QTableView#trophy_app_table::item:selected, QTableView#trophy_table::item:selected {
background-color: {{table_selected_bg}};
color: {{selected_text_hex}};
border-left: 0.063em solid transparent;
border-right: 0.063em solid transparent;
}
QHeaderView::section {
background-color: {{header_bg}};
color: {{selected_text_hex}};
padding-left: .5em;
padding-right: .5em;
padding-top: .4em;
padding-bottom: -.1em;
border: 0.063em solid {{header_border}};
}
QHeaderView::section:hover { background: {{header_hover_bg}}; }
QPlainTextEdit#log_frame {
color: {{selected_text_hex}};
border: none;
}
QWidget#log_search_container {
background-color: {{log_search_bg}};
border: 1px solid {{log_search_border}};
border-radius: 4px;
}
QWidget#toolbar_icon_size_container {
background: transparent;
border: none;
}
QSlider#toolbar_icon_size_slider {
background: transparent;
margin: 0;
min-height: 20px;
}
QSlider#toolbar_icon_size_slider::groove:horizontal {
background: {{toolbar_slider_groove_bg}};
border: none;
height: 6px;
border-radius: 3px;
}
QSlider#toolbar_icon_size_slider::sub-page:horizontal {
background: {{toolbar_slider_subpage_bg}};
border: none;
height: 6px;
border-radius: 3px;
}
QSlider#toolbar_icon_size_slider::add-page:horizontal {
background: {{toolbar_slider_addpage_bg}};
border: none;
height: 6px;
border-radius: 3px;
}
QSlider#toolbar_icon_size_slider::handle:horizontal {
background: {{toolbar_slider_handle_bg}};
border: none;
width: 14px;
height: 14px;
margin: -4px 0;
border-radius: 7px;
}
QSlider#toolbar_icon_size_slider::handle:horizontal:hover { background: {{toolbar_slider_handle_hover_bg}}; }
QLabel[themeRole="logFatal"] { color: {{log_fatal_hex}}; }
QLabel[themeRole="logError"] { color: {{log_error_hex}}; }
QLabel[themeRole="logWarning"] { color: {{log_warning_hex}}; }
QLabel[themeRole="logNotice"] { color: {{selected_text_hex}}; }
QLabel[themeRole="logDebug"] { color: {{log_debug_hex}}; }
QLabel[themeRole="logTrace"] { color: {{muted_text_hex}}; }
QLabel[themeRole="sectionHeader"] { font-weight: 600; margin-top: 8px; }
QLabel[severity="success"] { border-radius: 10px; padding: 4px 10px; background-color: {{severity_success_bg}}; color: {{severity_success_text_hex}}; }
QLabel[severity="error"] { border-radius: 10px; padding: 4px 10px; background-color: {{severity_error_bg}}; color: {{severity_error_text_hex}}; }
QTabWidget::pane { border: 1px solid {{tab_pane_border}}; background-color: {{tab_pane_bg}}; }
QTabBar::tab {
background-color: {{tab_bg}};
color: {{text_hex}};
padding: 6px 14px;
border: 1px solid {{tab_border}};
border-bottom: none;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
QTabBar::tab:selected { background-color: {{tab_selected_bg}}; }
QTabBar::tab:!selected { margin-top: 2px; }
QTabBar::tab:hover:!selected { background-color: {{tab_hover_bg}}; }
QListWidget {
background-color: transparent;
color: {{text_hex}};
border: 1px solid {{list_border}};
}
QListWidget:disabled { color: {{disabled_text_hex}}; background-color: {{list_disabled_bg}}; border-color: {{list_disabled_border}}; }
QListWidget::item:selected { background-color: {{list_selected_bg}}; color: {{selected_text_hex}}; }
QListWidget::item:hover:!selected { background-color: {{list_hover_bg}}; }
QDialog[class="settings-window"] QListWidget#settingsCategory {
background-color: {{settings_category_bg}};
border: none;
border-radius: 10px;
padding: 6px;
outline: none;
}
QDialog[class="settings-window"] QListWidget#settingsCategory::item {
background: transparent;
border: none;
border-radius: 10px;
padding: 8px 12px;
margin: 0px;
}
QDialog[class="settings-window"] QListWidget#settingsCategory::item:selected,
QDialog[class="settings-window"] QListWidget#settingsCategory::item:selected:active,
QDialog[class="settings-window"] QListWidget#settingsCategory::item:selected:!active {
background-color: {{settings_category_selected_bg}};
color: {{dialog_selected_text_hex}};
}
QDialog[class="settings-window"] QListWidget#settingsCategory::item:hover:!selected { background-color: {{settings_category_hover_bg}}; }
QDialog[class="settings-window"] QTextBrowser#helpText {
background-color: {{settings_help_bg}};
color: {{dialog_text_hex}};
border: none;
border-radius: 10px;
padding: 3px 4px;
}
QDialog[class="settings-window"] QListWidget#modules_list {
background-color: {{modules_list_bg}};
color: {{dialog_text_hex}};
border: 1px solid {{modules_list_border}};
border-radius: 6px;
padding: 4px;
outline: none;
}
QDialog[class="settings-window"] QListWidget#modules_list::item {
padding: 3px 8px;
border-radius: 4px;
margin: 0px;
}
QDialog[class="settings-window"] QListWidget#modules_list::item:hover { background-color: {{modules_list_hover_bg}}; }
QDialog[class="settings-window"] QListWidget#modules_list::item:selected { background: transparent; color: {{dialog_text_hex}}; }
QScrollBar:vertical { background: {{scrollbar_bg}}; width: 14px; margin: 0; }
QScrollBar::handle:vertical { background: {{scrollbar_handle_bg}}; min-height: 20px; border-radius: 3px; margin: 2px; }
QScrollBar::handle:vertical:hover { background: {{scrollbar_handle_hover_bg}}; }
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; }
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: none; }
QScrollBar:horizontal { background: {{scrollbar_bg}}; height: 14px; margin: 0; }
QScrollBar::handle:horizontal { background: {{scrollbar_handle_bg}}; min-width: 20px; border-radius: 3px; margin: 2px; }
QScrollBar::handle:horizontal:hover { background: {{scrollbar_handle_hover_bg}}; }
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0px; }
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: none; }
QAbstractScrollArea::corner { background: {{scrollbar_bg}}; }
QSlider::groove:horizontal { background: transparent; height: 4px; border-radius: 2px; }
QSlider::sub-page:horizontal { background: {{slider_subpage_bg}}; height: 4px; border-radius: 2px; }
QSlider::add-page:horizontal { background: {{slider_addpage_bg}}; height: 4px; border-radius: 2px; }
QSlider::handle:horizontal { background: {{slider_handle_bg}}; width: 14px; margin: -5px 0; border-radius: 7px; }
QSlider::handle:horizontal:hover { background: {{slider_handle_hover_bg}}; }
QSlider::groove:vertical { background: transparent; width: 4px; border-radius: 2px; }
QSlider::sub-page:vertical { background: {{slider_subpage_bg}}; width: 4px; border-radius: 2px; }
QSlider::add-page:vertical { background: {{slider_addpage_bg}}; width: 4px; border-radius: 2px; }
QSlider::handle:vertical { background: {{slider_handle_bg}}; height: 14px; margin: 0 -5px; border-radius: 7px; }
QSlider::handle:vertical:hover { background: {{slider_handle_hover_bg}}; }
QProgressBar {
background-color: {{progress_bg}};
color: {{text_hex}};
border: 1px solid {{progress_border}};
text-align: center;
border-radius: 2px;
}
QProgressBar::chunk { background-color: {{progress_chunk_bg}}; border-radius: 2px; }
QSplitter::handle { background-color: {{splitter_handle_bg}}; }
QToolTip {
background-color: {{tooltip_bg}};
color: {{text_hex}};
border: 1px solid {{tooltip_border}};
padding: 2px;
}
QPlainTextEdit, QTextEdit, QTextBrowser {
background-color: {{text_edit_bg}};
color: {{text_hex}};
border: 1px solid {{text_edit_border}};
}
QPlainTextEdit:disabled, QTextEdit:disabled, QTextBrowser:disabled {
color: {{disabled_text_hex}};
background-color: {{text_edit_disabled_bg}};
border-color: {{text_edit_disabled_border}};
}
QDialogButtonBox QPushButton { min-width: 80px; }
@@ -0,0 +1,26 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1600 900">
<defs>
<linearGradient id="sky" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#101722"/>
<stop offset="45%" stop-color="#1f3953"/>
<stop offset="100%" stop-color="#355e7f"/>
</linearGradient>
<radialGradient id="glow" cx="72%" cy="24%" r="52%">
<stop offset="0%" stop-color="#f6b67b" stop-opacity="0.82"/>
<stop offset="38%" stop-color="#db8f5e" stop-opacity="0.45"/>
<stop offset="100%" stop-color="#db8f5e" stop-opacity="0"/>
</radialGradient>
<linearGradient id="ridge" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#0f2438" stop-opacity="0"/>
<stop offset="100%" stop-color="#07111d" stop-opacity="0.86"/>
</linearGradient>
</defs>
<rect width="1600" height="900" fill="url(#sky)"/>
<rect width="1600" height="900" fill="url(#glow)"/>
<path d="M0 545 C170 500 305 470 510 520 C720 572 860 567 1055 510 C1248 455 1438 446 1600 498 L1600 900 L0 900 Z" fill="#0b1624" fill-opacity="0.74"/>
<path d="M0 618 C220 555 390 552 612 620 C812 680 1010 694 1252 618 C1393 574 1512 553 1600 560 L1600 900 L0 900 Z" fill="#13283d" fill-opacity="0.66"/>
<path d="M0 690 C155 654 319 661 500 712 C702 770 933 795 1130 736 C1337 674 1494 652 1600 672 L1600 900 L0 900 Z" fill="url(#ridge)"/>
<circle cx="1230" cy="190" r="108" fill="#ffd8b1" fill-opacity="0.22"/>
<circle cx="1298" cy="160" r="28" fill="#ffffff" fill-opacity="0.16"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,539 @@
/*!PALETTE
* dark: dark
* window: #102030
* window-text: #f2f3f6
* base: #112334
* alternate-base: #173047
* tooltip-base: #102030
* tooltip-text: #f2f3f6
* text: #f2f3f6
* button: #173047
* button-text: #f2f3f6
* bright-text: #ffffff
* link: #8cd4ff
* highlight: #56b2ff
* inactive-highlight: #56b2ff
* disabled-highlight: #3356b2ff
* highlight-text: #061018
* inactive-highlight-text: #061018
* disabled-highlight-text: #ced7de
* placeholder-text: #a9bbc8
* disabled-button-text: #788791
* disabled-window-text: #788791
* disabled-text: #788791
*/
QWidget { color: #edf3f8; background-color: #102030; }
QWidget:disabled { color: #788791; }
QMainWindow#main_window {
border-image: url("shell_background.svg") 0 0 0 0 stretch stretch;
background-color: #102030;
}
QWidget#mw_frame,
QMainWindow#mw_dock_host {
background: transparent;
}
QMenuBar {
background-color: rgba(11, 20, 31, 0.42);
color: #eef4f8;
border-bottom: 1px solid rgba(255, 255, 255, 0.10);
}
QMenuBar::item { background: transparent; padding: 4px 8px; }
QMenuBar::item:selected { background: rgba(255, 255, 255, 0.11); }
QMenu {
background-color: rgba(10, 19, 30, 0.95);
color: #eef4f8;
border: 1px solid rgba(255, 255, 255, 0.16);
}
QMenu::item:selected { background-color: rgba(86, 178, 255, 0.28); color: #ffffff; }
QMenu::item:disabled { color: #7f8d97; }
QMenu::separator { height: 1px; background: rgba(255, 255, 255, 0.12); margin: 4px 8px; }
QToolBar#mw_toolbar {
background-color: rgba(8, 17, 27, 0.45);
border: none;
border-top: 1px solid rgba(255, 255, 255, 0.12);
border-bottom: 1px solid rgba(255, 255, 255, 0.10);
}
QToolBar#mw_toolbar::separator {
background-color: rgba(255, 255, 255, 0.16);
width: 0.125em;
margin-top: 0.250em;
margin-bottom: 0.250em;
}
QToolBar#mw_toolbar > QWidget { background: transparent; }
QToolButton {
color: #eef4f8;
background: transparent;
border: 1px solid transparent;
padding: 3px;
}
QToolButton:hover { background-color: rgba(255, 255, 255, 0.10); border: 1px solid rgba(255, 255, 255, 0.12); }
QToolButton:pressed { background-color: rgba(86, 178, 255, 0.18); }
QToolButton:disabled { color: #7f8d97; }
QToolButton#welcome_firmware_button {
background-color: rgba(9, 18, 29, 0.68);
color: #eef4f8;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 2px;
padding: 4px 32px 4px 12px;
text-align: left;
}
QToolButton#welcome_firmware_button:hover { background-color: rgba(255, 255, 255, 0.10); }
QToolButton#welcome_firmware_button:pressed { background-color: rgba(86, 178, 255, 0.18); }
QToolButton#welcome_firmware_button::menu-button {
subcontrol-origin: padding;
subcontrol-position: top right;
width: 24px;
border-left: 1px solid rgba(255, 255, 255, 0.14);
background: rgba(255, 255, 255, 0.05);
}
QLabel[themeRole="toolbarIconTint"] { color: #f2f3f6; }
QLabel[themeRole="mutedText"] { color: #9eb2c1; }
QPushButton {
background-color: rgba(10, 20, 31, 0.66);
color: #eef4f8;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 2px;
padding: 4px 16px;
min-height: 1.2em;
}
QPushButton:hover { background-color: rgba(255, 255, 255, 0.10); border-color: rgba(255, 255, 255, 0.18); }
QPushButton:pressed { background-color: rgba(86, 178, 255, 0.20); }
QPushButton:disabled { color: #7f8d97; background-color: rgba(8, 15, 24, 0.56); border-color: rgba(255, 255, 255, 0.08); }
QPushButton:default { border-color: rgba(140, 212, 255, 0.60); }
QDialog#vita_themes_dialog { color: #eef4f8; }
QDialog#vita_themes_dialog QLabel,
QDialog#vita_themes_dialog QCheckBox,
QDialog#vita_themes_dialog QRadioButton { color: #eef4f8; background: transparent; }
QDialog#vita_themes_dialog QLabel[themeRole="mutedText"] { color: #9eb2c1; }
QDialog#vita_themes_dialog QWidget[themeRole="backgroundCell"] { background: transparent; }
QDialog#vita_themes_dialog QCheckBox[themeRole="backgroundCellControl"],
QDialog#vita_themes_dialog QRadioButton[themeRole="backgroundCellControl"] { spacing: 0px; padding: 0px; margin: 0px; }
QDialog#vita_themes_dialog QListWidget#vitaThemesList {
background-color: rgba(10, 19, 30, 0.96);
color: #eef4f8;
border: none;
border-radius: 10px;
padding: 6px;
outline: none;
}
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item {
background: transparent;
border: none;
border-radius: 10px;
padding: 8px 12px;
margin: 0px;
}
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item:selected,
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item:selected:active,
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item:selected:!active { background-color: rgba(86, 178, 255, 0.28); color: #ffffff; }
QDialog#vita_themes_dialog QListWidget#vitaThemesList::item:hover:!selected { background-color: rgba(255, 255, 255, 0.06); }
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList {
background-color: rgba(9, 18, 29, 0.72);
color: #eef4f8;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 8px;
padding: 2px;
outline: none;
}
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item {
padding: 5px 8px;
border-radius: 0px;
margin: 0px;
}
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item:selected,
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item:selected:active,
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item:selected:!active { background-color: rgba(86, 178, 255, 0.28); color: #ffffff; }
QDialog#vita_themes_dialog QTreeWidget#vitaThemesBackgroundList::item:hover:!selected { background-color: rgba(255, 255, 255, 0.08); }
QDialog#vita_themes_dialog QTextBrowser#vitaThemesHelpText {
background-color: rgba(10, 19, 30, 0.96);
color: #eef4f8;
border: none;
border-radius: 10px;
padding: 3px 4px;
}
QDialog#vita_themes_dialog QScrollArea { background: transparent; border: none; }
QDialog#vita_themes_dialog QCheckBox::indicator {
width: 12px;
height: 12px;
padding: 0px;
margin: 0px;
border-radius: 3px;
border: 1px solid rgba(255, 255, 255, 0.20);
background-color: rgba(9, 18, 29, 0.72);
}
QDialog#vita_themes_dialog QCheckBox::indicator:hover { border-color: rgba(140, 212, 255, 0.54); background-color: rgba(255, 255, 255, 0.08); }
QDialog#vita_themes_dialog QCheckBox::indicator:checked,
QDialog#vita_themes_dialog QCheckBox::indicator:indeterminate {
background-color: rgba(140, 212, 255, 0.86);
border-color: rgba(140, 212, 255, 0.92);
image: url(":/icons/check_dark.svg");
}
QDialog#vita_themes_dialog QCheckBox::indicator:disabled { border-color: rgba(255, 255, 255, 0.10); background-color: rgba(8, 15, 24, 0.60); }
QDialog#vita_themes_dialog QCheckBox::indicator:checked:disabled,
QDialog#vita_themes_dialog QCheckBox::indicator:indeterminate:disabled {
background-color: rgba(140, 212, 255, 0.32);
border-color: rgba(140, 212, 255, 0.40);
}
QDialog#vita_themes_dialog QRadioButton::indicator {
width: 12px;
height: 12px;
padding: 0px;
margin: 0px;
border-radius: 6px;
border: 1px solid rgba(255, 255, 255, 0.20);
background-color: rgba(9, 18, 29, 0.72);
}
QDialog#vita_themes_dialog QRadioButton::indicator:hover { border-color: rgba(140, 212, 255, 0.54); background-color: rgba(255, 255, 255, 0.08); }
QDialog#vita_themes_dialog QRadioButton::indicator:checked {
background-color: rgba(140, 212, 255, 0.86);
border-color: rgba(140, 212, 255, 0.92);
image: url(":/icons/radio_dot_dark.svg");
}
QDialog#vita_themes_dialog QRadioButton::indicator:disabled { border-color: rgba(255, 255, 255, 0.10); background-color: rgba(8, 15, 24, 0.60); }
QDialog#vita_themes_dialog QRadioButton::indicator:checked:disabled {
background-color: rgba(140, 212, 255, 0.32);
border-color: rgba(140, 212, 255, 0.40);
}
QDialog#controls_dialog QToolButton[modeButton="true"] {
background: transparent;
border: none;
border-radius: 10px;
padding: 6px 12px;
}
QDialog#controls_dialog QToolButton[modeButton="true"]:hover { background-color: rgba(255, 255, 255, 0.08); }
QDialog#controls_dialog QToolButton[modeButton="true"]:checked { background-color: rgba(255, 255, 255, 0.14); }
QDialog#controls_dialog QToolButton[modeButton="true"]:pressed { background-color: rgba(255, 255, 255, 0.20); }
QDialog#controls_dialog QPushButton {
background-color: rgba(9, 18, 29, 0.66);
color: #eef4f8;
border: none;
border-radius: 6px;
padding: 6px 12px;
outline: none;
}
QDialog#controls_dialog QPushButton:hover { background-color: rgba(255, 255, 255, 0.10); border: none; }
QDialog#controls_dialog QPushButton:pressed { background-color: rgba(86, 178, 255, 0.20); border: none; }
QDialog#controls_dialog QPushButton:disabled { color: #7f8d97; background-color: rgba(8, 15, 24, 0.56); border: none; }
QDialog#controls_dialog QPushButton[bindButton="true"] {
background-color: rgba(9, 18, 29, 0.72);
border-radius: 6px;
padding: 6px 12px;
}
QDialog#controls_dialog QPushButton[bindButton="true"]:hover { background-color: rgba(255, 255, 255, 0.08); }
QDialog#controls_dialog QPushButton[bindButton="true"]:pressed { background-color: rgba(86, 178, 255, 0.18); }
QDialog#controls_dialog QPushButton[bindButton="true"][captureActive="true"] {
background-color: rgba(140, 212, 255, 0.22);
color: #ffffff;
}
QDialog#controls_dialog QPushButton[bindButton="true"][captureActive="true"]:hover { background-color: rgba(140, 212, 255, 0.28); }
QDialog#controls_dialog QPushButton[bindButton="true"][captureActive="true"]:pressed { background-color: rgba(140, 212, 255, 0.34); }
QStatusBar#emulator_status_bar {
background-color: rgba(8, 17, 27, 0.52);
color: #eef4f8;
border-top: 1px solid rgba(255, 255, 255, 0.10);
}
QStatusBar#emulator_status_bar::item { border: none; }
QStatusBar#emulator_status_bar QPushButton#status_button {
border: 1px solid transparent;
padding: 2px 8px;
font-weight: bold;
font-size: 11px;
background: transparent;
border-radius: 6px;
color: #eef4f8;
}
QStatusBar#emulator_status_bar QPushButton#status_button:hover { border: 1px solid rgba(255, 255, 255, 0.14); background: rgba(255, 255, 255, 0.06); }
QStatusBar#emulator_status_bar QPushButton#status_button:pressed { border: 1px solid rgba(255, 255, 255, 0.20); background: rgba(86, 178, 255, 0.18); }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="renderer_vulkan"] { color: #ff8f7a; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="renderer_opengl"] { color: #7fb2ff; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="high"] { color: #ffd278; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="standard"] { color: #b8c5cf; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="neutral"] { color: #d6e0e7; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="audio"] { color: #8ef0a2; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="muted"] { color: #7f8d97; }
QStatusBar#emulator_status_bar QPushButton#status_button[accent="update"] { color: #a3f0b5; }
QLineEdit {
background-color: rgba(9, 18, 29, 0.72);
color: #eef4f8;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 2px;
padding: 2px 4px;
selection-background-color: rgba(140, 212, 255, 0.50);
selection-color: #061018;
}
QLineEdit:focus { border-color: rgba(140, 212, 255, 0.65); }
QLineEdit:disabled { color: #788791; background-color: rgba(8, 15, 24, 0.60); border-color: rgba(255, 255, 255, 0.08); }
QLineEdit#mw_searchbar {
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 10px;
color: #eef4f8;
padding: 5px 12px;
selection-background-color: rgba(140, 212, 255, 0.42);
selection-color: #061018;
}
QLineEdit#mw_searchbar:focus {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(140, 212, 255, 0.42);
}
QComboBox {
background-color: rgba(9, 18, 29, 0.72);
color: #eef4f8;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 2px;
padding: 2px 28px 2px 6px;
}
QComboBox:hover { border-color: rgba(255, 255, 255, 0.20); }
QComboBox:disabled { color: #788791; background-color: rgba(8, 15, 24, 0.60); border-color: rgba(255, 255, 255, 0.08); }
QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 24px; border: none; background: transparent; }
QComboBox::down-arrow { image: url(":/icons/chevron_down_light.svg"); width: 10px; height: 6px; }
QComboBox QAbstractItemView {
background-color: rgba(10, 19, 30, 0.96);
color: #eef4f8;
border: 1px solid rgba(255, 255, 255, 0.16);
selection-background-color: rgba(86, 178, 255, 0.28);
selection-color: #ffffff;
}
QSpinBox, QDoubleSpinBox {
background-color: rgba(9, 18, 29, 0.72);
color: #eef4f8;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 2px;
padding: 2px 24px 2px 6px;
selection-background-color: rgba(140, 212, 255, 0.50);
selection-color: #061018;
}
QSpinBox:disabled, QDoubleSpinBox:disabled { color: #788791; background-color: rgba(8, 15, 24, 0.60); border-color: rgba(255, 255, 255, 0.08); }
QSpinBox::up-button, QDoubleSpinBox::up-button { subcontrol-origin: border; subcontrol-position: top right; width: 18px; border: none; background: transparent; }
QSpinBox::down-button, QDoubleSpinBox::down-button { subcontrol-origin: border; subcontrol-position: bottom right; width: 18px; border: none; background: transparent; }
QSpinBox::up-arrow, QDoubleSpinBox::up-arrow { image: url(":/icons/chevron_up_light.svg"); width: 10px; height: 6px; }
QSpinBox::down-arrow, QDoubleSpinBox::down-arrow { image: url(":/icons/chevron_down_light.svg"); width: 10px; height: 6px; }
QCheckBox { color: #eef4f8; spacing: 6px; }
QCheckBox:disabled { color: #788791; }
QRadioButton { color: #eef4f8; spacing: 6px; }
QRadioButton:disabled { color: #788791; }
QGroupBox {
color: #eef4f8;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 4px;
margin-top: 0.5em;
padding-top: 0.6em;
}
QGroupBox:disabled { color: #7f8d97; border-color: rgba(255, 255, 255, 0.08); }
QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; padding: 0 4px; color: #eef4f8; }
QLabel { color: #eef4f8; background: transparent; }
QLabel:disabled { color: #788791; }
QDockWidget#apps_list,
QDockWidget#logger {
background: transparent;
color: #ffffff;
}
QDockWidget#apps_list::title,
QDockWidget#logger::title {
background: rgba(8, 17, 27, 0.62);
border: none;
padding-top: 0.2em;
padding-left: 0.2em;
}
QDockWidget#apps_list::close-button,
QDockWidget#apps_list::float-button,
QDockWidget#logger::close-button,
QDockWidget#logger::float-button {
background-color: rgba(8, 17, 27, 0.62);
border: none;
}
QDockWidget#apps_list::close-button:hover,
QDockWidget#apps_list::float-button:hover,
QDockWidget#logger::close-button:hover,
QDockWidget#logger::float-button:hover {
background-color: rgba(255, 255, 255, 0.12);
}
QDockWidget#apps_list[floating="true"],
QDockWidget#logger[floating="true"] {
border-image: url("shell_background.svg") 0 0 0 0 stretch stretch;
background-color: #102030;
}
QWidget#apps_list_frame,
QWidget#log_surface {
background-color: rgba(8, 17, 27, 0.38);
}
QTableWidget#apps_list_table,
QWidget#apps_list_viewport,
QPlainTextEdit#log_frame,
QWidget#log_frame_viewport {
background: transparent;
}
QTableWidget, QTableView {
alternate-background-color: rgba(255, 255, 255, 0.04);
background-color: rgba(9, 18, 29, 0.54);
border: none;
color: #eef4f8;
gridline-color: rgba(255, 255, 255, 0.08);
}
QTableWidget:disabled, QTableView:disabled { color: #788791; background-color: rgba(8, 15, 24, 0.56); }
QTableCornerButton::section { background-color: rgba(12, 25, 38, 0.88); border: 1px solid rgba(255, 255, 255, 0.10); }
QTableView::item { border-left: 0.063em solid transparent; border-right: 0.063em solid transparent; padding-left: 0.313em; }
QTableView::item:selected { background-color: rgba(86, 178, 255, 0.20); color: #ffffff; }
QTableWidget#apps_list_table::item:selected, QTableView#apps_list_table::item:selected {
background-color: rgba(86, 178, 255, 0.22);
color: #f7fbfe;
border-left: 0.063em solid transparent;
border-right: 0.063em solid transparent;
}
QTableView#apps_list_table QHeaderView::section, QTableWidget#apps_list_table QHeaderView::section { padding-top: 0; padding-bottom: 0; }
QHeaderView::section {
background-color: rgba(12, 25, 38, 0.84);
color: #ffffff;
padding-left: .5em;
padding-right: .5em;
padding-top: .4em;
padding-bottom: -.1em;
border: 0.063em solid rgba(255, 255, 255, 0.06);
}
QHeaderView::section:hover { background: rgba(255, 255, 255, 0.10); }
QPlainTextEdit#log_frame {
color: #f7fbfe;
border: none;
}
QWidget#log_search_container {
background-color: rgba(8, 17, 27, 0.82);
border: 1px solid rgba(255, 255, 255, 0.16);
border-radius: 4px;
}
QWidget#toolbar_icon_size_container {
background: transparent;
border: none;
}
QSlider#toolbar_icon_size_slider {
background: transparent;
margin: 0;
min-height: 20px;
}
QSlider#toolbar_icon_size_slider::groove:horizontal {
background: rgba(255, 255, 255, 0.18);
border: none;
height: 6px;
border-radius: 3px;
}
QSlider#toolbar_icon_size_slider::sub-page:horizontal {
background: rgba(242, 243, 246, 0.92);
border: none;
height: 6px;
border-radius: 3px;
}
QSlider#toolbar_icon_size_slider::add-page:horizontal {
background: rgba(255, 255, 255, 0.16);
border: none;
height: 6px;
border-radius: 3px;
}
QSlider#toolbar_icon_size_slider::handle:horizontal {
background: #f2f3f6;
border: none;
width: 14px;
height: 14px;
margin: -4px 0;
border-radius: 7px;
}
QSlider#toolbar_icon_size_slider::handle:horizontal:hover { background: #ffffff; }
QLabel[themeRole="logFatal"] { color: #ff9ff3; }
QLabel[themeRole="logError"] { color: #ff9a7f; }
QLabel[themeRole="logWarning"] { color: #ffd37f; }
QLabel[themeRole="logNotice"] { color: #f7fbfe; }
QLabel[themeRole="logDebug"] { color: #8cd4ff; }
QLabel[themeRole="logTrace"] { color: #bdd0de; }
QLabel[themeRole="sectionHeader"] { font-weight: 600; margin-top: 8px; }
QLabel[severity="success"] { border-radius: 10px; padding: 4px 10px; background-color: rgba(71, 125, 92, 0.58); color: #d3ffe0; }
QLabel[severity="error"] { border-radius: 10px; padding: 4px 10px; background-color: rgba(134, 72, 60, 0.62); color: #ffd5cd; }
QTabWidget::pane { border: 1px solid rgba(255, 255, 255, 0.12); background-color: rgba(8, 17, 27, 0.44); }
QTabBar::tab {
background-color: rgba(9, 18, 29, 0.62);
color: #eef4f8;
padding: 6px 14px;
border: 1px solid rgba(255, 255, 255, 0.12);
border-bottom: none;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
QTabBar::tab:selected { background-color: rgba(255, 255, 255, 0.10); }
QTabBar::tab:!selected { margin-top: 2px; }
QTabBar::tab:hover:!selected { background-color: rgba(255, 255, 255, 0.08); }
QListWidget {
background-color: rgba(9, 18, 29, 0.62);
color: #eef4f8;
border: 1px solid rgba(255, 255, 255, 0.12);
}
QListWidget:disabled { color: #788791; background-color: rgba(8, 15, 24, 0.56); border-color: rgba(255, 255, 255, 0.08); }
QListWidget::item:selected { background-color: rgba(86, 178, 255, 0.26); color: #ffffff; }
QListWidget::item:hover:!selected { background-color: rgba(255, 255, 255, 0.06); }
QDialog[class="settings-window"] QListWidget#settingsCategory {
background-color: rgba(9, 18, 29, 0.62);
border: none;
border-radius: 10px;
padding: 6px;
outline: none;
}
QDialog[class="settings-window"] QListWidget#settingsCategory::item {
background: transparent;
border: none;
border-radius: 10px;
padding: 8px 12px;
margin: 0px;
}
QDialog[class="settings-window"] QListWidget#settingsCategory::item:selected,
QDialog[class="settings-window"] QListWidget#settingsCategory::item:selected:active,
QDialog[class="settings-window"] QListWidget#settingsCategory::item:selected:!active {
background-color: rgba(86, 178, 255, 0.26);
color: #ffffff;
}
QDialog[class="settings-window"] QListWidget#settingsCategory::item:hover:!selected { background-color: rgba(255, 255, 255, 0.06); }
QDialog[class="settings-window"] QTextBrowser#helpText {
background-color: rgba(9, 18, 29, 0.62);
color: #eef4f8;
border: none;
border-radius: 10px;
padding: 3px 4px;
}
QScrollBar:vertical { background: rgba(9, 18, 29, 0.52); width: 14px; margin: 0; }
QScrollBar::handle:vertical { background: rgba(255, 255, 255, 0.20); min-height: 20px; border-radius: 3px; margin: 2px; }
QScrollBar::handle:vertical:hover { background: rgba(255, 255, 255, 0.28); }
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; }
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: none; }
QScrollBar:horizontal { background: rgba(9, 18, 29, 0.52); height: 14px; margin: 0; }
QScrollBar::handle:horizontal { background: rgba(255, 255, 255, 0.20); min-width: 20px; border-radius: 3px; margin: 2px; }
QScrollBar::handle:horizontal:hover { background: rgba(255, 255, 255, 0.28); }
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0px; }
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: none; }
QAbstractScrollArea::corner { background: rgba(9, 18, 29, 0.52); }
QSlider::groove:horizontal { background: transparent; height: 4px; border-radius: 2px; }
QSlider::sub-page:horizontal { background: rgba(242, 243, 246, 0.88); height: 4px; border-radius: 2px; }
QSlider::add-page:horizontal { background: rgba(255, 255, 255, 0.20); height: 4px; border-radius: 2px; }
QSlider::handle:horizontal { background: #ced7de; width: 14px; margin: -5px 0; border-radius: 7px; }
QSlider::handle:horizontal:hover { background: #ffffff; }
QSlider::groove:vertical { background: transparent; width: 4px; border-radius: 2px; }
QSlider::sub-page:vertical { background: rgba(242, 243, 246, 0.88); width: 4px; border-radius: 2px; }
QSlider::add-page:vertical { background: rgba(255, 255, 255, 0.20); width: 4px; border-radius: 2px; }
QSlider::handle:vertical { background: #ced7de; height: 14px; margin: 0 -5px; border-radius: 7px; }
QSlider::handle:vertical:hover { background: #ffffff; }
QProgressBar {
background-color: rgba(9, 18, 29, 0.70);
color: #eef4f8;
border: 1px solid rgba(255, 255, 255, 0.12);
text-align: center;
border-radius: 2px;
}
QProgressBar::chunk { background-color: #8cd4ff; border-radius: 2px; }
QSplitter::handle { background-color: rgba(255, 255, 255, 0.14); }
QToolTip {
background-color: rgba(10, 19, 30, 0.96);
color: #eef4f8;
border: 1px solid rgba(255, 255, 255, 0.16);
padding: 2px;
}
QPlainTextEdit, QTextEdit, QTextBrowser {
background-color: rgba(9, 18, 29, 0.62);
color: #eef4f8;
border: 1px solid rgba(255, 255, 255, 0.12);
}
QPlainTextEdit:disabled, QTextEdit:disabled, QTextBrowser:disabled { color: #788791; background-color: rgba(8, 15, 24, 0.56); border-color: rgba(255, 255, 255, 0.08); }
QDialogButtonBox QPushButton { min-width: 80px; }
+5 -2
View File
@@ -18,6 +18,7 @@
#include <gui-qt/apps_list.h>
#include <gui-qt/apps_list_table.h>
#include <gui-qt/gui_settings.h>
#include <gui-qt/theme_surface.h>
#include <app/functions.h>
#include <emuenv/state.h>
@@ -33,15 +34,17 @@
AppsList::AppsList(EmuEnvState &emuenv, QWidget *parent)
: custom_dock_widget(tr("App Library"), parent)
, m_emuenv(emuenv) {
setObjectName(QStringLiteral("AppsList"));
setObjectName(QStringLiteral("apps_list"));
setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
auto *central = new QWidget(this);
auto *central = new ThemeSurface(this);
central->setObjectName(QStringLiteral("apps_list_frame"));
auto *vbox = new QVBoxLayout(central);
vbox->setContentsMargins(0, 4, 0, 0);
vbox->setSpacing(4);
m_table = new AppsListTable(central);
m_table->viewport()->setObjectName(QStringLiteral("apps_list_viewport"));
vbox->addWidget(m_table);
setWidget(central);
+2 -11
View File
@@ -72,10 +72,7 @@ QLabel *make_muted_label(const QString &text, QWidget *parent, Qt::Alignment ali
auto *label = new QLabel(text, parent);
label->setAlignment(alignment);
label->setWordWrap(true);
const QColor color = gui::utils::get_label_color(QColor(0x5b, 0x5b, 0x5b), QColor(0xa8, 0xa8, 0xa8));
QPalette palette = label->palette();
palette.setColor(QPalette::WindowText, color);
label->setPalette(palette);
label->setProperty("themeRole", QStringLiteral("mutedText"));
return label;
}
@@ -109,13 +106,7 @@ QFrame *make_result_row(const ArchiveInstallResult &result, QWidget *parent) {
status->setAlignment(Qt::AlignCenter);
status->setMinimumWidth(84);
status->setMargin(6);
const QColor status_bg = result.success
? gui::utils::get_label_color(QColor(0xd9, 0xf2, 0xdd), QColor(0x25, 0x5a, 0x2e))
: gui::utils::get_label_color(QColor(0xf7, 0xdc, 0xd9), QColor(0x6d, 0x2a, 0x25));
const QColor status_fg = result.success
? gui::utils::get_label_color(QColor(0x1f, 0x7a, 0x1f), QColor(0xb9, 0xf5, 0xbf))
: gui::utils::get_label_color(QColor(0xa3, 0x2a, 0x1c), QColor(0xff, 0xb1, 0xaa));
status->setStyleSheet(QStringLiteral("border-radius: 10px; padding: 4px 10px; background-color: %1; color: %2;").arg(status_bg.name(), status_fg.name()));
status->setProperty("severity", result.success ? QStringLiteral("success") : QStringLiteral("error"));
layout->addWidget(status, 0, Qt::AlignTop);
return row;
+29 -61
View File
@@ -58,7 +58,6 @@
#include <SDL3/SDL_gamepad.h>
#include <algorithm>
#include <cmath>
namespace {
@@ -67,22 +66,6 @@ constexpr int STACK_PAGE_MIN_WIDTH = 980;
constexpr int CAPTURE_TIMEOUT_MS = 5'000;
constexpr int16_t AXIS_DEADZONE = 16000;
QColor mix_colors(const QColor &a, const QColor &b, float amount_b) {
const float amount_a = 1.0f - amount_b;
return QColor(
std::lround(a.red() * amount_a + b.red() * amount_b),
std::lround(a.green() * amount_a + b.green() * amount_b),
std::lround(a.blue() * amount_a + b.blue() * amount_b),
std::lround(a.alpha() * amount_a + b.alpha() * amount_b));
}
QIcon theme_icon(QWidget *widget, const QString &theme_name, QStyle::StandardPixmap fallback) {
QIcon icon = QIcon::fromTheme(theme_name);
if (icon.isNull() && widget)
icon = widget->style()->standardIcon(fallback);
return icon;
}
QPixmap render_vita_svg(const QColor &color) {
QSvgRenderer renderer(QStringLiteral(":/icons/PSV_Layout.svg"));
QPixmap px(renderer.defaultSize() * 4);
@@ -112,51 +95,22 @@ QLabel *make_wrapped_label(const QString &text, QWidget *parent = nullptr) {
return label;
}
QToolButton *make_header_tool_button(QWidget *parent, const QString &text, const QString &theme_name,
QStyle::StandardPixmap fallback) {
Q_UNUSED(theme_name);
Q_UNUSED(fallback);
QToolButton *make_header_tool_button(QWidget *parent, const QString &text) {
auto *button = new QToolButton(parent);
button->setText(text);
button->setCheckable(true);
button->setToolButtonStyle(Qt::ToolButtonTextOnly);
button->setAutoRaise(true);
button->setProperty("modeButton", true);
return button;
}
void refresh_widget_style(QWidget *widget) {
if (!widget)
return;
widget->style()->unpolish(widget);
widget->style()->polish(widget);
widget->update();
}
void style_mode_button(QToolButton *button) {
if (!button)
return;
const QPalette pal = button->palette();
const QColor base = pal.color(QPalette::Base);
const QColor hover = mix_colors(base, pal.color(QPalette::Highlight), 0.10f);
const QColor checked = mix_colors(base, pal.color(QPalette::Highlight), 0.18f);
button->setStyleSheet(QStringLiteral(
"QToolButton {"
" background: transparent;"
" border: none;"
" border-radius: 10px;"
" padding: 6px 12px;"
" }"
"QToolButton:hover { background-color: %1; }"
"QToolButton:checked { background-color: %2; }")
.arg(hover.name(QColor::HexArgb), checked.name(QColor::HexArgb)));
}
void style_binding_button(QPushButton *button, bool capturing = false) {
if (!button)
return;
button->setProperty("bindButton", true);
button->setProperty("captureActive", capturing);
refresh_widget_style(button);
gui::utils::refresh_theme_state(button);
}
QPushButton *make_bind_button() {
@@ -375,6 +329,7 @@ ControlsDialog::ControlsDialog(EmuEnvState &emuenv,
, m_gui_settings(std::move(gui_settings))
, m_ui(std::make_unique<Ui::ControlsDialog>()) {
m_ui->setupUi(this);
setObjectName(QStringLiteral("controls_dialog"));
setWindowTitle(tr("Controls"));
setWindowModality(Qt::NonModal);
setWindowFlag(Qt::Window, true);
@@ -425,6 +380,13 @@ ControlsDialog::~ControlsDialog() {
cancel_controller_capture();
}
void ControlsDialog::changeEvent(QEvent *event) {
if (event->type() == QEvent::PaletteChange || event->type() == QEvent::StyleChange)
refresh_controller_art();
QDialog::changeEvent(event);
}
void ControlsDialog::closeEvent(QCloseEvent *event) {
if (m_gui_settings) {
m_gui_settings->set_value(gui::cd_geometry, saveGeometry(), false);
@@ -542,12 +504,8 @@ void ControlsDialog::build_keyboard_page() {
header_layout->addWidget(m_keyboard_page.lbl_summary, 1);
auto *view_layout = new QHBoxLayout();
m_keyboard_page.btn_primary = make_header_tool_button(header_group, tr("Primary"),
QStringLiteral("input-keyboard"), QStyle::SP_ArrowRight);
m_keyboard_page.btn_alt = make_header_tool_button(header_group, tr("Alternate"),
QStringLiteral("input-keyboard-shortcut"), QStyle::SP_FileDialogDetailedView);
style_mode_button(m_keyboard_page.btn_primary);
style_mode_button(m_keyboard_page.btn_alt);
m_keyboard_page.btn_primary = make_header_tool_button(header_group, tr("Primary"));
m_keyboard_page.btn_alt = make_header_tool_button(header_group, tr("Alternate"));
view_layout->addWidget(m_keyboard_page.btn_primary);
view_layout->addWidget(m_keyboard_page.btn_alt);
header_layout->addLayout(view_layout);
@@ -561,7 +519,7 @@ void ControlsDialog::build_keyboard_page() {
m_keyboard_page.stack = new QStackedWidget(m_keyboard_page.page);
root_layout->addWidget(m_keyboard_page.stack, 1);
const QPixmap vita_pixmap = render_vita_svg(gui::utils::get_foreground_color(this));
const QPixmap vita_pixmap = render_vita_svg(gui::utils::foreground_color(this));
{
QWidget *content_widget = nullptr;
@@ -705,10 +663,8 @@ void ControlsDialog::build_controller_page(int index) {
header_layout->addWidget(t.lbl_summary, 1);
auto *view_layout = new QHBoxLayout();
t.btn_bindings = make_header_tool_button(header_group, tr("Bindings"), QStringLiteral("controller-digital-line"), QStyle::SP_ArrowRight);
t.btn_settings = make_header_tool_button(header_group, tr("Settings"), QStringLiteral("settings-3-line"), QStyle::SP_FileDialogDetailedView);
style_mode_button(t.btn_bindings);
style_mode_button(t.btn_settings);
t.btn_bindings = make_header_tool_button(header_group, tr("Bindings"));
t.btn_settings = make_header_tool_button(header_group, tr("Settings"));
view_layout->addWidget(t.btn_bindings);
view_layout->addWidget(t.btn_settings);
header_layout->addLayout(view_layout);
@@ -750,7 +706,7 @@ void ControlsDialog::build_controller_page(int index) {
top_grid->addWidget(make_button_group(tr("R1"), t.btn_r1), 0, 3);
center_column->addLayout(top_grid);
t.l_controller_image = make_vita_image_label(this, render_vita_svg(gui::utils::get_foreground_color(this)));
t.l_controller_image = make_vita_image_label(this, render_vita_svg(gui::utils::foreground_color(this)));
center_column->addWidget(make_centered_widget(t.l_controller_image));
center_column->addStretch();
@@ -1190,6 +1146,18 @@ void ControlsDialog::refresh_controller_page_labels(int index) {
}
}
void ControlsDialog::refresh_controller_art() {
const QPixmap vita_pixmap = render_vita_svg(gui::utils::foreground_color(this));
if (m_keyboard_page.l_controller_image)
m_keyboard_page.l_controller_image->setPixmap(vita_pixmap);
for (auto &tab : m_ctrl_tabs) {
if (tab.l_controller_image)
tab.l_controller_image->setPixmap(vita_pixmap);
}
}
void ControlsDialog::set_controller_page_enabled(int index, bool enabled) {
auto &tab = m_ctrl_tabs[index];
for (auto &binding : tab.bindings)
+2 -2
View File
@@ -399,7 +399,7 @@ void DebugWidget::on_thread_double_clicked(QTreeWidgetItem *item, int /*column*/
layout->addLayout(form);
auto *reg_label = new QLabel(tr("Registers"), dlg);
reg_label->setStyleSheet(QStringLiteral("font-weight: bold; margin-top: 8px;"));
reg_label->setProperty("themeRole", QStringLiteral("sectionHeader"));
layout->addWidget(reg_label);
auto *reg_tree = new QTreeWidget(dlg);
@@ -432,7 +432,7 @@ void DebugWidget::on_thread_double_clicked(QTreeWidgetItem *item, int /*column*/
layout->addWidget(reg_tree);
auto *stack_label = new QLabel(tr("Stack"), dlg);
stack_label->setStyleSheet(QStringLiteral("font-weight: bold; margin-top: 8px;"));
stack_label->setProperty("themeRole", QStringLiteral("sectionHeader"));
layout->addWidget(stack_label);
auto *stack_tree = new QTreeWidget(dlg);
-19
View File
@@ -18,8 +18,6 @@
#include <gui-qt/gui_settings.h>
#include <QDir>
#include <QDirIterator>
GuiSettings::GuiSettings(const QString &settings_dir, QObject *parent)
: GuiSettingsBase(parent) {
m_settings_dir = QDir(settings_dir);
@@ -30,20 +28,3 @@ GuiSettings::GuiSettings(const QString &settings_dir, QObject *parent)
m_settings_dir.filePath(gui::Settings + QStringLiteral(".ini")),
QSettings::IniFormat, parent);
}
QStringList GuiSettings::get_stylesheet_entries() const {
const QStringList name_filter = QStringList(QStringLiteral("*.qss"));
QStringList res;
{
QDirIterator it(m_settings_dir.absolutePath(), name_filter, QDir::Files);
while (it.hasNext()) {
it.next();
res.append(it.fileInfo().completeBaseName());
}
}
res.removeDuplicates();
res.sort();
return res;
}
+159 -77
View File
@@ -17,11 +17,13 @@
#include <gui-qt/log_widget.h>
#include <gui-qt/qt_utils.h>
#include <gui-qt/theme_surface.h>
#include <util/log.h>
#include <spdlog/common.h>
#include <QElapsedTimer>
#include <QEvent>
#include <QFont>
#include <QFontDatabase>
@@ -34,8 +36,11 @@
#include <QShortcut>
#include <QSize>
#include <QStyle>
#include <QTextBlock>
#include <QTextBlockUserData>
#include <QTextDocument>
#include <QTextEdit>
#include <QTimer>
#include <QToolButton>
#include <QVBoxLayout>
@@ -47,7 +52,9 @@
namespace {
constexpr int SAFE_UNBOUNDED_LOG_BUFFER_SIZE = 50000;
constexpr size_t MAX_LOG_DRAIN_BATCH = 256;
constexpr int LOG_DRAIN_INTERVAL_MS = 10;
constexpr qint64 LOG_DRAIN_TIME_BUDGET_MS = 4;
constexpr size_t MAX_LOG_DRAIN_BATCH = 64;
constexpr size_t MIN_PENDING_LOG_LINES = 512;
constexpr size_t MAX_PENDING_LOG_LINES = 8192;
@@ -57,6 +64,84 @@ struct PendingLogMessage {
size_t repeat_count = 1;
};
class LogBlockData final : public QTextBlockUserData {
public:
explicit LogBlockData(int level_value)
: level(level_value) {
}
int level = 0;
};
struct RenderedLogMessage {
QString msg;
int level = 0;
};
QColor log_level_color(int level) {
using L = spdlog::level::level_enum;
switch (static_cast<L>(level)) {
case L::critical:
return gui::utils::theme_role_color(QStringLiteral("logFatal"));
case L::err:
return gui::utils::theme_role_color(QStringLiteral("logError"));
case L::warn:
return gui::utils::theme_role_color(QStringLiteral("logWarning"));
case L::info:
return gui::utils::theme_role_color(QStringLiteral("logNotice"));
case L::debug:
return gui::utils::theme_role_color(QStringLiteral("logDebug"));
case L::trace:
default:
return gui::utils::theme_role_color(QStringLiteral("logTrace"));
}
}
void apply_block_color(const QTextBlock &block, int level) {
if (!block.isValid())
return;
QTextCursor cursor(block);
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
QTextCharFormat format;
format.setForeground(log_level_color(level));
cursor.mergeCharFormat(format);
}
void append_log_entries(QPlainTextEdit *log, const std::vector<RenderedLogMessage> &entries, bool preserve_scroll = true) {
if (!log || entries.empty())
return;
QScrollBar *bar = log->verticalScrollBar();
const bool at_bottom = !preserve_scroll || bar->value() == bar->maximum();
QTextCursor cursor(log->document());
cursor.movePosition(QTextCursor::End);
cursor.beginEditBlock();
for (const RenderedLogMessage &entry : entries) {
const QString normalized = entry.msg;
const QStringList lines = normalized.split(QLatin1Char('\n'), Qt::KeepEmptyParts);
QTextCharFormat format;
format.setForeground(log_level_color(entry.level));
for (const QString &line : lines) {
cursor.insertText(line, format);
if (QTextBlock block = cursor.block(); block.isValid())
block.setUserData(new LogBlockData(entry.level));
cursor.insertBlock();
}
}
cursor.endEditBlock();
if (at_bottom)
bar->setValue(bar->maximum());
}
QFont make_log_font(const QString &family = {}) {
QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
font.setPointSize(9);
@@ -83,7 +168,10 @@ static struct {
LogWidget::LogWidget(QWidget *parent)
: custom_dock_widget(tr("Log"), parent) {
auto *container = new QWidget(this);
setObjectName(QStringLiteral("logger"));
auto *container = new ThemeSurface(this);
container->setObjectName(QStringLiteral("log_surface"));
auto *layout = new QVBoxLayout(container);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
@@ -93,10 +181,11 @@ LogWidget::LogWidget(QWidget *parent)
m_log->setFont(make_log_font());
m_log->document()->setMaximumBlockCount(effective_buffer_size(1000));
m_log->setObjectName(QStringLiteral("log_frame"));
m_log->viewport()->setObjectName(QStringLiteral("log_frame_viewport"));
layout->addWidget(m_log);
setWidget(container);
m_search_container = new QWidget(m_log);
m_search_container = new ThemeSurface(m_log);
m_search_container->setObjectName(QStringLiteral("log_search_container"));
auto *search_layout = new QHBoxLayout(m_search_container);
search_layout->setContentsMargins(6, 6, 6, 6);
@@ -147,6 +236,10 @@ LogWidget::LogWidget(QWidget *parent)
connect(m_search_next_button, &QToolButton::clicked, this, [this]() { find_text(false); });
connect(m_search_close_button, &QToolButton::clicked, this, &LogWidget::hide_search_bar);
m_drain_timer = new QTimer(this);
m_drain_timer->setSingleShot(true);
connect(m_drain_timer, &QTimer::timeout, this, &LogWidget::drain_pending_messages);
update_search_ui_text();
}
@@ -209,15 +302,15 @@ void LogWidget::attach() {
}
void LogWidget::on_log_message(const QString &msg, int level) {
m_entries.emplace_back(msg, level);
trim_entries();
append_log_entry(msg, level);
}
void LogWidget::drain_pending_messages() {
if (m_drain_timer && m_drain_timer->isActive())
m_drain_timer->stop();
std::deque<PendingLogMessage> batch;
size_t dropped_lines = 0;
bool should_schedule_next = false;
{
const std::lock_guard<std::mutex> lock(s_log_state.mutex);
@@ -228,95 +321,76 @@ void LogWidget::drain_pending_messages() {
batch.push_back(std::move(s_log_state.backlog.front()));
s_log_state.backlog.pop_front();
}
should_schedule_next = !s_log_state.backlog.empty();
s_log_state.drain_posted = should_schedule_next;
s_log_state.drain_posted = false;
}
std::vector<RenderedLogMessage> rendered_entries;
rendered_entries.reserve(batch.size() + (dropped_lines > 0 ? 1 : 0));
if (dropped_lines > 0) {
on_log_message(
QStringLiteral("[Qt log] Dropped %1 queued log lines before they could be rendered.")
rendered_entries.push_back({ QStringLiteral("[Qt log] Dropped %1 queued log lines before they could be rendered.")
.arg(static_cast<qulonglong>(dropped_lines)),
static_cast<int>(spdlog::level::warn));
static_cast<int>(spdlog::level::warn) });
}
if (!batch.empty()) {
m_log->setUpdatesEnabled(false);
for (const auto &entry : batch) {
QString qmsg = QString::fromStdString(entry.msg).trimmed();
QElapsedTimer timer;
timer.start();
size_t consumed = 0;
for (; consumed < batch.size(); ++consumed) {
if (consumed > 0 && timer.elapsed() >= LOG_DRAIN_TIME_BUDGET_MS)
break;
QString qmsg = QString::fromStdString(batch[consumed].msg).trimmed();
if (qmsg.isEmpty())
continue;
if (entry.repeat_count > 1) {
qmsg += QStringLiteral(" [x%1]").arg(static_cast<qulonglong>(entry.repeat_count));
qmsg.replace(QStringLiteral("\r\n"), QStringLiteral("\n"));
qmsg.replace(QLatin1Char('\r'), QLatin1Char('\n'));
if (batch[consumed].repeat_count > 1)
qmsg += QStringLiteral(" [x%1]").arg(static_cast<qulonglong>(batch[consumed].repeat_count));
rendered_entries.push_back({ std::move(qmsg), batch[consumed].level });
}
on_log_message(qmsg, entry.level);
if (consumed < batch.size()) {
const std::lock_guard<std::mutex> lock(s_log_state.mutex);
for (auto it = batch.rbegin(); it != batch.rbegin() + static_cast<std::ptrdiff_t>(batch.size() - consumed); ++it)
s_log_state.backlog.push_front(std::move(*it));
}
if (!rendered_entries.empty()) {
m_log->setUpdatesEnabled(false);
append_log_entries(m_log, rendered_entries);
m_log->setUpdatesEnabled(true);
m_log->viewport()->update();
}
bool should_schedule_next = false;
{
const std::lock_guard<std::mutex> lock(s_log_state.mutex);
should_schedule_next = !s_log_state.backlog.empty();
s_log_state.drain_posted = should_schedule_next;
}
if (m_search_container && m_search_container->isVisible() && m_search_edit && !m_search_edit->text().isEmpty())
update_search_highlights();
if (should_schedule_next)
QMetaObject::invokeMethod(this, &LogWidget::drain_pending_messages, Qt::QueuedConnection);
}
void LogWidget::append_log_entry(const QString &msg, int level, bool preserve_scroll) {
QScrollBar *bar = m_log->verticalScrollBar();
const bool at_bottom = !preserve_scroll || bar->value() == bar->maximum();
QTextCursor cursor = m_log->textCursor();
cursor.movePosition(QTextCursor::End);
QTextCharFormat fmt;
fmt.setForeground(color_for_level(level));
cursor.insertText(msg + "\n", fmt);
if (at_bottom)
bar->setValue(bar->maximum());
}
QColor LogWidget::color_for_level(int level) const {
using L = spdlog::level::level_enum;
switch (static_cast<L>(level)) {
case L::critical:
return gui::utils::get_label_color(
QStringLiteral("log_level_fatal"),
QColor(QStringLiteral("#ff00ff")),
QColor(QStringLiteral("#ff66ff")));
case L::err:
return gui::utils::get_label_color(
QStringLiteral("log_level_error"),
QColor(QStringLiteral("#C02F1D")),
QColor(QStringLiteral("#ff8080")));
case L::warn:
return gui::utils::get_label_color(
QStringLiteral("log_level_warning"),
QColor(QStringLiteral("#BA8745")),
QColor(QStringLiteral("#ffd27f")));
case L::info:
return gui::utils::get_label_color(
QStringLiteral("log_level_notice"),
QColor(Qt::black),
QColor(Qt::white));
default:
return gui::utils::get_label_color(
QStringLiteral("log_level_trace"),
QColor(QStringLiteral("#808080")),
QColor(Qt::white));
if (should_schedule_next && m_drain_timer) {
m_drain_timer->start(LOG_DRAIN_INTERVAL_MS);
}
}
void LogWidget::trim_entries() {
const int max_blocks = m_log->document()->maximumBlockCount();
while (max_blocks > 0 && static_cast<int>(m_entries.size()) > max_blocks)
m_entries.pop_front();
void LogWidget::append_log_entry(const QString &msg, int level, bool preserve_scroll) {
QString normalized = msg.trimmed();
if (normalized.isEmpty())
return;
normalized.replace(QStringLiteral("\r\n"), QStringLiteral("\n"));
normalized.replace(QLatin1Char('\r'), QLatin1Char('\n'));
append_log_entries(m_log, { RenderedLogMessage{ std::move(normalized), level } }, preserve_scroll);
}
void LogWidget::set_log_buffer_size(int size) {
@@ -329,7 +403,6 @@ void LogWidget::set_log_buffer_size(int size) {
s_log_state.backlog.pop_front();
}
}
trim_entries();
if (m_search_edit && !m_search_edit->text().isEmpty())
update_search_highlights();
}
@@ -354,14 +427,23 @@ QString LogWidget::log_font_family() const {
}
void LogWidget::repaint_text_colors() {
if (!m_log)
return;
const QScrollBar *bar = m_log->verticalScrollBar();
const int old_value = bar->value();
const bool at_bottom = old_value == bar->maximum();
m_log->clear();
m_log->setUpdatesEnabled(false);
for (QTextBlock block = m_log->document()->begin(); block.isValid(); block = block.next()) {
const auto *data = static_cast<const LogBlockData *>(block.userData());
if (!data)
continue;
for (const auto &[msg, level] : m_entries)
append_log_entry(msg, level, false);
apply_block_color(block, data->level);
}
m_log->setUpdatesEnabled(true);
m_log->viewport()->update();
if (!at_bottom)
m_log->verticalScrollBar()->setValue(old_value);
@@ -521,7 +603,7 @@ void LogWidget::update_search_ui_icons() {
if (!m_search_prev_button || !m_search_next_button || !m_search_close_button)
return;
const QColor foreground = gui::utils::get_foreground_color(m_search_container);
const QColor foreground = gui::utils::foreground_color(m_search_container);
const QString theme_suffix = foreground.lightnessF() > 0.5f
? QStringLiteral("light")
: QStringLiteral("dark");
+214 -126
View File
@@ -39,10 +39,12 @@
#include <gui-qt/qt_utils.h>
#include <gui-qt/settings_dialog.h>
#include <gui-qt/settings_dialog_tooltips.h>
#include <gui-qt/stylesheets.h>
#include <gui-qt/theme_manager.h>
#include <gui-qt/theme_surface.h>
#include <gui-qt/trophy_collection_dialog.h>
#include <gui-qt/update_manager.h>
#include <gui-qt/user_management_dialog.h>
#include <gui-qt/vita_themes_dialog.h>
#include <gui-qt/welcome_dialog.h>
#include <app/functions.h>
@@ -90,7 +92,7 @@
#include <QMessageBox>
#include <QMimeData>
#include <QOpenGLContext>
#include <QPalette>
#include <QPainter>
#include <QProgressDialog>
#include <QPushButton>
#include <QSlider>
@@ -99,6 +101,7 @@
#include <QStyleFactory>
#include <QStyleHints>
#include <QToolBar>
#include <QVariantAnimation>
#include <QWidgetAction>
#include <QtResource>
@@ -127,35 +130,6 @@ void copy_config_for_edit(Config &dst, const Config &src) {
dst.current_config = src.current_config;
}
QPalette make_dark_palette() {
QPalette palette;
const QColor selection_highlight(255, 255, 255, 33);
const QColor disabled_selection_highlight(255, 255, 255, 20);
palette.setColor(QPalette::Window, QColor(45, 45, 48));
palette.setColor(QPalette::WindowText, QColor(243, 243, 243));
palette.setColor(QPalette::Base, QColor(30, 30, 30));
palette.setColor(QPalette::AlternateBase, QColor(38, 38, 41));
palette.setColor(QPalette::ToolTipBase, QColor(45, 45, 48));
palette.setColor(QPalette::ToolTipText, QColor(243, 243, 243));
palette.setColor(QPalette::Text, QColor(243, 243, 243));
palette.setColor(QPalette::Button, QColor(45, 45, 48));
palette.setColor(QPalette::ButtonText, QColor(243, 243, 243));
palette.setColor(QPalette::BrightText, Qt::white);
palette.setColor(QPalette::Link, QColor(66, 156, 255));
palette.setColor(QPalette::Highlight, selection_highlight);
palette.setColor(QPalette::Inactive, QPalette::Highlight, selection_highlight);
palette.setColor(QPalette::Disabled, QPalette::Highlight, disabled_selection_highlight);
palette.setColor(QPalette::HighlightedText, Qt::white);
palette.setColor(QPalette::Inactive, QPalette::HighlightedText, Qt::white);
palette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(200, 200, 200));
palette.setColor(QPalette::PlaceholderText, QColor(160, 160, 160));
palette.setColor(QPalette::Disabled, QPalette::Text, QColor(115, 115, 115));
palette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(115, 115, 115));
palette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(115, 115, 115));
return palette;
}
void update_status_button_accent(QPushButton *button, const QString &accent) {
if (!button)
return;
@@ -165,11 +139,8 @@ void update_status_button_accent(QPushButton *button, const QString &accent) {
if (button->property("accent").toString() != accent) {
button->setProperty("accent", accent);
button->style()->unpolish(button);
button->style()->polish(button);
gui::utils::refresh_theme_state(button);
}
button->update();
}
} // namespace
@@ -212,9 +183,6 @@ void MainWindow::register_auxiliary_window(QWidget *window) {
return;
m_auxiliary_windows.push_back(window);
connect(window, &QObject::destroyed, this, [this](QObject *) {
prune_auxiliary_windows();
});
}
void MainWindow::prune_auxiliary_windows() {
@@ -287,6 +255,39 @@ void MainWindow::closeEvent(QCloseEvent *event) {
QMainWindow::closeEvent(event);
}
void MainWindow::paintEvent(QPaintEvent *event) {
QMainWindow::paintEvent(event);
if (m_theme_background_scaled_pixmaps.empty())
return;
if (m_theme_background_scaled_size != size())
rescale_theme_background_pixmaps();
if (m_theme_background_scaled_pixmaps.empty())
return;
const int current_index = std::clamp(m_theme_background_current_index, 0,
static_cast<int>(m_theme_background_scaled_pixmaps.size()) - 1);
QPainter painter(this);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
const auto draw_index = [this, &painter](const int index, const qreal opacity) {
if (index < 0 || index >= static_cast<int>(m_theme_background_scaled_pixmaps.size()) || opacity <= 0.0)
return;
painter.setOpacity(opacity);
painter.drawPixmap(rect(), m_theme_background_scaled_pixmaps[static_cast<std::size_t>(index)]);
};
if (m_theme_background_next_index >= 0 && m_theme_background_next_index < static_cast<int>(m_theme_background_scaled_pixmaps.size())) {
draw_index(current_index, 1.0 - m_theme_background_fade_progress);
draw_index(m_theme_background_next_index, m_theme_background_fade_progress);
} else {
draw_index(current_index, 1.0);
}
}
void MainWindow::save_window_state() {
if (!m_gui_settings)
return;
@@ -374,6 +375,7 @@ void MainWindow::restore_window_state() {
void MainWindow::initialize() {
Q_INIT_RESOURCE(resources);
m_ui->setupUi(this);
setObjectName(QStringLiteral("main_window"));
this->resize(1280, 720);
this->setWindowIcon(QIcon(":/Vita3K.png"));
@@ -386,11 +388,19 @@ void MainWindow::initialize() {
init_current_user();
m_dock_host = new QMainWindow(this);
auto *shell_frame = new ThemeSurface(this);
shell_frame->setObjectName(QStringLiteral("mw_frame"));
auto *shell_layout = new QVBoxLayout(shell_frame);
shell_layout->setContentsMargins(0, 0, 0, 0);
shell_layout->setSpacing(0);
setCentralWidget(shell_frame);
m_dock_host = new QMainWindow(shell_frame);
m_dock_host->setObjectName(QStringLiteral("mw_dock_host"));
m_dock_host->setWindowFlags(Qt::Widget);
m_dock_host->setContextMenuPolicy(Qt::PreventContextMenu);
m_dock_host->setDockNestingEnabled(true);
setCentralWidget(m_dock_host);
shell_layout->addWidget(m_dock_host);
m_apps_list_widget = new AppsList(emuenv, m_dock_host);
m_apps_list_widget->refresh();
@@ -423,6 +433,9 @@ void MainWindow::initialize() {
connect(m_ui->trophy_collection_action, &QAction::triggered,
this, &MainWindow::open_trophy_collection);
connect(m_ui->vita_themes_action, &QAction::triggered,
this, &MainWindow::open_vita_themes);
connect(m_ui->user_management_action, &QAction::triggered,
this, &MainWindow::open_user_management);
@@ -500,7 +513,35 @@ void MainWindow::initialize() {
apply_log_gui_settings();
init_default_style();
m_theme_manager = std::make_unique<ThemeManager>(m_gui_settings);
m_theme_manager->set_pref_path(emuenv.pref_path);
m_theme_manager->set_vita_themes_root(emuenv.pref_path / "ux0/theme");
connect(m_theme_manager.get(), &ThemeManager::theme_state_changed, this, &MainWindow::repaint_gui);
connect(m_theme_manager.get(), &ThemeManager::theme_state_changed, this, &MainWindow::apply_theme_background_presentation);
m_theme_background_cycle_timer = new QTimer(this);
connect(m_theme_background_cycle_timer, &QTimer::timeout, this, &MainWindow::advance_theme_background_presentation);
m_theme_background_fade_animation = new QVariantAnimation(this);
m_theme_background_fade_animation->setDuration(900);
m_theme_background_fade_animation->setStartValue(0.0);
m_theme_background_fade_animation->setEndValue(1.0);
m_theme_background_fade_animation->setEasingCurve(QEasingCurve::InOutSine);
connect(m_theme_background_fade_animation, &QVariantAnimation::valueChanged, this, [this](const QVariant &value) {
m_theme_background_fade_progress = value.toReal();
update();
});
connect(m_theme_background_fade_animation, &QVariantAnimation::finished, this, [this] {
if (m_theme_background_next_index >= 0)
m_theme_background_current_index = m_theme_background_next_index;
m_theme_background_next_index = -1;
m_theme_background_fade_progress = 0.0;
if (m_theme_background_cycle_timer && m_theme_background_scaled_pixmaps.size() > 1)
m_theme_background_cycle_timer->start(m_theme_background_cycle_interval_ms);
update();
});
init_first_run_stylesheet();
apply_stylesheet();
@@ -580,6 +621,10 @@ void MainWindow::on_install_license_triggered() {
void MainWindow::on_install_finished() {
m_apps_list_widget->refresh(true);
if (m_theme_manager)
m_theme_manager->refresh_vita_theme_catalog();
if (m_vita_themes_dialog)
m_vita_themes_dialog->reload();
}
bool MainWindow::prompt_startup_warnings() {
@@ -747,9 +792,6 @@ void MainWindow::on_controls_action_triggered() {
if (!m_controls_dialog) {
m_controls_dialog = new ControlsDialog(emuenv, m_gui_settings);
register_auxiliary_window(m_controls_dialog);
connect(m_controls_dialog, &QObject::destroyed, this, [this] {
m_controls_dialog = nullptr;
});
}
present_tool_window(m_controls_dialog);
}
@@ -761,13 +803,18 @@ void MainWindow::on_check_for_updates_action_triggered() {
void MainWindow::open_settings(int tab_index) {
if (!m_settings_dialog) {
m_settings_dialog = new SettingsDialog(emuenv, m_gui_settings, {}, tab_index);
m_settings_dialog = new SettingsDialog(emuenv, m_gui_settings, *m_theme_manager, {}, tab_index);
register_auxiliary_window(m_settings_dialog);
connect(m_settings_dialog, &SettingsDialog::gui_stylesheet_request, this, &MainWindow::apply_stylesheet);
connect(m_settings_dialog, &SettingsDialog::gui_theme_music_settings_request, this, [this] {
if (m_theme_manager)
m_theme_manager->refresh_vita_theme_bgm();
});
connect(m_settings_dialog, &SettingsDialog::gui_log_settings_request, this, &MainWindow::apply_log_gui_settings);
connect(m_settings_dialog, &SettingsDialog::storage_path_changed, this, [this] {
if (m_apps_list_widget)
m_apps_list_widget->refresh(true);
apply_stylesheet();
});
connect(m_settings_dialog, &SettingsDialog::ui_language_request, this, &MainWindow::apply_ui_language);
connect(m_settings_dialog, &SettingsDialog::restart_game_requested, this, &MainWindow::restart_running_app);
@@ -776,9 +823,6 @@ void MainWindow::open_settings(int tab_index) {
refresh_status_bar();
apply_log_gui_settings();
});
connect(m_settings_dialog, &QObject::destroyed, this, [this] {
m_settings_dialog = nullptr;
});
}
m_settings_dialog->set_storage_path_locked(m_game_window != nullptr);
@@ -904,6 +948,8 @@ std::optional<AppLaunchRequest> MainWindow::boot_game_once(const AppLaunchReques
tr("Could not find app '%1' in apps list.").arg(QString::fromStdString(launch_request.app_path)));
return std::nullopt;
}
if (m_theme_manager)
m_theme_manager->set_vita_theme_bgm_blocked(true);
bool launch_fullscreen = emuenv.cfg.boot_apps_full_screen;
if (m_game_container) {
@@ -941,6 +987,8 @@ std::optional<AppLaunchRequest> MainWindow::boot_game_once(const AppLaunchReques
const auto abort_boot = [&](const QString &msg) {
QMessageBox::critical(this, tr("Error"), msg);
m_app_session.stop(app::AppSessionStopReason::LaunchFailure);
if (m_theme_manager)
m_theme_manager->set_vita_theme_bgm_blocked(false);
if (m_game_window) {
delete m_game_window;
@@ -1156,6 +1204,8 @@ void MainWindow::on_game_closed() {
if (m_settings_dialog)
m_settings_dialog->set_storage_path_locked(false);
if (m_theme_manager && !m_is_app_closing)
m_theme_manager->set_vita_theme_bgm_blocked(false);
refresh_emulation_actions();
if (!m_is_app_closing && m_apps_list_widget)
@@ -1451,13 +1501,19 @@ void MainWindow::open_trophy_collection() {
if (!m_trophy_dialog) {
m_trophy_dialog = new TrophyCollectionDialog(emuenv, m_gui_settings);
register_auxiliary_window(m_trophy_dialog);
connect(m_trophy_dialog, &QObject::destroyed, this, [this] {
m_trophy_dialog = nullptr;
});
}
present_tool_window(m_trophy_dialog);
}
void MainWindow::open_vita_themes() {
if (!m_vita_themes_dialog) {
m_vita_themes_dialog = new VitaThemesDialog(m_gui_settings, *m_theme_manager, this);
register_auxiliary_window(m_vita_themes_dialog);
}
present_tool_window(m_vita_themes_dialog);
}
void MainWindow::open_user_management() {
if (m_game_window) {
return;
@@ -1472,9 +1528,6 @@ void MainWindow::open_user_management() {
if (m_trophy_dialog)
m_trophy_dialog->reload();
});
connect(m_user_mgmt_dialog, &QObject::destroyed, this, [this] {
m_user_mgmt_dialog = nullptr;
});
}
present_tool_window(m_user_mgmt_dialog);
}
@@ -1483,9 +1536,6 @@ void MainWindow::open_debug_widget(int tab) {
if (!m_debug_widget) {
m_debug_widget = new DebugWidget(emuenv, this);
m_debug_widget->setFloating(true);
connect(m_debug_widget, &QObject::destroyed, this, [this] {
m_debug_widget = nullptr;
});
}
m_debug_widget->show_tab(tab);
}
@@ -1537,7 +1587,7 @@ void MainWindow::setup_toolbar() {
connect(m_ui->toolbar_controls, &QAction::triggered,
this, &MainWindow::on_controls_action_triggered);
m_slider_container = new QWidget(this);
m_slider_container = new ThemeSurface(this);
m_slider_container->setObjectName(QStringLiteral("toolbar_icon_size_container"));
auto *slider_layout = new QHBoxLayout(m_slider_container);
slider_layout->setContentsMargins(14, 0, 14, 0);
@@ -1558,6 +1608,7 @@ void MainWindow::setup_toolbar() {
this, &MainWindow::resize_icons);
m_search_bar = new QLineEdit(this);
m_search_bar->setObjectName(QStringLiteral("mw_searchbar"));
m_search_bar->setPlaceholderText(tr("Search..."));
m_search_bar->setFocusPolicy(Qt::ClickFocus);
m_search_bar->setClearButtonEnabled(true);
@@ -1591,20 +1642,6 @@ void MainWindow::repaint_gui() {
update();
}
void MainWindow::init_default_style() {
if (QStyleFactory::keys().contains(QStringLiteral("Fusion"), Qt::CaseInsensitive))
m_default_style = QStringLiteral("Fusion");
if (m_default_style.isEmpty()) {
if (const QStyle *style = QApplication::style())
m_default_style = style->name();
}
if (m_default_style.isEmpty()) {
if (const QStringList styles = QStyleFactory::keys(); !styles.empty())
m_default_style = styles.front();
}
}
void MainWindow::init_first_run_stylesheet() {
if (!m_gui_settings || m_gui_settings->contains_value(gui::m_currentStylesheet))
return;
@@ -1617,69 +1654,116 @@ void MainWindow::init_first_run_stylesheet() {
}
void MainWindow::apply_stylesheet() {
if (!m_gui_settings)
if (!m_theme_manager)
return;
m_theme_manager->ensure_generated_theme_ready(emuenv.pref_path / "ux0/theme");
m_theme_manager->apply_theme({}, true);
}
void MainWindow::apply_theme_background_presentation() {
if (!m_theme_manager) {
clear_theme_background_presentation();
return;
}
const auto state = m_theme_manager->current_applied_theme_state();
if (!state || state->background.image_paths.isEmpty()) {
clear_theme_background_presentation();
return;
}
std::vector<QPixmap> next_pixmaps;
next_pixmaps.reserve(static_cast<std::size_t>(state->background.image_paths.size()));
for (const QString &path : state->background.image_paths) {
QPixmap pixmap(path);
if (!pixmap.isNull())
next_pixmaps.push_back(std::move(pixmap));
}
if (next_pixmaps.empty()) {
clear_theme_background_presentation();
return;
}
if (m_theme_background_cycle_timer)
m_theme_background_cycle_timer->stop();
if (m_theme_background_fade_animation)
m_theme_background_fade_animation->stop();
m_theme_background_original_pixmaps = std::move(next_pixmaps);
m_theme_background_scaled_pixmaps.clear();
m_theme_background_scaled_size = {};
m_theme_background_cycle_interval_ms = state->background.interval_ms;
m_theme_background_current_index = 0;
m_theme_background_next_index = -1;
m_theme_background_fade_progress = 0.0;
rescale_theme_background_pixmaps();
if (m_theme_background_cycle_timer && state->background.animated && m_theme_background_scaled_pixmaps.size() > 1)
m_theme_background_cycle_timer->start(m_theme_background_cycle_interval_ms);
update();
}
void MainWindow::clear_theme_background_presentation() {
if (m_theme_background_cycle_timer)
m_theme_background_cycle_timer->stop();
if (m_theme_background_fade_animation)
m_theme_background_fade_animation->stop();
m_theme_background_original_pixmaps.clear();
m_theme_background_scaled_pixmaps.clear();
m_theme_background_scaled_size = {};
m_theme_background_current_index = 0;
m_theme_background_next_index = -1;
m_theme_background_fade_progress = 0.0;
update();
}
void MainWindow::rescale_theme_background_pixmaps() {
m_theme_background_scaled_pixmaps.clear();
if (m_theme_background_original_pixmaps.empty() || size().isEmpty()) {
m_theme_background_scaled_size = {};
return;
}
m_theme_background_scaled_size = size();
m_theme_background_scaled_pixmaps.reserve(m_theme_background_original_pixmaps.size());
for (const QPixmap &pixmap : m_theme_background_original_pixmaps) {
m_theme_background_scaled_pixmaps.push_back(pixmap.scaled(
m_theme_background_scaled_size,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
}
}
void MainWindow::advance_theme_background_presentation() {
if (!m_theme_background_fade_animation || m_theme_background_scaled_pixmaps.size() < 2)
return;
const QString stylesheet_name = m_gui_settings->get_value(gui::m_currentStylesheet).toString();
if (QStyle *style = QStyleFactory::create(m_default_style)) {
QApplication::setStyle(style);
QApplication::setPalette(style->standardPalette());
if (!isVisible() || isMinimized()) {
if (m_theme_background_cycle_timer)
m_theme_background_cycle_timer->start(1000);
return;
}
if (stylesheet_name.isEmpty() || stylesheet_name == gui::LightStylesheet) {
LOG_INFO("Using light stylesheet");
if (QStyle *fusion = QStyleFactory::create(QStringLiteral("Fusion"))) {
QApplication::setStyle(fusion);
QApplication::setPalette(fusion->standardPalette());
}
qApp->setStyleSheet(gui::stylesheets::light_style_sheet);
} else if (stylesheet_name == gui::DarkStylesheet) {
LOG_INFO("Using dark stylesheet");
if (QStyle *fusion = QStyleFactory::create(QStringLiteral("Fusion"))) {
QApplication::setStyle(fusion);
QApplication::setPalette(fusion->standardPalette());
}
QApplication::setPalette(make_dark_palette());
qApp->setStyleSheet(gui::stylesheets::dark_style_sheet);
} else if (stylesheet_name == gui::NoStylesheet) {
LOG_INFO("Using empty stylesheet");
qApp->setStyleSheet(QStringLiteral("/* none */"));
} else {
const QString settings_dir = m_gui_settings->get_settings_dir();
const QString stylesheet_path = settings_dir + QStringLiteral("/") + stylesheet_name + QStringLiteral(".qss");
if (m_theme_background_fade_animation->state() == QAbstractAnimation::Running)
return;
if (QFile file(stylesheet_path); file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QString qss = file.readAll();
file.close();
qss.replace(
QStringLiteral("url(\"gui-configs/"),
QStringLiteral("url(\"") + settings_dir + QStringLiteral("/"));
LOG_INFO("Loaded stylesheet '{}' from {}", stylesheet_name.toUtf8().constData(), stylesheet_path.toUtf8().constData());
qApp->setStyleSheet(qss);
} else {
LOG_ERROR("Could not find stylesheet '{}', falling back to light", stylesheet_name.toUtf8().constData());
qApp->setStyleSheet(gui::stylesheets::light_style_sheet);
}
}
repaint_gui();
m_theme_background_next_index = (m_theme_background_current_index + 1)
% static_cast<int>(m_theme_background_scaled_pixmaps.size());
m_theme_background_fade_progress = 0.0;
m_theme_background_fade_animation->start();
}
void MainWindow::repaint_toolbar_icons() {
auto *tb = m_ui->toolBar;
const QColor fg = gui::utils::get_label_color(
QStringLiteral("toolbar_icon_color"),
QColor(QStringLiteral("#5b5b5b")),
QColor(QStringLiteral("#f3f3f3")));
const QColor fg = gui::utils::toolbar_icon_tint();
std::map<QIcon::Mode, QColor> new_colors;
new_colors[QIcon::Normal] = gui::utils::get_label_color(fg, fg);
new_colors[QIcon::Disabled] = gui::utils::get_label_color(Qt::gray, Qt::darkGray);
new_colors[QIcon::Active] = gui::utils::get_label_color(fg, fg);
new_colors[QIcon::Selected] = gui::utils::get_label_color(fg, fg);
new_colors[QIcon::Normal] = fg;
new_colors[QIcon::Disabled] = gui::utils::muted_text_color();
new_colors[QIcon::Active] = fg;
new_colors[QIcon::Selected] = fg;
const auto icon = [&new_colors](const QString &path) {
return gui::utils::get_colorized_icon(QIcon(path), Qt::black, new_colors);
@@ -1776,9 +1860,13 @@ void MainWindow::on_context_menu_requested(const QPoint &global_pos, const std::
});
connect(&menu, &AppsListContextMenu::open_settings_requested,
this, [this](const std::string &app_path, int tab) {
auto *dlg = new SettingsDialog(emuenv, m_gui_settings, app_path, tab);
auto *dlg = new SettingsDialog(emuenv, m_gui_settings, *m_theme_manager, app_path, tab);
register_auxiliary_window(dlg);
connect(dlg, &SettingsDialog::gui_stylesheet_request, this, &MainWindow::apply_stylesheet);
connect(dlg, &SettingsDialog::gui_theme_music_settings_request, this, [this] {
if (m_theme_manager)
m_theme_manager->refresh_vita_theme_bgm();
});
connect(dlg, &SettingsDialog::gui_log_settings_request, this, &MainWindow::apply_log_gui_settings);
connect(dlg, &SettingsDialog::ui_language_request, this, &MainWindow::apply_ui_language);
connect(dlg, &SettingsDialog::restart_game_requested, this, &MainWindow::restart_running_app);
@@ -1807,7 +1895,7 @@ void MainWindow::resize_icons(int index) {
void MainWindow::setup_status_bar() {
auto *sb = statusBar();
sb->setObjectName(QStringLiteral("emulatorStatusBar"));
sb->setObjectName(QStringLiteral("emulator_status_bar"));
m_renderer_button = new QPushButton(this);
m_renderer_button->setObjectName(QStringLiteral("status_button"));
+6
View File
@@ -72,6 +72,7 @@
<property name="title">
<string>Manage</string>
</property>
<addaction name="vita_themes_action"/>
<addaction name="trophy_collection_action"/>
<addaction name="user_management_action"/>
</widget>
@@ -241,6 +242,11 @@
<string>Trophy Collection</string>
</property>
</action>
<action name="vita_themes_action">
<property name="text">
<string>Vita Themes</string>
</property>
</action>
<action name="user_management_action">
<property name="text">
<string>User Management</string>
+109 -22
View File
@@ -20,6 +20,8 @@
#include <QApplication>
#include <QCheckBox>
#include <QDesktopServices>
#include <QDir>
#include <QHash>
#include <QIcon>
#include <QImage>
#include <QLabel>
@@ -28,7 +30,9 @@
#include <QPalette>
#include <QPixmap>
#include <QPushButton>
#include <QRegularExpression>
#include <QRgb>
#include <QStyle>
#include <QStyleHints>
#include <QUrl>
#include <QWidget>
@@ -37,6 +41,22 @@
namespace gui::utils {
namespace {
QColor default_label_color(QPalette::ColorRole color_role) {
QLabel dummy;
dummy.ensurePolished();
return dummy.palette().color(color_role);
}
QHash<QString, QColor> s_theme_role_colors;
QString theme_role_cache_key(const QString &role, QPalette::ColorRole color_role) {
return role + QLatin1Char(':') + QString::number(static_cast<int>(color_role));
}
} // namespace
void open_dir(const fs::path &path) {
QDesktopServices::openUrl(QUrl::fromLocalFile(to_qt_path(path)));
}
@@ -49,40 +69,107 @@ QString to_qt_path(const fs::path &path) {
return QString::fromStdWString(path.wstring());
}
QColor get_foreground_color(QWidget *widget) {
QString sanitize_relative_path_reference(const QString &path) {
QString normalized = path.trimmed();
normalized.replace(QLatin1Char('\\'), QLatin1Char('/'));
if (normalized.isEmpty())
return {};
static const QRegularExpression windows_drive_prefix(QStringLiteral(R"(^[A-Za-z]:/)"));
static const QRegularExpression url_scheme_prefix(
QStringLiteral(R"(^[A-Za-z][A-Za-z0-9+.-]*:)"),
QRegularExpression::CaseInsensitiveOption);
if (windows_drive_prefix.match(normalized).hasMatch() || url_scheme_prefix.match(normalized).hasMatch())
return {};
QString relative = normalized;
while (relative.startsWith(QStringLiteral("./")))
relative.remove(0, 2);
while (relative.startsWith(QLatin1Char('/')))
relative.remove(0, 1);
relative = QDir::cleanPath(relative);
if (relative.isEmpty() || relative == QStringLiteral(".")
|| relative == QStringLiteral("..")
|| relative.startsWith(QStringLiteral("../"))) {
return {};
}
return relative;
}
QString resolve_relative_path_in_root(const QString &relative_path, const QString &root_path) {
if (relative_path.isEmpty() || root_path.isEmpty())
return {};
const QDir root_dir(QDir::cleanPath(root_path));
const QString resolved_path = QDir::cleanPath(root_dir.absoluteFilePath(relative_path));
const QString root_relative = QDir::cleanPath(root_dir.relativeFilePath(resolved_path));
if (root_relative == QStringLiteral("..") || root_relative.startsWith(QStringLiteral("../")))
return {};
return QDir::fromNativeSeparators(resolved_path);
}
QColor foreground_color(QWidget *widget) {
if (widget) {
widget->ensurePolished();
return widget->palette().color(QPalette::ColorRole::WindowText);
}
return default_label_color(QPalette::ColorRole::WindowText);
}
QColor theme_role_color(
const QString &role,
QPalette::ColorRole color_role) {
const QString cache_key = theme_role_cache_key(role, color_role);
if (const auto it = s_theme_role_colors.constFind(cache_key); it != s_theme_role_colors.cend())
return *it;
QLabel dummy;
dummy.setProperty("themeRole", role);
dummy.ensurePolished();
return dummy.palette().color(QPalette::ColorRole::WindowText);
const QColor color = dummy.palette().color(color_role);
s_theme_role_colors.insert(cache_key, color);
return color;
}
QString format_help_html(const QString &title, const QString &body) {
QString escaped = body.toHtmlEscaped();
escaped.replace(QLatin1Char('\n'), QStringLiteral("<br>"));
return QStringLiteral(
"<div style=\"margin:0;\"><strong>%1</strong></div>"
"<hr style=\"margin:1px 0 3px 0; border:0; border-top:1px solid rgba(255,255,255,0.28);\">"
"<div style=\"margin:0;\">%2</div>")
.arg(title.toHtmlEscaped(), escaped);
}
void invalidate_theme_cache() {
s_theme_role_colors.clear();
}
QColor muted_text_color() {
return theme_role_color(QStringLiteral("mutedText"));
}
QColor toolbar_icon_tint() {
return theme_role_color(QStringLiteral("toolbarIconTint"));
}
void refresh_theme_state(QWidget *widget) {
if (!widget)
return;
widget->style()->unpolish(widget);
widget->style()->polish(widget);
widget->update();
}
bool dark_mode_active() {
return QApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark;
}
QColor get_label_color(
const QString &object_name,
const QColor &fallback_light,
const QColor &fallback_dark,
QPalette::ColorRole color_role) {
if (!qApp || qApp->styleSheet().isEmpty() || !qApp->styleSheet().contains(object_name))
return dark_mode_active() ? fallback_dark : fallback_light;
QLabel dummy;
dummy.setObjectName(object_name);
dummy.ensurePolished();
return dummy.palette().color(color_role);
}
QColor get_label_color(
const QColor &fallback_light,
const QColor &fallback_dark) {
return dark_mode_active() ? fallback_dark : fallback_light;
}
QIcon get_colorized_icon(const QIcon &icon,
const QColor &source_color,
const std::map<QIcon::Mode, QColor> &new_colors) {
+68 -29
View File
@@ -29,6 +29,7 @@
#include <gui-qt/qt_utils.h>
#include <gui-qt/settings_dialog.h>
#include <gui-qt/settings_dialog_tooltips.h>
#include <gui-qt/theme_manager.h>
#include <io/state.h>
#include <kernel/state.h>
#include <renderer/functions.h>
@@ -59,19 +60,12 @@
#include <QTabBar>
#include <QTextDocument>
#include <QVBoxLayout>
#include <QtGlobal>
#include <algorithm>
namespace {
QString format_help_html(const QString &title, const QString &body) {
QString escaped = body.toHtmlEscaped();
escaped.replace(QLatin1Char('\n'), QStringLiteral("<br>"));
return QStringLiteral(
"<div style=\"margin:0;\"><strong>%1</strong></div>"
"<hr style=\"margin:1px 0 3px 0; border:0; border-top:1px solid rgba(255,255,255,0.28);\">"
"<div style=\"margin:0;\">%2</div>")
.arg(title.toHtmlEscaped(), escaped);
}
int first_visible_category(const QListWidget *list, int preferred) {
const int count = list->count();
if (preferred >= 0 && preferred < count) {
@@ -91,11 +85,13 @@ int first_visible_category(const QListWidget *list, int preferred) {
SettingsDialog::SettingsDialog(EmuEnvState &emuenv,
std::shared_ptr<GuiSettings> gui_settings,
ThemeManager &theme_manager,
const std::string &app_path,
int initial_tab)
: QDialog(nullptr)
, emuenv(emuenv)
, m_gui_settings(std::move(gui_settings))
, m_theme_manager(theme_manager)
, m_tooltips(std::make_unique<SettingsDialogTooltips>(this))
, m_app_path(app_path)
, m_initial_tab(initial_tab)
@@ -123,7 +119,12 @@ SettingsDialog::SettingsDialog(EmuEnvState &emuenv,
}
if (!m_gui_settings || !restoreGeometry(m_gui_settings->get_value(gui::sd_geometry).toByteArray()))
resize(780, 676);
connect(&m_theme_manager, &ThemeManager::theme_state_changed, this, &SettingsDialog::refresh_themes);
connect(&m_theme_manager, &ThemeManager::theme_catalog_changed, this, &SettingsDialog::refresh_themes);
m_ui->settingsCategory->setSpacing(0);
m_ui->modules_list->setSelectionMode(QAbstractItemView::NoSelection);
m_ui->modules_list->setFocusPolicy(Qt::NoFocus);
m_ui->modules_list->setAlternatingRowColors(true);
for (int i = 0; i < m_ui->tab_widget_settings->count(); ++i) {
auto *item = new QListWidgetItem(m_ui->tab_widget_settings->tabText(i), m_ui->settingsCategory);
@@ -169,6 +170,7 @@ SettingsDialog::SettingsDialog(EmuEnvState &emuenv,
m_ui->show_mode->setVisible(false);
m_ui->demo_mode->setVisible(false);
m_ui->gb_theme_music_section->setVisible(false);
m_ui->boot_apps_fullscreen->setVisible(false);
m_ui->show_live_area_screen->setVisible(false);
m_ui->show_compile_shaders->setVisible(false);
@@ -198,6 +200,13 @@ SettingsDialog::SettingsDialog(EmuEnvState &emuenv,
bb->button(QDialogButtonBox::Apply)->setEnabled(false);
}
void SettingsDialog::refresh_themes() {
const QString preferred_name = m_ui->combo_stylesheets
? m_ui->combo_stylesheets->currentData().toString()
: m_current_stylesheet;
add_stylesheets(preferred_name);
}
SettingsDialog::~SettingsDialog() {
}
@@ -360,6 +369,17 @@ void SettingsDialog::load_config() {
m_ui->audio_volume->setValue(m_config.audio_volume);
m_ui->audio_volume_label->setText(tr("Current volume: %1%").arg(m_config.audio_volume));
const bool theme_music_enabled = m_gui_settings
? m_gui_settings->get_value(gui::vt_bgmEnabled).toBool()
: gui::vt_bgmEnabled.def.toBool();
const int theme_music_volume = std::clamp(
m_gui_settings
? m_gui_settings->get_value(gui::vt_bgmVolume).toInt()
: gui::vt_bgmVolume.def.toInt(),
0, 100);
m_ui->theme_music_enable->setChecked(theme_music_enabled);
m_ui->theme_music_volume->setValue(theme_music_volume);
m_ui->theme_music_volume_label->setText(tr("Theme music volume: %1%").arg(theme_music_volume));
m_ui->ngs_enable->setChecked(m_config.ngs_enable);
@@ -707,6 +727,12 @@ bool SettingsDialog::commit_changes(bool close_after) {
const bool previous_windows_rounded_corners = update_gui_settings
? m_gui_settings->get_value(gui::gw_roundedCorners).toBool()
: false;
const bool previous_theme_music_enabled = update_gui_settings
? m_gui_settings->get_value(gui::vt_bgmEnabled).toBool()
: gui::vt_bgmEnabled.def.toBool();
const int previous_theme_music_volume = update_gui_settings
? m_gui_settings->get_value(gui::vt_bgmVolume).toInt()
: gui::vt_bgmVolume.def.toInt();
if (storage_path_changed && !apply_pending_storage_path())
return false;
@@ -737,8 +763,19 @@ bool SettingsDialog::commit_changes(bool close_after) {
m_gui_settings->set_value(gui::mw_warnAdminPrivileges, m_ui->warn_admin_privileges->isChecked());
if (update_gui_settings && previous_windows_rounded_corners != m_ui->windows_rounded_corners->isChecked())
m_gui_settings->set_value(gui::gw_roundedCorners, m_ui->windows_rounded_corners->isChecked());
bool theme_music_settings_changed = false;
if (update_gui_settings && previous_theme_music_enabled != m_ui->theme_music_enable->isChecked()) {
m_gui_settings->set_value(gui::vt_bgmEnabled, m_ui->theme_music_enable->isChecked(), false);
theme_music_settings_changed = true;
}
if (update_gui_settings && previous_theme_music_volume != m_ui->theme_music_volume->value()) {
m_gui_settings->set_value(gui::vt_bgmVolume, m_ui->theme_music_volume->value(), false);
theme_music_settings_changed = true;
}
if (log_settings_changed)
Q_EMIT gui_log_settings_request();
if (theme_music_settings_changed)
Q_EMIT gui_theme_music_settings_request();
emuenv.kernel.debugger.log_imports = m_ui->log_imports->isChecked();
emuenv.kernel.debugger.log_exports = m_ui->log_exports->isChecked();
@@ -756,7 +793,7 @@ void SettingsDialog::populate_modules_list() {
const auto modules = config::get_modules_list(emuenv.pref_path, m_config.lle_modules);
for (const auto &[name, enabled] : modules) {
auto *item = new QListWidgetItem(QString::fromStdString(name), m_ui->modules_list);
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setFlags((item->flags() | Qt::ItemIsUserCheckable) & ~Qt::ItemIsSelectable);
item->setCheckState(enabled ? Qt::Checked : Qt::Unchecked);
}
update_modules_list_enabled();
@@ -951,6 +988,9 @@ void SettingsDialog::setup_connections() {
connect(m_ui->audio_volume, &QSlider::valueChanged, this, [this](int val) {
m_ui->audio_volume_label->setText(tr("Current volume: %1%").arg(val));
});
connect(m_ui->theme_music_volume, &QSlider::valueChanged, this, [this](int val) {
m_ui->theme_music_volume_label->setText(tr("Theme music volume: %1%").arg(val));
});
connect(m_ui->perf_overlay_enabled, &QCheckBox::toggled, this, [this](bool on) {
m_ui->label_perf_detail->setEnabled(on);
@@ -1122,6 +1162,8 @@ void SettingsDialog::setup_connections() {
// Audio
{ m_ui->audio_backend_box, tr("Audio Backend"), m_tooltips->audio_backend },
{ m_ui->audio_volume, tr("Volume"), m_tooltips->audio_volume },
{ m_ui->theme_music_enable, tr("Enable Vita Theme Background Music"), m_tooltips->theme_music_enable },
{ m_ui->theme_music_volume, tr("Theme Music Volume"), m_tooltips->theme_music_volume },
{ m_ui->ngs_enable, tr("Enable NGS Support"), m_tooltips->ngs_enable },
// Camera
{ m_ui->front_camera_box, tr("Front Camera Source"), m_tooltips->front_camera },
@@ -1243,9 +1285,9 @@ void SettingsDialog::set_description(QWidget *tab, const QString &title_override
}();
if (text == m_tooltips->default_description) {
m_ui->helpText->setHtml(format_help_html(title, m_tooltips->category_summary(static_cast<SettingsTab>(index))));
m_ui->helpText->setHtml(gui::utils::format_help_html(title, m_tooltips->category_summary(static_cast<SettingsTab>(index))));
} else {
m_ui->helpText->setHtml(format_help_html(title, text));
m_ui->helpText->setHtml(gui::utils::format_help_html(title, text));
}
}
@@ -1392,30 +1434,27 @@ void SettingsDialog::update_gpu_visibility() {
}
}
void SettingsDialog::add_stylesheets() {
void SettingsDialog::add_stylesheets(const QString &preferred_name) {
m_ui->combo_stylesheets->blockSignals(true);
m_ui->combo_stylesheets->clear();
m_ui->combo_stylesheets->addItem(tr("None", "Stylesheets"), gui::NoStylesheet);
const auto themes = m_theme_manager.available_themes();
for (const auto &theme : themes)
m_ui->combo_stylesheets->addItem(theme.display_name, theme.name);
m_ui->combo_stylesheets->addItem(tr("Light", "Stylesheets"), gui::LightStylesheet);
m_ui->combo_stylesheets->addItem(tr("Dark", "Stylesheets"), gui::DarkStylesheet);
if (m_gui_settings) {
for (const QString &entry : m_gui_settings->get_stylesheet_entries()) {
if (entry != gui::LightStylesheet && entry != gui::DarkStylesheet) {
m_ui->combo_stylesheets->addItem(entry, entry);
}
}
}
m_current_stylesheet = m_gui_settings
? m_gui_settings->get_value(gui::m_currentStylesheet).toString()
: gui::DarkStylesheet;
if (!preferred_name.isEmpty())
m_current_stylesheet = preferred_name;
else if (m_gui_settings)
m_current_stylesheet = m_gui_settings->get_value(gui::m_currentStylesheet).toString();
else
m_current_stylesheet.clear();
const int index = m_ui->combo_stylesheets->findData(m_current_stylesheet);
if (index >= 0) {
m_ui->combo_stylesheets->setCurrentIndex(index);
} else if (m_ui->combo_stylesheets->count() > 0) {
m_ui->combo_stylesheets->setCurrentIndex(0);
m_current_stylesheet = m_ui->combo_stylesheets->currentData().toString();
}
m_ui->combo_stylesheets->blockSignals(false);
}
+72
View File
@@ -1266,6 +1266,78 @@ QGroupBox[flat=&quot;true&quot;]::title {
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_theme_music_section">
<property name="title">
<string>Theme Music</string>
</property>
<layout class="QVBoxLayout" name="gb_theme_music_section_layout">
<item>
<widget class="QGroupBox" name="gb_theme_music_controls">
<property name="title">
<string>Vita Theme Music</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="gb_theme_music_controls_layout">
<property name="spacing">
<number>8</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="theme_music_enable">
<property name="text">
<string>Enable Vita theme background music</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="theme_music_volume_label">
<property name="text">
<string>Theme music volume: 50%</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="theme_music_volume">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TickPosition::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>25</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="audio_tab_content_spacer">
<property name="orientation">
@@ -124,6 +124,8 @@ SettingsDialogTooltips::SettingsDialogTooltips(QObject *parent)
, memory_mapping(tr("Memory mapping improved performance, reduces memory usage and fixes many graphical issues. However, it may be unstable on some GPUs."))
, audio_backend(tr("Select the audio backend. Cubeb is recommended for most systems."))
, audio_volume(tr("Set the in-game audio volume."))
, theme_music_enable(tr("Enable background music for generated Vita themes when the selected theme provides an ATRAC9 BGM file."))
, theme_music_volume(tr("Set the playback volume for Vita theme background music. This only affects interface theme music."))
, ngs_enable(tr("Enable advanced audio library NGS support."))
, front_camera(tr("Select the front camera source.\nSolid Color or Static Image can be used as substitutes."))
, back_camera(tr("Select the back camera source.\nSolid Color or Static Image can be used as substitutes."))
-321
View File
@@ -1,321 +0,0 @@
// Vita3K emulator project
// Copyright (C) 2026 Vita3K team
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <gui-qt/stylesheets.h>
namespace gui::stylesheets {
// Light built-in stylesheet
const QString light_style_sheet(
"QWidget { color: #202020; background-color: #f5f5f5; }"
"QWidget:disabled { color: #8a8a8a; }"
"QMainWindow { background-color: #f5f5f5; }"
"QDialog { background-color: #f5f5f5; }"
"QMenuBar { background-color: #f0f0f0; color: #202020; }"
"QMenuBar::item { background: transparent; padding: 4px 8px; }"
"QMenuBar::item:selected { background: #e1e1e1; }"
"QMenu { background-color: #f7f7f7; color: #202020; border: 1px solid #c6c6c6; }"
"QMenu::item:selected { background-color: #d6e8ff; color: #202020; }"
"QMenu::item:disabled { color: #808080; }"
"QMenu::separator { height: 1px; background: #d0d0d0; margin: 4px 8px; }"
"QToolBar#mw_toolbar { background-color: #f0f0f0; border: none; border-top: 1px solid #cfcfcf; }"
"QToolBar#mw_toolbar::separator { background-color: rgba(207, 207, 207, 235); width: 0.125em; margin-top: 0.250em; margin-bottom: 0.250em; }"
"QToolButton { color: #202020; background: transparent; border: 1px solid transparent; padding: 3px; }"
"QToolButton:hover { background-color: #e8e8e8; border: 1px solid #bdbdbd; }"
"QToolButton:pressed { background-color: #d8d8d8; color: #202020; }"
"QToolButton:disabled { color: #808080; }"
"QToolButton#welcome_firmware_button { background-color: #ffffff; color: #202020; border: 1px solid #b8b8b8; border-radius: 2px; padding: 4px 32px 4px 12px; text-align: left; }"
"QToolButton#welcome_firmware_button:hover { background-color: #f3f3f3; border-color: #9f9f9f; }"
"QToolButton#welcome_firmware_button:pressed { background-color: #e3e3e3; }"
"QToolButton#welcome_firmware_button::menu-button { subcontrol-origin: padding; subcontrol-position: top right; width: 24px; border-left: 1px solid #b8b8b8; border-top-right-radius: 2px; border-bottom-right-radius: 2px; background: #f3f3f3; }"
"QToolButton#welcome_firmware_button::menu-button:hover { background: #e8e8e8; border-left-color: #9f9f9f; }"
"QToolButton#welcome_firmware_button::menu-button:pressed { background: #dcdcdc; }"
"QLabel#toolbar_icon_color { color: #5b5b5b; }"
"QPushButton { background-color: #ffffff; color: #202020; border: 1px solid #b8b8b8; border-radius: 2px; padding: 4px 16px; min-height: 1.2em; }"
"QPushButton:hover { background-color: #f3f3f3; border-color: #9f9f9f; }"
"QPushButton:pressed { background-color: #e3e3e3; }"
"QPushButton:disabled { color: #8a8a8a; background-color: #efefef; border-color: #d4d4d4; }"
"QPushButton:default { border-color: #148aff; }"
"QDialog#ControlsDialog QPushButton { background-color: #ececec; color: #202020; border: none; border-radius: 6px; padding: 6px 12px; outline: none; }"
"QDialog#ControlsDialog QPushButton:hover { background-color: #e3e3e3; border: none; }"
"QDialog#ControlsDialog QPushButton:pressed { background-color: #d8d8d8; border: none; }"
"QDialog#ControlsDialog QPushButton:disabled { color: #8a8a8a; background-color: #e4e4e4; border: none; }"
"QDialog#ControlsDialog QPushButton[bindButton=\"true\"] { background-color: #e6e6e6; border-radius: 6px; padding: 6px 12px; }"
"QDialog#ControlsDialog QPushButton[bindButton=\"true\"]:hover { background-color: #dddddd; }"
"QDialog#ControlsDialog QPushButton[bindButton=\"true\"]:pressed { background-color: #d2d2d2; }"
"QDialog#ControlsDialog QPushButton[bindButton=\"true\"][captureActive=\"true\"] { background-color: #148aff; color: #ffffff; }"
"QDialog#ControlsDialog QPushButton[bindButton=\"true\"][captureActive=\"true\"]:hover { background-color: #2a97ff; }"
"QDialog#ControlsDialog QPushButton[bindButton=\"true\"][captureActive=\"true\"]:pressed { background-color: #0f74d6; }"
"QStatusBar#emulatorStatusBar { background-color: #f0f0f0; color: #202020; }"
"QStatusBar#emulatorStatusBar::item { border: none; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button { border: 1px solid transparent; padding: 2px 8px; font-weight: bold; font-size: 11px; background: transparent; border-radius: 6px; color: #202020; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button:hover { border: 1px solid #a0a0a0; background: rgba(0, 0, 0, 0.03); }"
"QStatusBar#emulatorStatusBar QPushButton#status_button:pressed { border: 1px solid #8a8a8a; background: rgba(0, 0, 0, 0.06); }"
"QStatusBar#emulatorStatusBar QPushButton#status_button[accent=\"renderer_vulkan\"] { color: #ff4444; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button[accent=\"renderer_opengl\"] { color: #4488ff; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button[accent=\"high\"] { color: #ff8800; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button[accent=\"standard\"] { color: #aaaaaa; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button[accent=\"neutral\"] { color: #5f5f5f; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button[accent=\"audio\"] { color: #4b9b2f; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button[accent=\"muted\"] { color: #777777; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button[accent=\"update\"] { color: #1f8f4e; }"
"QLineEdit { background-color: #ffffff; color: #202020; border: 1px solid #b8b8b8; border-radius: 2px; padding: 2px 4px; selection-background-color: #148aff; selection-color: #ffffff; }"
"QLineEdit:focus { border-color: #148aff; }"
"QLineEdit:disabled { color: #8a8a8a; background-color: #f0f0f0; border-color: #d0d0d0; }"
"QComboBox { background-color: #ffffff; color: #202020; border: 1px solid #b8b8b8; border-radius: 2px; padding: 2px 28px 2px 6px; }"
"QComboBox:hover { border-color: #9f9f9f; }"
"QComboBox:disabled { color: #8a8a8a; background-color: #f0f0f0; border-color: #d0d0d0; }"
"QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 24px; border: none; background: transparent; }"
"QComboBox::down-arrow { image: url(:/icons/chevron_down_dark.svg); width: 10px; height: 6px; }"
"QComboBox::down-arrow:on { top: 1px; }"
"QComboBox QAbstractItemView { background-color: #ffffff; color: #202020; border: 1px solid #b8b8b8; selection-background-color: #148aff; selection-color: #ffffff; }"
"QSpinBox, QDoubleSpinBox { background-color: #ffffff; color: #202020; border: 1px solid #b8b8b8; border-radius: 2px; padding: 2px 24px 2px 6px; selection-background-color: #148aff; selection-color: #ffffff; }"
"QSpinBox:disabled, QDoubleSpinBox:disabled { color: #8a8a8a; background-color: #f0f0f0; border-color: #d0d0d0; }"
"QSpinBox::up-button, QDoubleSpinBox::up-button { subcontrol-origin: border; subcontrol-position: top right; width: 18px; border: none; background: transparent; }"
"QSpinBox::down-button, QDoubleSpinBox::down-button { subcontrol-origin: border; subcontrol-position: bottom right; width: 18px; border: none; background: transparent; }"
"QSpinBox::up-arrow, QDoubleSpinBox::up-arrow { image: url(:/icons/chevron_up_dark.svg); width: 10px; height: 6px; }"
"QSpinBox::down-arrow, QDoubleSpinBox::down-arrow { image: url(:/icons/chevron_down_dark.svg); width: 10px; height: 6px; }"
"QCheckBox { color: #202020; spacing: 6px; }"
"QCheckBox:disabled { color: #8a8a8a; }"
"QRadioButton { color: #202020; spacing: 6px; }"
"QRadioButton:disabled { color: #8a8a8a; }"
"QGroupBox { color: #202020; border: 1px solid #c8c8c8; border-radius: 4px; margin-top: 0.5em; padding-top: 0.6em; }"
"QGroupBox:disabled { color: #9a9a9a; border-color: #d8d8d8; }"
"QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; padding: 0 4px; color: #202020; }"
"QLabel { color: #202020; background: transparent; }"
"QLabel:disabled { color: #8a8a8a; }"
"QTableWidget, QTableView { alternate-background-color: #f2f2f2; background-color: #ffffff; border: none; color: #202020; gridline-color: #e0e0e0; }"
"QTableWidget:disabled, QTableView:disabled { color: #8a8a8a; background-color: #f0f0f0; }"
"QTableCornerButton::section { background-color: #ececec; border: 1px solid #d4d4d4; }"
"QTableView::item { border-left: 0.063em solid #ffffff; border-right: 0.063em solid #ffffff; padding-left: 0.313em; }"
"QTableView::item:selected { background-color: #148aff; color: #ffffff; }"
"QTableWidget#apps_list_table::item:selected, QTableView#apps_list_table::item:selected { background-color: rgba(84, 140, 188, 48); color: #202020; border-left: 0.063em solid rgba(84, 140, 188, 0); border-right: 0.063em solid rgba(84, 140, 188, 0); }"
"QTableView#apps_list_table QHeaderView::section, QTableWidget#apps_list_table QHeaderView::section { padding-top: 0; padding-bottom: 0; }"
"QTableView#trophy_app_table::item:selected, QTableView#trophy_table::item:selected { background-color: rgba(84, 140, 188, 48); color: #202020; border-left: 0.063em solid rgba(84, 140, 188, 0); border-right: 0.063em solid rgba(84, 140, 188, 0); }"
"QHeaderView::section { background-color: #ececec; color: #202020; padding-left: .5em; padding-right: .5em; padding-top: .4em; padding-bottom: -.1em; border: 0.063em solid #ffffff; }"
"QHeaderView::section:hover { background: #e0e0e0; }"
"QDockWidget { background: transparent; color: #202020; }"
"QDockWidget[floating=\"true\"] { background: #f5f5f5; }"
"QDockWidget::title { background: #ececec; border: none; padding-top: 0.2em; padding-left: 0.2em; }"
"QDockWidget::close-button, QDockWidget::float-button { background-color: #ececec; border: none; }"
"QDockWidget::close-button:hover, QDockWidget::float-button:hover { background-color: #e0e0e0; }"
"QPlainTextEdit#log_frame { background-color: #ffffff; color: #202020; }"
"QWidget#log_search_container { background-color: #ffffff; border: 1px solid #c8c8c8; border-radius: 4px; }"
"QWidget#toolbar_icon_size_container { background-color: #f0f0f0; }"
"QSlider#toolbar_icon_size_slider { background: transparent; }"
"QSlider#toolbar_icon_size_slider::groove:horizontal { background: transparent; height: 4px; border-radius: 2px; }"
"QSlider#toolbar_icon_size_slider::sub-page:horizontal { background: #7f7f7f; height: 4px; border-radius: 2px; }"
"QSlider#toolbar_icon_size_slider::add-page:horizontal { background: #c8c8c8; height: 4px; border-radius: 2px; }"
"QSlider#toolbar_icon_size_slider::handle:horizontal { background: #5b5b5b; width: 14px; margin: -5px 0; border-radius: 7px; }"
"QSlider#toolbar_icon_size_slider::handle:horizontal:hover { background: #404040; }"
"QLabel#log_level_fatal { color: #ff00ff; }"
"QLabel#log_level_error { color: #C02F1D; }"
"QLabel#log_level_warning { color: #BA8745; }"
"QLabel#log_level_notice { color: #000000; }"
"QLabel#log_level_trace { color: #808080; }"
"QTabWidget::pane { border: 1px solid #c8c8c8; background-color: #f5f5f5; }"
"QTabBar::tab { background-color: #e9e9e9; color: #202020; padding: 6px 14px; border: 1px solid #c8c8c8; border-bottom: none; border-top-left-radius: 3px; border-top-right-radius: 3px; }"
"QTabBar::tab:selected { background-color: #f5f5f5; }"
"QTabBar::tab:!selected { margin-top: 2px; }"
"QTabBar::tab:hover:!selected { background-color: #dfdfdf; }"
"QListWidget { background-color: #ffffff; color: #202020; border: 1px solid #c8c8c8; }"
"QListWidget:disabled { color: #8a8a8a; background-color: #f0f0f0; border-color: #d6d6d6; }"
"QListWidget::item:selected { background-color: #148aff; color: #ffffff; }"
"QListWidget::item:hover:!selected { background-color: #f2f2f2; }"
"QDialog[class=\"settings-window\"] QListWidget#settingsCategory { background-color: #ffffff; border: none; border-radius: 10px; padding: 6px; outline: none; }"
"QDialog[class=\"settings-window\"] QListWidget#settingsCategory::item { background: transparent; border: none; border-radius: 10px; padding: 8px 12px; margin: 0px; }"
"QDialog[class=\"settings-window\"] QListWidget#settingsCategory::item:selected,"
"QDialog[class=\"settings-window\"] QListWidget#settingsCategory::item:selected:active,"
"QDialog[class=\"settings-window\"] QListWidget#settingsCategory::item:selected:!active { background-color: #148aff; color: #ffffff; }"
"QDialog[class=\"settings-window\"] QListWidget#settingsCategory::item:hover:!selected { background-color: #f2f2f2; }"
"QDialog[class=\"settings-window\"] QTextBrowser#helpText { background-color: #ffffff; color: #202020; border: none; border-radius: 10px; padding: 3px 4px; }"
"QScrollBar:vertical { background: #f0f0f0; width: 14px; margin: 0; }"
"QScrollBar::handle:vertical { background: #b0b0b0; min-height: 20px; border-radius: 3px; margin: 2px; }"
"QScrollBar::handle:vertical:hover { background: #989898; }"
"QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; }"
"QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: none; }"
"QScrollBar:horizontal { background: #f0f0f0; height: 14px; margin: 0; }"
"QScrollBar::handle:horizontal { background: #b0b0b0; min-width: 20px; border-radius: 3px; margin: 2px; }"
"QScrollBar::handle:horizontal:hover { background: #989898; }"
"QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0px; }"
"QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: none; }"
"QSlider::groove:horizontal { background: #c8c8c8; height: 4px; border-radius: 2px; }"
"QSlider::handle:horizontal { background: #7f7f7f; width: 14px; margin: -5px 0; border-radius: 7px; }"
"QSlider::handle:horizontal:hover { background: #666666; }"
"QSlider::groove:vertical { background: #c8c8c8; width: 4px; border-radius: 2px; }"
"QSlider::handle:vertical { background: #7f7f7f; height: 14px; margin: 0 -5px; border-radius: 7px; }"
"QSlider::handle:vertical:hover { background: #666666; }"
"QProgressBar { background-color: #ffffff; color: #202020; border: 1px solid #c8c8c8; text-align: center; border-radius: 2px; }"
"QProgressBar::chunk { background-color: #148aff; border-radius: 2px; }"
"QSplitter::handle { background-color: #d4d4d4; }"
"QToolTip { background-color: #f7f7f7; color: #202020; border: 1px solid #c8c8c8; padding: 2px; }"
"QPlainTextEdit, QTextEdit, QTextBrowser { background-color: #ffffff; color: #202020; border: 1px solid #c8c8c8; }"
"QPlainTextEdit:disabled, QTextEdit:disabled, QTextBrowser:disabled { color: #8a8a8a; background-color: #f0f0f0; border-color: #d0d0d0; }"
"QDialogButtonBox QPushButton { min-width: 80px; }");
const QString dark_style_sheet(
"QWidget { color: #e0e0e0; background-color: #353535; }"
"QWidget:disabled { color: #777777; }"
"QMainWindow { background-color: #353535; }"
"QDialog { background-color: #353535; }"
"QMenuBar { background-color: #353535; color: #e0e0e0; }"
"QMenuBar::item { background: transparent; padding: 4px 8px; }"
"QMenuBar::item:selected { background: #4a4a4a; }"
"QMenu { background-color: #353535; color: #e0e0e0; border: 1px solid #555555; }"
"QMenu::item:selected { background-color: rgba(255, 255, 255, 0.12); color: #ffffff; }"
"QMenu::item:disabled { color: #808080; }"
"QMenu::separator { height: 1px; background: #555555; margin: 4px 8px; }"
"QToolBar#mw_toolbar { background-color: #353535; border: none; border-top: 1px solid #555555; }"
"QToolBar#mw_toolbar::separator { background-color: rgba(100, 100, 100, 235); width: 0.125em; margin-top: 0.250em; margin-bottom: 0.250em; }"
"QToolButton { color: #e0e0e0; background: transparent; border: 1px solid transparent; padding: 3px; }"
"QToolButton:hover { background-color: #4a4a4a; border: 1px solid #666666; }"
"QToolButton:pressed { background-color: rgba(255, 255, 255, 0.12); }"
"QToolButton:disabled { color: #808080; }"
"QToolButton#welcome_firmware_button { background-color: #353535; color: #e0e0e0; border: 1px solid #555555; border-radius: 2px; padding: 4px 32px 4px 12px; text-align: left; }"
"QToolButton#welcome_firmware_button:hover { background-color: #4a4a4a; border-color: #666666; }"
"QToolButton#welcome_firmware_button:pressed { background-color: rgba(255, 255, 255, 0.12); }"
"QToolButton#welcome_firmware_button::menu-button { subcontrol-origin: padding; subcontrol-position: top right; width: 24px; border-left: 1px solid #555555; border-top-right-radius: 2px; border-bottom-right-radius: 2px; background: #303030; }"
"QToolButton#welcome_firmware_button::menu-button:hover { background: #444444; border-left-color: #666666; }"
"QToolButton#welcome_firmware_button::menu-button:pressed { background: rgba(255, 255, 255, 0.12); }"
"QLabel#toolbar_icon_color { color: #f3f3f3; }"
"QPushButton { background-color: #353535; color: #e0e0e0; border: 1px solid #555555; border-radius: 2px; padding: 4px 16px; min-height: 1.2em; }"
"QPushButton:hover { background-color: #4a4a4a; border-color: #666666; }"
"QPushButton:pressed { background-color: rgba(255, 255, 255, 0.12); }"
"QPushButton:disabled { color: #808080; background-color: #303030; border-color: #444444; }"
"QPushButton:default { border-color: rgba(255, 255, 255, 0.24); }"
"QDialog#ControlsDialog QPushButton { background-color: #303030; color: #e0e0e0; border: none; border-radius: 6px; padding: 6px 12px; outline: none; }"
"QDialog#ControlsDialog QPushButton:hover { background-color: #3a3a3a; border: none; }"
"QDialog#ControlsDialog QPushButton:pressed { background-color: #464646; border: none; }"
"QDialog#ControlsDialog QPushButton:disabled { color: #808080; background-color: #2c2c2c; border: none; }"
"QDialog#ControlsDialog QPushButton[bindButton=\"true\"] { background-color: #2f2f2f; border-radius: 6px; padding: 6px 12px; }"
"QDialog#ControlsDialog QPushButton[bindButton=\"true\"]:hover { background-color: #393939; }"
"QDialog#ControlsDialog QPushButton[bindButton=\"true\"]:pressed { background-color: #454545; }"
"QDialog#ControlsDialog QPushButton[bindButton=\"true\"][captureActive=\"true\"] { background-color: rgba(255, 255, 255, 0.18); color: #ffffff; }"
"QDialog#ControlsDialog QPushButton[bindButton=\"true\"][captureActive=\"true\"]:hover { background-color: rgba(255, 255, 255, 0.24); }"
"QDialog#ControlsDialog QPushButton[bindButton=\"true\"][captureActive=\"true\"]:pressed { background-color: rgba(255, 255, 255, 0.30); }"
"QStatusBar#emulatorStatusBar { background-color: #353535; color: #e0e0e0; }"
"QStatusBar#emulatorStatusBar::item { border: none; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button { border: 1px solid transparent; padding: 2px 8px; font-weight: bold; font-size: 11px; background: transparent; border-radius: 6px; color: #e0e0e0; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button:hover { border: 1px solid #666666; background: rgba(255, 255, 255, 0.04); }"
"QStatusBar#emulatorStatusBar QPushButton#status_button:pressed { border: 1px solid #888888; background: rgba(255, 255, 255, 0.07); }"
"QStatusBar#emulatorStatusBar QPushButton#status_button[accent=\"renderer_vulkan\"] { color: #ff4444; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button[accent=\"renderer_opengl\"] { color: #4488ff; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button[accent=\"high\"] { color: #ff8800; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button[accent=\"standard\"] { color: #aaaaaa; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button[accent=\"neutral\"] { color: #cccccc; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button[accent=\"audio\"] { color: #88dd44; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button[accent=\"muted\"] { color: #777777; }"
"QStatusBar#emulatorStatusBar QPushButton#status_button[accent=\"update\"] { color: #6de089; }"
"QLineEdit { background-color: #2a2a2a; color: #e0e0e0; border: 1px solid #555555; border-radius: 2px; padding: 2px 4px; selection-background-color: rgba(255, 255, 255, 0.18); selection-color: #ffffff; }"
"QLineEdit:focus { border-color: rgba(255, 255, 255, 0.24); }"
"QLineEdit:disabled { color: #777777; background-color: #303030; border-color: #444444; }"
"QComboBox { background-color: #353535; color: #e0e0e0; border: 1px solid #555555; border-radius: 2px; padding: 2px 28px 2px 6px; }"
"QComboBox:hover { border-color: #666666; }"
"QComboBox:disabled { color: #777777; background-color: #303030; border-color: #444444; }"
"QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 24px; border: none; background: transparent; }"
"QComboBox::down-arrow { image: url(:/icons/chevron_down_light.svg); width: 10px; height: 6px; }"
"QComboBox::down-arrow:on { top: 1px; }"
"QComboBox QAbstractItemView { background-color: #2a2a2a; color: #e0e0e0; border: 1px solid #555555; selection-background-color: rgba(255, 255, 255, 0.12); selection-color: #ffffff; }"
"QSpinBox, QDoubleSpinBox { background-color: #2a2a2a; color: #e0e0e0; border: 1px solid #555555; border-radius: 2px; padding: 2px 24px 2px 6px; selection-background-color: rgba(255, 255, 255, 0.18); selection-color: #ffffff; }"
"QSpinBox:disabled, QDoubleSpinBox:disabled { color: #777777; background-color: #303030; border-color: #444444; }"
"QSpinBox::up-button, QDoubleSpinBox::up-button { subcontrol-origin: border; subcontrol-position: top right; width: 18px; border: none; background: transparent; }"
"QSpinBox::down-button, QDoubleSpinBox::down-button { subcontrol-origin: border; subcontrol-position: bottom right; width: 18px; border: none; background: transparent; }"
"QSpinBox::up-arrow, QDoubleSpinBox::up-arrow { image: url(:/icons/chevron_up_light.svg); width: 10px; height: 6px; }"
"QSpinBox::down-arrow, QDoubleSpinBox::down-arrow { image: url(:/icons/chevron_down_light.svg); width: 10px; height: 6px; }"
"QCheckBox { color: #e0e0e0; spacing: 6px; }"
"QCheckBox:disabled { color: #777777; }"
"QRadioButton { color: #e0e0e0; spacing: 6px; }"
"QRadioButton:disabled { color: #777777; }"
"QGroupBox { color: #e0e0e0; border: 1px solid #555555; border-radius: 4px; margin-top: 0.5em; padding-top: 0.6em; }"
"QGroupBox:disabled { color: #7f7f7f; border-color: #4a4a4a; }"
"QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; padding: 0 4px; color: #e0e0e0; }"
"QLabel { color: #e0e0e0; background: transparent; }"
"QLabel:disabled { color: #777777; }"
"QTableWidget, QTableView { alternate-background-color: #3a3a3a; background-color: #2a2a2a; border: none; color: #e0e0e0; gridline-color: #444444; }"
"QTableWidget:disabled, QTableView:disabled { color: #777777; background-color: #303030; }"
"QTableCornerButton::section { background-color: #353535; border: 1px solid #555555; }"
"QTableView::item { border-left: 0.063em solid #2a2a2a; border-right: 0.063em solid #2a2a2a; padding-left: 0.313em; }"
"QTableView::item:selected { background-color: rgba(255, 255, 255, 0.12); color: #ffffff; }"
"QTableWidget#apps_list_table::item:selected, QTableView#apps_list_table::item:selected { background-color: rgba(255, 255, 255, 0.12); color: #f0f0f0; border-left: 0.063em solid rgba(255, 255, 255, 0); border-right: 0.063em solid rgba(255, 255, 255, 0); }"
"QTableView#apps_list_table QHeaderView::section, QTableWidget#apps_list_table QHeaderView::section { padding-top: 0; padding-bottom: 0; }"
"QTableView#trophy_app_table::item:selected, QTableView#trophy_table::item:selected { background-color: rgba(255, 255, 255, 0.13); color: #ffffff; border-left: 0.063em solid rgba(255, 255, 255, 0); border-right: 0.063em solid rgba(255, 255, 255, 0); }"
"QHeaderView::section { background-color: #353535; color: #ffffff; padding-left: .5em; padding-right: .5em; padding-top: .4em; padding-bottom: -.1em; border: 0.063em solid #2a2a2a; }"
"QHeaderView::section:hover { background: #4a4a4a; }"
"QDockWidget { background: transparent; color: #ffffff; }"
"QDockWidget[floating=\"true\"] { background: #353535; }"
"QDockWidget::title { background: #353535; border: none; padding-top: 0.2em; padding-left: 0.2em; }"
"QDockWidget::close-button, QDockWidget::float-button { background-color: #353535; border: none; }"
"QDockWidget::close-button:hover, QDockWidget::float-button:hover { background-color: #4a4a4a; }"
"QPlainTextEdit#log_frame { background-color: #2a2a2a; color: #ffffff; }"
"QWidget#log_search_container { background-color: #2a2a2a; border: 1px solid #555555; border-radius: 4px; }"
"QWidget#toolbar_icon_size_container { background-color: #353535; }"
"QSlider#toolbar_icon_size_slider { background: transparent; }"
"QSlider#toolbar_icon_size_slider::groove:horizontal { background: transparent; height: 4px; border-radius: 2px; }"
"QSlider#toolbar_icon_size_slider::sub-page:horizontal { background: rgba(255, 255, 255, 0.82); height: 4px; border-radius: 2px; }"
"QSlider#toolbar_icon_size_slider::add-page:horizontal { background: #555555; height: 4px; border-radius: 2px; }"
"QSlider#toolbar_icon_size_slider::handle:horizontal { background: #f3f3f3; width: 14px; margin: -5px 0; border-radius: 7px; }"
"QSlider#toolbar_icon_size_slider::handle:horizontal:hover { background: #ffffff; }"
"QLabel#log_level_fatal { color: #ff66ff; }"
"QLabel#log_level_error { color: #ff8080; }"
"QLabel#log_level_warning { color: #ffd27f; }"
"QLabel#log_level_notice { color: #ffffff; }"
"QLabel#log_level_trace { color: #ffffff; }"
"QTabWidget::pane { border: 1px solid #555555; background-color: #353535; }"
"QTabBar::tab { background-color: #2d2d2d; color: #e0e0e0; padding: 6px 14px; border: 1px solid #555555; border-bottom: none; border-top-left-radius: 3px; border-top-right-radius: 3px; }"
"QTabBar::tab:selected { background-color: #353535; }"
"QTabBar::tab:!selected { margin-top: 2px; }"
"QTabBar::tab:hover:!selected { background-color: #3a3a3a; }"
"QListWidget { background-color: #2a2a2a; color: #e0e0e0; border: 1px solid #555555; }"
"QListWidget:disabled { color: #777777; background-color: #303030; border-color: #444444; }"
"QListWidget::item:selected { background-color: rgba(255, 255, 255, 0.12); color: #ffffff; }"
"QListWidget::item:hover:!selected { background-color: #3a3a3a; }"
"QDialog[class=\"settings-window\"] QListWidget#settingsCategory { background-color: #2a2a2a; border: none; border-radius: 10px; padding: 6px; outline: none; }"
"QDialog[class=\"settings-window\"] QListWidget#settingsCategory::item { background: transparent; border: none; border-radius: 10px; padding: 8px 12px; margin: 0px; }"
"QDialog[class=\"settings-window\"] QListWidget#settingsCategory::item:selected,"
"QDialog[class=\"settings-window\"] QListWidget#settingsCategory::item:selected:active,"
"QDialog[class=\"settings-window\"] QListWidget#settingsCategory::item:selected:!active { background-color: rgba(255, 255, 255, 0.12); color: #ffffff; }"
"QDialog[class=\"settings-window\"] QListWidget#settingsCategory::item:hover:!selected { background-color: #3a3a3a; }"
"QDialog[class=\"settings-window\"] QTextBrowser#helpText { background-color: #2a2a2a; color: #e0e0e0; border: none; border-radius: 10px; padding: 3px 4px; }"
"QScrollBar:vertical { background: #2a2a2a; width: 14px; margin: 0; }"
"QScrollBar::handle:vertical { background: #5a5a5a; min-height: 20px; border-radius: 3px; margin: 2px; }"
"QScrollBar::handle:vertical:hover { background: #6a6a6a; }"
"QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; }"
"QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: none; }"
"QScrollBar:horizontal { background: #2a2a2a; height: 14px; margin: 0; }"
"QScrollBar::handle:horizontal { background: #5a5a5a; min-width: 20px; border-radius: 3px; margin: 2px; }"
"QScrollBar::handle:horizontal:hover { background: #6a6a6a; }"
"QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0px; }"
"QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: none; }"
"QSlider::groove:horizontal { background: transparent; height: 4px; border-radius: 2px; }"
"QSlider::sub-page:horizontal { background: rgba(255, 255, 255, 0.82); height: 4px; border-radius: 2px; }"
"QSlider::add-page:horizontal { background: #555555; height: 4px; border-radius: 2px; }"
"QSlider::handle:horizontal { background: #c0c0c0; width: 14px; margin: -5px 0; border-radius: 7px; }"
"QSlider::handle:horizontal:hover { background: #ffffff; }"
"QSlider::groove:vertical { background: transparent; width: 4px; border-radius: 2px; }"
"QSlider::sub-page:vertical { background: rgba(255, 255, 255, 0.82); width: 4px; border-radius: 2px; }"
"QSlider::add-page:vertical { background: #555555; width: 4px; border-radius: 2px; }"
"QSlider::handle:vertical { background: #c0c0c0; height: 14px; margin: 0 -5px; border-radius: 7px; }"
"QSlider::handle:vertical:hover { background: #ffffff; }"
"QProgressBar { background-color: #2a2a2a; color: #e0e0e0; border: 1px solid #555555; text-align: center; border-radius: 2px; }"
"QProgressBar::chunk { background-color: #6a6a6a; border-radius: 2px; }"
"QSplitter::handle { background-color: #555555; }"
"QToolTip { background-color: #353535; color: #e0e0e0; border: 1px solid #555555; padding: 2px; }"
"QPlainTextEdit, QTextEdit, QTextBrowser { background-color: #2a2a2a; color: #e0e0e0; border: 1px solid #555555; }"
"QPlainTextEdit:disabled, QTextEdit:disabled, QTextBrowser:disabled { color: #777777; background-color: #303030; border-color: #444444; }"
"QDialogButtonBox QPushButton { min-width: 80px; }");
} // namespace gui::stylesheets
File diff suppressed because it is too large Load Diff
+37
View File
@@ -0,0 +1,37 @@
// Vita3K emulator project
// Copyright (C) 2026 Vita3K team
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <gui-qt/theme_surface.h>
#include <QPainter>
#include <QStyle>
#include <QStyleOption>
ThemeSurface::ThemeSurface(QWidget *parent)
: QWidget(parent) {
setAttribute(Qt::WA_StyledBackground, true);
}
void ThemeSurface::paintEvent(QPaintEvent *event) {
Q_UNUSED(event);
QStyleOption opt;
opt.initFrom(this);
QPainter painter(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
}
@@ -128,7 +128,7 @@ TrophyCollectionDialog::TrophyCollectionDialog(EmuEnvState &emuenv,
, m_emuenv(emuenv)
, m_gui_settings(std::move(gui_settings)) {
setWindowTitle(tr("Trophy Collection"));
setObjectName(QStringLiteral("TrophyCollectionDialog"));
setObjectName(QStringLiteral("trophy_collection_dialog"));
setWindowModality(Qt::NonModal);
setWindowFlag(Qt::WindowSystemMenuHint, true);
setWindowFlag(Qt::WindowMinMaxButtonsHint, true);
+921
View File
@@ -0,0 +1,921 @@
// Vita3K emulator project
// Copyright (C) 2026 Vita3K team
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <gui-qt/qt_utils.h>
#include <gui-qt/vita_theme.h>
#include <util/log.h>
#include <util/string_utils.h>
#include <util/vita_theme_utils.h>
#include <QFile>
#include <pugixml.hpp>
#include <algorithm>
#include <cctype>
#include <cmath>
#include <optional>
#include <unordered_map>
namespace gui {
namespace {
std::string lowercase_copy(std::string value) {
std::transform(value.begin(), value.end(), value.begin(), [](const unsigned char ch) {
return static_cast<char>(std::tolower(ch));
});
return value;
}
std::string normalize_locale(std::string locale) {
locale = string_utils::trim_copy(std::move(locale));
std::replace(locale.begin(), locale.end(), '_', '-');
return lowercase_copy(std::move(locale));
}
std::string path_leaf_string(const fs::path &path) {
return fs_utils::path_to_utf8(path.filename());
}
std::string child_text(const pugi::xml_node &parent, const char *name) {
return string_utils::trim_copy(parent.child(name).text().as_string());
}
std::string localized_text(const pugi::xml_node &node, const std::string &preferred_locale) {
if (!node)
return {};
const std::string normalized_locale = normalize_locale(preferred_locale);
const auto try_key = [&node](const std::string &name) -> std::string {
return child_text(node, name.c_str());
};
const auto try_locale = [&try_key](const std::string &name) -> std::string {
if (name.empty())
return {};
if (const std::string exact = try_key(name); !exact.empty())
return exact;
return try_key("m_" + name);
};
if (!normalized_locale.empty()) {
if (const std::string exact = try_locale(normalized_locale); !exact.empty())
return exact;
if (const auto separator = normalized_locale.find('-'); separator != std::string::npos) {
if (const std::string base = try_locale(normalized_locale.substr(0, separator)); !base.empty())
return base;
}
}
if (const std::string fallback = child_text(node, "m_default"); !fallback.empty())
return fallback;
for (const auto &child : node.children()) {
if (child.type() != pugi::node_element)
continue;
if (const std::string value = string_utils::trim_copy(child.text().as_string()); !value.empty())
return value;
}
return {};
}
fs::path resolve_asset_path(const fs::path &theme_dir, std::string asset_value) {
const QString relative_path = gui::utils::sanitize_relative_path_reference(
QString::fromStdString(string_utils::trim_copy(std::move(asset_value))));
if (relative_path.isEmpty())
return {};
return (theme_dir / gui::utils::to_fs_path(relative_path)).lexically_normal();
}
std::optional<VitaThemeColor> parse_theme_color(std::string raw_value) {
raw_value = string_utils::trim_copy(std::move(raw_value));
if (raw_value.empty())
return std::nullopt;
if (raw_value.front() == '#')
raw_value.erase(raw_value.begin());
else if (raw_value.starts_with("0x") || raw_value.starts_with("0X"))
raw_value.erase(0, 2);
unsigned long long value = 0;
try {
value = std::stoull(raw_value, nullptr, 16);
} catch (const std::exception &) {
try {
value = std::stoull(raw_value, nullptr, 10);
} catch (const std::exception &) {
LOG_WARN("Unable to parse Vita theme color '{}'", raw_value);
return std::nullopt;
}
}
if (raw_value.size() <= 6 && value <= 0xFFFFFFULL) {
return VitaThemeColor{
static_cast<std::uint8_t>((value >> 16) & 0xFF),
static_cast<std::uint8_t>((value >> 8) & 0xFF),
static_cast<std::uint8_t>(value & 0xFF),
255,
};
}
return VitaThemeColor{
static_cast<std::uint8_t>((value >> 16) & 0xFF),
static_cast<std::uint8_t>((value >> 8) & 0xFF),
static_cast<std::uint8_t>(value & 0xFF),
static_cast<std::uint8_t>((value >> 24) & 0xFF),
};
}
double luminance(const VitaThemeColor &color) {
return (0.2126 * color.red + 0.7152 * color.green + 0.0722 * color.blue) / 255.0;
}
double color_distance(const VitaThemeColor &lhs, const VitaThemeColor &rhs) {
const auto component_delta = [](const std::uint8_t first, const std::uint8_t second) {
return static_cast<double>(static_cast<int>(first) - static_cast<int>(second)) / 255.0;
};
const double red = component_delta(lhs.red, rhs.red);
const double green = component_delta(lhs.green, rhs.green);
const double blue = component_delta(lhs.blue, rhs.blue);
return std::sqrt((red * red + green * green + blue * blue) / 3.0);
}
VitaThemeColor mix(const VitaThemeColor &lhs, const VitaThemeColor &rhs, const double ratio) {
const double clamped = std::clamp(ratio, 0.0, 1.0);
const auto blend = [clamped](const std::uint8_t first, const std::uint8_t second) {
return static_cast<std::uint8_t>(std::lround((first * (1.0 - clamped)) + (second * clamped)));
};
return VitaThemeColor{
blend(lhs.red, rhs.red),
blend(lhs.green, rhs.green),
blend(lhs.blue, rhs.blue),
blend(lhs.alpha, rhs.alpha),
};
}
VitaThemeColor darken(const VitaThemeColor &color, const double ratio) {
return mix(color, VitaThemeColor{}, ratio);
}
VitaThemeColor lighten(const VitaThemeColor &color, const double ratio) {
return mix(color, VitaThemeColor{ 255, 255, 255, 255 }, ratio);
}
VitaThemeColor contrasting_text(const VitaThemeColor &background) {
return luminance(background) > 0.55
? VitaThemeColor{ 10, 18, 24, 255 }
: VitaThemeColor{ 245, 247, 250, 255 };
}
VitaThemeColor ensure_readable(const VitaThemeColor &foreground, const VitaThemeColor &background) {
return std::abs(luminance(foreground) - luminance(background)) < 0.28
? contrasting_text(background)
: foreground;
}
std::string css_hex(const VitaThemeColor &color) {
return fmt::format("#{:02X}{:02X}{:02X}", color.red, color.green, color.blue);
}
std::string css_argb(const VitaThemeColor &color, const std::uint8_t alpha) {
return fmt::format("#{:02X}{:02X}{:02X}{:02X}", alpha, color.red, color.green, color.blue);
}
std::string css_rgba(const VitaThemeColor &color, const std::uint8_t alpha) {
return fmt::format("rgba({}, {}, {}, {:.3f})",
color.red,
color.green,
color.blue,
static_cast<double>(alpha) / 255.0);
}
std::uint8_t readability_alpha(const int readability_percent, const std::uint8_t minimum, const std::uint8_t maximum) {
const double amount = std::clamp(static_cast<double>(readability_percent) / 100.0, 0.0, 1.0);
return static_cast<std::uint8_t>(std::lround((minimum * (1.0 - amount)) + (maximum * amount)));
}
std::string stylesheet_path_url(const fs::path &path, const fs::path &pref_path) {
fs::path normalized_path = path.lexically_normal();
if (!pref_path.empty()) {
const fs::path relative = normalized_path.lexically_relative(pref_path);
if (!relative.empty() && relative != normalized_path)
normalized_path = relative;
}
std::string normalized = fs_utils::path_to_utf8(normalized_path);
std::replace(normalized.begin(), normalized.end(), '\\', '/');
return normalized;
}
std::string join_stylesheet_path_urls(const std::vector<fs::path> &paths, const fs::path &pref_path) {
std::string joined;
bool first = true;
for (const fs::path &path : paths) {
if (path.empty())
continue;
if (!first)
joined += ", ";
joined += stylesheet_path_url(path, pref_path);
first = false;
}
return joined;
}
struct ThemeTemplateContext {
std::unordered_map<std::string, std::string> strings;
};
std::optional<std::string> load_vita_theme_stylesheet_template() {
static const std::optional<std::string> stylesheet_template = []() -> std::optional<std::string> {
QFile file(QStringLiteral(":/themes/vita/generated.qss.in"));
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
LOG_ERROR("Failed to load Vita theme stylesheet template from resources");
return std::nullopt;
}
return std::string(file.readAll().constData(), static_cast<std::size_t>(file.size()));
}();
return stylesheet_template;
}
std::optional<std::string> render_template_token(
const ThemeTemplateContext &context,
const std::string &token) {
const auto it = context.strings.find(token);
return (it != context.strings.end()) ? std::optional<std::string>(it->second) : std::nullopt;
}
std::optional<std::string> render_stylesheet_template(
const ThemeTemplateContext &context) {
auto stylesheet_template = load_vita_theme_stylesheet_template();
if (!stylesheet_template)
return std::nullopt;
std::string rendered;
rendered.reserve(stylesheet_template->size());
std::size_t cursor = 0;
while (cursor < stylesheet_template->size()) {
const std::size_t start = stylesheet_template->find("{{", cursor);
if (start == std::string::npos) {
rendered.append(*stylesheet_template, cursor, std::string::npos);
break;
}
rendered.append(*stylesheet_template, cursor, start - cursor);
const std::size_t finish = stylesheet_template->find("}}", start + 2);
if (finish == std::string::npos) {
rendered.append(*stylesheet_template, start, std::string::npos);
break;
}
const std::string token = stylesheet_template->substr(start + 2, finish - (start + 2));
const auto value = render_template_token(context, token);
if (!value) {
LOG_ERROR("Unknown Vita theme stylesheet template token '{}'", token);
return std::nullopt;
}
rendered.append(*value);
cursor = finish + 2;
}
return rendered;
}
std::optional<fs::path> existing_asset_path(const fs::path &theme_dir, const std::string &raw_path) {
const fs::path resolved = resolve_asset_path(theme_dir, raw_path);
return (!resolved.empty() && fs::exists(resolved)) ? std::optional<fs::path>(resolved) : std::nullopt;
}
std::vector<pugi::xml_node> collect_background_nodes(const pugi::xml_node &home_node) {
std::vector<pugi::xml_node> nodes;
const auto bg_parent = home_node.child("m_bgParam");
if (!bg_parent)
return nodes;
for (const auto &child : bg_parent.children()) {
if (child.type() != pugi::node_element)
continue;
if ((std::string_view(child.name()) == "BackgroundParam") || child.child("m_imageFilePath"))
nodes.push_back(child);
else if (const auto nested = child.child("BackgroundParam"))
nodes.push_back(nested);
}
return nodes;
}
void collect_directory_metrics(const fs::path &root, std::uintmax_t &total_size, std::time_t &latest_write_time) {
if (!fs::exists(root))
return;
boost::system::error_code error_code;
if (const auto root_time = fs::last_write_time(root, error_code); !error_code)
latest_write_time = std::max(latest_write_time, root_time);
fs::recursive_directory_iterator end;
for (fs::recursive_directory_iterator it(root, error_code); !error_code && it != end; it.increment(error_code)) {
const fs::path current = it->path();
if (fs::is_regular_file(current, error_code)) {
total_size += fs::file_size(current, error_code);
error_code.clear();
}
if (const auto write_time = fs::last_write_time(current, error_code); !error_code)
latest_write_time = std::max(latest_write_time, write_time);
error_code.clear();
}
}
const VitaThemeBackgroundOption *find_background_option(
const VitaThemeInfo &theme,
const std::string &background_option_id) {
if (theme.background_options.empty())
return nullptr;
if (background_option_id.empty())
return &theme.background_options.front();
const auto it = std::find_if(theme.background_options.begin(), theme.background_options.end(),
[&background_option_id](const VitaThemeBackgroundOption &option) {
return option.id == background_option_id;
});
return (it != theme.background_options.end()) ? &(*it) : nullptr;
}
std::string build_stylesheet(
const VitaThemeInfo &theme,
const VitaThemeBackgroundOption &background_option,
const int readability_percent,
const bool normalize_font_colors,
const std::vector<fs::path> &background_image_paths,
const int cycle_interval_seconds,
const bool cycle_enabled,
const fs::path &pref_path) {
const VitaThemeColor window = theme.palette.information_bar_color.value_or(VitaThemeColor{ 18, 26, 34, 255 });
const bool dark_theme = luminance(window) < 0.5;
const VitaThemeColor base = darken(window, 0.18);
const VitaThemeColor alternate_base = lighten(base, 0.08);
const VitaThemeColor accent_hint = dark_theme
? VitaThemeColor{ 140, 212, 255, 255 }
: VitaThemeColor{ 20, 138, 255, 255 };
VitaThemeColor accent = theme.palette.information_indicator_color.value_or(accent_hint);
const VitaThemeColor normalized_text = contrasting_text(window);
const VitaThemeColor neutral_border = mix(normalized_text, window, dark_theme ? 0.34 : 0.56);
VitaThemeColor border = theme.palette.notify_border_color
? mix(*theme.palette.notify_border_color, neutral_border, dark_theme ? 0.42 : 0.58)
: neutral_border;
const VitaThemeColor theme_text = normalize_font_colors
? normalized_text
: ensure_readable(
background_option.primary_text_color.value_or(
theme.palette.notify_font_color.value_or(normalized_text)),
window);
const VitaThemeColor dialog_text = dark_theme ? VitaThemeColor{ 224, 224, 224, 255 } : VitaThemeColor{ 32, 32, 32, 255 };
const VitaThemeColor dialog_disabled_text = dark_theme ? VitaThemeColor{ 119, 119, 119, 255 } : VitaThemeColor{ 138, 138, 138, 255 };
const VitaThemeColor dialog_muted_text = dark_theme ? VitaThemeColor{ 168, 168, 168, 255 } : VitaThemeColor{ 91, 91, 91, 255 };
const VitaThemeColor overlay_text = (normalize_font_colors && !dark_theme)
? VitaThemeColor{ 245, 247, 250, 255 }
: theme_text;
const VitaThemeColor text = overlay_text;
VitaThemeColor muted_text = ensure_readable(
normalize_font_colors
? mix(dialog_text, window, dark_theme ? 0.32 : 0.48)
: theme.palette.date_color.value_or(mix(theme_text, window, dark_theme ? 0.42 : 0.52)),
window);
const VitaThemeColor overlay_muted_text = normalize_font_colors
? (dark_theme ? VitaThemeColor{ 168, 168, 168, 255 } : VitaThemeColor{ 208, 220, 230, 255 })
: muted_text;
if ((std::abs(luminance(accent) - luminance(window)) < 0.16)
&& (std::abs(luminance(accent) - luminance(theme_text)) < 0.10)) {
accent = accent_hint;
}
accent = ensure_readable(accent, window);
const VitaThemeColor button = theme.palette.notify_background_color.value_or(mix(window, accent, 0.10));
const VitaThemeColor button_text = normalize_font_colors
? ensure_readable(dialog_text, button)
: ensure_readable(
theme.palette.notify_font_color.value_or(text),
button);
const VitaThemeColor highlight_text = contrasting_text(accent);
const VitaThemeColor checkbox_fill = mix(base, window, dark_theme ? 0.22 : 0.14);
const VitaThemeColor disabled_text = mix(muted_text, window, 0.32);
const VitaThemeColor overlay_disabled_text = normalize_font_colors
? (dark_theme ? VitaThemeColor{ 119, 119, 119, 255 } : VitaThemeColor{ 166, 180, 194, 255 })
: disabled_text;
const VitaThemeColor line_edit_fill = mix(base, window, 0.35);
const VitaThemeColor subtle_surface = mix(base, window, 0.40);
const VitaThemeColor overlay_selection_fill = dark_theme
? VitaThemeColor{ 255, 255, 255, 255 }
: VitaThemeColor{ 10, 18, 24, 255 };
const VitaThemeColor overlay_selection_text = VitaThemeColor{ 255, 255, 255, 255 };
const VitaThemeColor hover_surface = overlay_selection_fill;
const VitaThemeColor selected_text = overlay_selection_text;
const VitaThemeColor toolbar_icon_tint = normalize_font_colors
? overlay_text
: (dark_theme ? text : mix(text, window, 0.48));
const VitaThemeColor dialog_button_fill = dark_theme ? VitaThemeColor{ 48, 48, 48, 255 } : VitaThemeColor{ 236, 236, 236, 255 };
const VitaThemeColor dialog_bind_fill = dark_theme ? VitaThemeColor{ 47, 47, 47, 255 } : VitaThemeColor{ 230, 230, 230, 255 };
const VitaThemeColor dialog_sidebar_fill = dark_theme ? VitaThemeColor{ 42, 42, 42, 255 } : VitaThemeColor{ 255, 255, 255, 255 };
const VitaThemeColor dialog_help_fill = dark_theme ? VitaThemeColor{ 42, 42, 42, 255 } : VitaThemeColor{ 255, 255, 255, 255 };
const VitaThemeColor dialog_hover_fill = dark_theme ? VitaThemeColor{ 58, 58, 58, 255 } : VitaThemeColor{ 242, 242, 242, 255 };
const VitaThemeColor dialog_selected_fill = dark_theme ? VitaThemeColor{ 255, 255, 255, 255 } : VitaThemeColor{ 20, 138, 255, 255 };
const VitaThemeColor dialog_selected_text = VitaThemeColor{ 255, 255, 255, 255 };
const VitaThemeColor dialog_mode_checked_text = dark_theme ? dialog_text : dialog_selected_text;
const VitaThemeColor renderer_vulkan = VitaThemeColor{ 255, 68, 68, 255 };
const VitaThemeColor renderer_opengl = VitaThemeColor{ 68, 136, 255, 255 };
const VitaThemeColor status_high = VitaThemeColor{ 255, 136, 0, 255 };
const VitaThemeColor status_standard = text;
const VitaThemeColor status_neutral = text;
const VitaThemeColor status_audio = dark_theme ? VitaThemeColor{ 136, 221, 68, 255 } : VitaThemeColor{ 75, 155, 47, 255 };
const VitaThemeColor status_muted = text;
const VitaThemeColor status_update = dark_theme ? VitaThemeColor{ 109, 224, 137, 255 } : VitaThemeColor{ 31, 143, 78, 255 };
const VitaThemeColor log_fatal = ensure_readable(VitaThemeColor{ 255, 159, 243, 255 }, window);
const VitaThemeColor log_error = ensure_readable(VitaThemeColor{ 255, 154, 127, 255 }, window);
const VitaThemeColor log_warning = ensure_readable(VitaThemeColor{ 255, 211, 127, 255 }, window);
const VitaThemeColor log_debug = ensure_readable(VitaThemeColor{ 140, 212, 255, 255 }, window);
if (std::abs(luminance(muted_text) - luminance(text)) < 0.08)
muted_text = ensure_readable(mix(text, window, dark_theme ? 0.32 : 0.48), window);
if ((std::abs(luminance(border) - luminance(window)) < 0.11)
|| (color_distance(border, window) < 0.22)) {
border = neutral_border;
}
const std::uint8_t dialog_alpha = readability_alpha(readability_percent, 188, 252);
const std::uint8_t chrome_alpha = readability_alpha(readability_percent, 58, 196);
const std::uint8_t chrome_border_alpha = readability_alpha(readability_percent, 18, 64);
const std::uint8_t control_alpha = readability_alpha(readability_percent, 26, 176);
const std::uint8_t input_alpha = readability_alpha(readability_percent, 32, 188);
const std::uint8_t sidebar_alpha = readability_alpha(readability_percent, 28, 210);
const std::uint8_t surface_alpha = readability_alpha(readability_percent, 10, 156);
const std::uint8_t pane_alpha = readability_alpha(readability_percent, 12, 190);
const std::uint8_t search_alpha = readability_alpha(readability_percent, 20, 210);
const std::uint8_t progress_alpha = readability_alpha(readability_percent, 28, 140);
const std::uint8_t scroll_alpha = readability_alpha(readability_percent, 18, 110);
const std::uint8_t hover_alpha = readability_alpha(readability_percent, 44, 96);
const std::uint8_t overlay_selected_alpha = dark_theme
? readability_alpha(readability_percent, 28, 82)
: readability_alpha(readability_percent, 42, 132);
const std::uint8_t overlay_selected_soft_alpha = dark_theme
? readability_alpha(readability_percent, 18, 54)
: readability_alpha(readability_percent, 28, 94);
const std::uint8_t overlay_hover_alpha = dark_theme
? readability_alpha(readability_percent, 16, 42)
: readability_alpha(readability_percent, 12, 34);
const std::uint8_t dialog_selected_alpha = dark_theme
? readability_alpha(readability_percent, 28, 78)
: readability_alpha(readability_percent, 96, 220);
const std::string icon_suffix = dark_theme ? "light" : "dark";
const std::string check_icon_suffix = luminance(highlight_text) > 0.5 ? "light" : "dark";
const std::string background_url = stylesheet_path_url(background_option.asset_path, pref_path);
ThemeTemplateContext context;
const auto put = [&context](const std::string &key, std::string value) {
context.strings[key] = std::move(value);
};
const auto put_hex = [&put](const std::string &key, const VitaThemeColor &color) {
put(key, css_hex(color));
};
const auto put_rgba = [&put](const std::string &key, const VitaThemeColor &color, const int alpha) {
put(key, css_rgba(color, static_cast<std::uint8_t>(std::clamp(alpha, 0, 255))));
};
const auto put_argb = [&put](const std::string &key, const VitaThemeColor &color, const int alpha) {
put(key, css_argb(color, static_cast<std::uint8_t>(std::clamp(alpha, 0, 255))));
};
put("generated_background_images", join_stylesheet_path_urls(background_image_paths, pref_path));
put("generated_background_interval_seconds", std::to_string(std::clamp(cycle_interval_seconds, 5, 120)));
put("generated_background_animated", cycle_enabled ? "true" : "false");
put("palette_dark", dark_theme ? "dark" : "light");
put_hex("window_hex", window);
put_hex("text_hex", text);
put_hex("base_hex", base);
put_hex("alternate_base_hex", alternate_base);
put_hex("button_hex", button);
put_hex("button_text_hex", button_text);
put_hex("button_bright_text_hex", contrasting_text(button));
put_hex("accent_hex", accent);
put_argb("highlight_argb", overlay_selection_fill, overlay_selected_alpha);
put_argb("inactive_highlight_argb", overlay_selection_fill, overlay_selected_soft_alpha);
put_argb("disabled_highlight_argb", overlay_selection_fill, overlay_hover_alpha);
put_hex("highlight_text_hex", overlay_selection_text);
put_hex("disabled_text_hex", disabled_text);
put_hex("muted_text_hex", muted_text);
put_hex("overlay_disabled_text_hex", overlay_disabled_text);
put_hex("dialog_text_hex", dialog_text);
put_hex("dialog_muted_text_hex", dialog_muted_text);
put_hex("dialog_disabled_text_hex", dialog_disabled_text);
put_hex("selected_text_hex", selected_text);
put_hex("push_button_pressed_text_hex", highlight_text);
put_hex("dialog_selected_text_hex", dialog_selected_text);
put_hex("dialog_mode_checked_text_hex", dialog_mode_checked_text);
put_hex("toolbar_icon_tint_hex", toolbar_icon_tint);
put_hex("renderer_vulkan_hex", renderer_vulkan);
put_hex("renderer_opengl_hex", renderer_opengl);
put_hex("status_high_hex", status_high);
put_hex("status_standard_hex", status_standard);
put_hex("status_neutral_hex", status_neutral);
put_hex("status_audio_hex", status_audio);
put_hex("status_muted_hex", status_muted);
put_hex("status_update_hex", status_update);
put_hex("log_fatal_hex", log_fatal);
put_hex("log_error_hex", log_error);
put_hex("log_warning_hex", log_warning);
put_hex("log_debug_hex", log_debug);
put_hex("severity_success_text_hex", contrasting_text(status_audio));
put_hex("severity_error_text_hex", contrasting_text(log_error));
put("background_url", background_url);
put("icon_suffix", icon_suffix);
put("check_icon_suffix", check_icon_suffix);
put_rgba("dialog_bg", base, dialog_alpha);
put_rgba("menu_bar_bg", base, chrome_alpha);
put_rgba("menu_bar_border", border, chrome_border_alpha);
put_rgba("menu_bar_item_hover_bg", hover_surface, hover_alpha);
put_rgba("menu_bg", base, 245);
put_rgba("menu_border", border, 40);
put_rgba("menu_selected_bg", overlay_selection_fill, overlay_selected_soft_alpha);
put_rgba("menu_separator_bg", border, 30);
put_rgba("toolbar_bg", base, chrome_alpha);
put_rgba("toolbar_border_top", border, chrome_border_alpha + 8);
put_rgba("toolbar_border_bottom", border, chrome_border_alpha);
put_rgba("toolbar_separator_bg", border, 40);
put_rgba("tool_button_hover_bg", hover_surface, hover_alpha);
put_rgba("tool_button_hover_border", border, chrome_border_alpha + 6);
put_rgba("tool_button_pressed_bg", accent, 54);
put_rgba("welcome_button_bg", button, 88);
put_rgba("welcome_button_border", border, 36);
put_rgba("welcome_button_hover_bg", hover_surface, 88);
put_rgba("welcome_button_hover_border", border, 48);
put_rgba("welcome_button_pressed_bg", accent, 56);
put_rgba("welcome_menu_button_border", border, 36);
put_rgba("welcome_menu_button_bg", base, 28);
put_rgba("welcome_menu_button_hover_bg", hover_surface, 74);
put_rgba("welcome_menu_button_hover_border", border, 48);
put_rgba("welcome_menu_button_pressed_bg", accent, 48);
put_rgba("push_button_bg", button, control_alpha);
put_rgba("push_button_border", border, 36);
put_rgba("push_button_hover_bg", hover_surface, hover_alpha + 12);
put_rgba("push_button_hover_border", border, chrome_border_alpha + 18);
put_rgba("push_button_pressed_bg", accent, 62);
put_rgba("push_button_disabled_bg", base, 72);
put_rgba("push_button_disabled_border", border, 20);
put_rgba("push_button_default_border", accent, 110);
put_rgba("vita_list_bg", dialog_sidebar_fill, sidebar_alpha);
put_rgba("vita_list_selected_bg", dialog_selected_fill, dialog_selected_alpha);
put_rgba("vita_list_hover_bg", dialog_hover_fill, dark_theme ? hover_alpha + 8 : hover_alpha);
put_rgba("background_list_bg", line_edit_fill, pane_alpha);
put_rgba("background_list_border", border, 26);
put_rgba("background_list_selected_bg", dialog_selected_fill, dialog_selected_alpha);
put_rgba("background_list_hover_bg", dialog_hover_fill, dark_theme ? hover_alpha + 6 : hover_alpha + 4);
put_rgba("vita_help_bg", dialog_help_fill, pane_alpha);
put_rgba("controls_mode_hover_bg", dialog_hover_fill, dark_theme ? hover_alpha : hover_alpha + 10);
put_rgba("controls_mode_checked_bg", dialog_selected_fill, dialog_selected_alpha);
put_rgba("controls_mode_pressed_bg", dialog_selected_fill, dark_theme ? dialog_selected_alpha + 16 : std::min(255, dialog_selected_alpha + 24));
put_rgba("controls_button_bg", dialog_button_fill, control_alpha);
put_rgba("controls_button_hover_bg", dialog_hover_fill, dark_theme ? hover_alpha + 10 : hover_alpha + 6);
put_rgba("controls_button_pressed_bg", dialog_hover_fill, dark_theme ? hover_alpha + 20 : hover_alpha + 18);
put_rgba("controls_button_disabled_bg", dialog_button_fill, std::max(40, control_alpha - 28));
put_rgba("controls_bind_bg", dialog_bind_fill, input_alpha);
put_rgba("controls_bind_hover_bg", dialog_hover_fill, dark_theme ? hover_alpha + 6 : hover_alpha + 4);
put_rgba("controls_bind_pressed_bg", dialog_hover_fill, dark_theme ? hover_alpha + 16 : hover_alpha + 14);
put_rgba("controls_capture_bg", dialog_selected_fill, dark_theme ? dialog_selected_alpha + 18 : dialog_selected_alpha);
put_rgba("controls_capture_hover_bg", dialog_selected_fill, dark_theme ? dialog_selected_alpha + 28 : std::min(255, dialog_selected_alpha + 18));
put_rgba("controls_capture_pressed_bg", dialog_selected_fill, dark_theme ? dialog_selected_alpha + 38 : std::min(255, dialog_selected_alpha + 28));
put_rgba("status_bar_bg", base, chrome_alpha + 18);
put_rgba("status_bar_border", border, 26);
put_rgba("status_button_hover_border", border, 32);
put_rgba("status_button_hover_bg", hover_surface, overlay_hover_alpha);
put_rgba("status_button_pressed_border", border, 40);
put_rgba("status_button_pressed_bg", overlay_selection_fill, overlay_selected_soft_alpha);
put_rgba("line_edit_bg", line_edit_fill, input_alpha);
put_rgba("line_edit_border", border, 36);
put_rgba("line_edit_selection_bg", overlay_selection_fill, overlay_selected_alpha);
put_rgba("line_edit_focus_border", accent, 96);
put_rgba("line_edit_disabled_bg", base, 76);
put_rgba("line_edit_disabled_border", border, 20);
put_rgba("main_search_border", border, 36);
put_rgba("main_search_selection_bg", overlay_selection_fill, overlay_selected_soft_alpha);
put_rgba("main_search_focus_bg", base, 18);
put_rgba("main_search_focus_border", accent, 72);
put_rgba("combo_bg", line_edit_fill, input_alpha);
put_rgba("combo_border", border, 36);
put_rgba("combo_hover_border", border, 54);
put_rgba("combo_disabled_bg", base, 76);
put_rgba("combo_disabled_border", border, 20);
put_rgba("combo_popup_bg", base, 244);
put_rgba("combo_popup_border", border, 40);
put_rgba("combo_popup_selection_bg", overlay_selection_fill, overlay_selected_soft_alpha);
put_rgba("spin_bg", line_edit_fill, input_alpha);
put_rgba("spin_border", border, 36);
put_rgba("spin_selection_bg", overlay_selection_fill, overlay_selected_alpha);
put_rgba("spin_disabled_bg", base, 76);
put_rgba("spin_disabled_border", border, 20);
put_rgba("indicator_border", border, 92);
put_rgba("indicator_bg", checkbox_fill, std::max(62, static_cast<int>(input_alpha) - 12));
put_rgba("indicator_hover_border", accent, 138);
put_rgba("indicator_hover_bg", checkbox_fill, std::max(82, static_cast<int>(input_alpha) + 6));
put_rgba("indicator_checked_bg", accent, 196);
put_rgba("indicator_checked_border", accent, 204);
put_rgba("indicator_disabled_border", border, 54);
put_rgba("indicator_disabled_bg", base, 88);
put_rgba("indicator_checked_disabled_bg", accent, 96);
put_rgba("indicator_checked_disabled_border", accent, 108);
put_rgba("radio_indicator_border", border, 92);
put_rgba("radio_indicator_bg", checkbox_fill, std::max(62, static_cast<int>(input_alpha) - 12));
put_rgba("radio_indicator_hover_border", accent, 138);
put_rgba("radio_indicator_hover_bg", checkbox_fill, std::max(82, static_cast<int>(input_alpha) + 6));
put_rgba("radio_checked_bg", accent, 196);
put_rgba("radio_checked_border", accent, 204);
put_rgba("radio_checked_disabled_bg", accent, 96);
put_rgba("radio_checked_disabled_border", accent, 108);
put_rgba("group_border", border, 32);
put_rgba("group_disabled_border", border, 18);
put_rgba("dock_title_bg", base, chrome_alpha);
put_rgba("dock_button_hover_bg", hover_surface, hover_alpha + 8);
put_rgba("apps_surface_bg", base, surface_alpha);
put_rgba("alt_row_bg", text, std::max<std::uint8_t>(8, surface_alpha / 2));
put_rgba("table_gridline", border, 18);
put_rgba("table_disabled_bg", base, 54);
put_rgba("table_corner_bg", darken(base, 0.08), 80);
put_rgba("table_corner_border", border, 24);
put_rgba("table_selected_bg", overlay_selection_fill, overlay_selected_alpha);
put_rgba("header_bg", darken(base, 0.08), surface_alpha);
put_rgba("header_border", border, 18);
put_rgba("header_hover_bg", hover_surface, 84);
put_rgba("log_search_bg", subtle_surface, search_alpha);
put_rgba("log_search_border", border, 30);
put_rgba("toolbar_slider_groove_bg", text, 24);
put_rgba("toolbar_slider_subpage_bg", text, 228);
put_rgba("toolbar_slider_addpage_bg", text, 20);
put_hex("toolbar_slider_handle_bg", lighten(text, dark_theme ? 0.0 : 0.12));
put_hex("toolbar_slider_handle_hover_bg", lighten(text, 0.12));
put_rgba("severity_success_bg", status_audio, 124);
put_rgba("severity_error_bg", log_error, 124);
put_rgba("tab_pane_border", border, 24);
put_rgba("tab_pane_bg", base, pane_alpha);
put_rgba("tab_bg", subtle_surface, sidebar_alpha);
put_rgba("tab_border", border, 24);
put_rgba("tab_selected_bg", base, pane_alpha);
put_rgba("tab_hover_bg", hover_surface, hover_alpha);
put_rgba("list_bg", base, pane_alpha);
put_rgba("list_border", border, 22);
put_rgba("list_disabled_bg", base, 54);
put_rgba("list_disabled_border", border, 16);
put_rgba("list_selected_bg", overlay_selection_fill, overlay_selected_alpha);
put_rgba("list_hover_bg", hover_surface, hover_alpha);
put_rgba("settings_category_bg", dialog_sidebar_fill, sidebar_alpha);
put_rgba("settings_category_selected_bg", dialog_selected_fill, dialog_selected_alpha);
put_rgba("settings_category_hover_bg", dialog_hover_fill, dark_theme ? hover_alpha + 8 : hover_alpha);
put_rgba("modules_list_bg", line_edit_fill, pane_alpha);
put_rgba("modules_list_border", border, 30);
put_rgba("modules_list_hover_bg", dialog_hover_fill, dark_theme ? hover_alpha + 4 : hover_alpha + 8);
put_rgba("settings_help_bg", dialog_help_fill, pane_alpha);
put_rgba("scrollbar_bg", base, scroll_alpha);
put_rgba("scrollbar_handle_bg", text, scroll_alpha + 22);
put_rgba("scrollbar_handle_hover_bg", text, scroll_alpha + 40);
put_rgba("slider_subpage_bg", text, 224);
put_rgba("slider_addpage_bg", text, 28);
put_hex("slider_handle_bg", lighten(text, dark_theme ? 0.0 : 0.12));
put_hex("slider_handle_hover_bg", lighten(text, 0.12));
put_rgba("progress_bg", base, progress_alpha);
put_rgba("progress_border", border, 22);
put_hex("progress_chunk_bg", accent);
put_rgba("splitter_handle_bg", border, 34);
put_rgba("tooltip_bg", base, 245);
put_rgba("tooltip_border", border, 40);
put_rgba("text_edit_bg", base, pane_alpha);
put_rgba("text_edit_border", border, 22);
put_rgba("text_edit_disabled_bg", base, 54);
put_rgba("text_edit_disabled_border", border, 16);
const auto rendered = render_stylesheet_template(context);
return rendered.value_or(std::string());
}
} // namespace
std::optional<VitaThemeInfo> load_vita_theme(
const fs::path &theme_dir,
const std::string &preferred_locale) {
const fs::path theme_xml_path = theme_dir / "theme.xml";
if (!fs::exists(theme_xml_path))
return std::nullopt;
std::vector<char> xml_buffer;
if (!fs_utils::read_data(theme_xml_path, xml_buffer)) {
LOG_WARN("Failed to read Vita theme metadata from {}", theme_xml_path);
return std::nullopt;
}
pugi::xml_document document;
const auto parse_result = document.load_buffer(xml_buffer.data(), xml_buffer.size());
if (!parse_result) {
LOG_WARN("Failed to parse Vita theme metadata from {}: {}", theme_xml_path, parse_result.description());
return std::nullopt;
}
const auto theme_root = document.child("theme");
if (!theme_root) {
LOG_WARN("Theme metadata at {} is missing the root <theme> node", theme_xml_path);
return std::nullopt;
}
VitaThemeInfo theme;
theme.installed_path = theme_dir;
const auto info_node = theme_root.child("InfomationProperty");
const auto info_bar_node = theme_root.child("InfomationBarProperty");
const auto home_node = theme_root.child("HomeProperty");
const auto start_node = theme_root.child("StartScreenProperty");
theme.content_id = child_text(info_node, "m_contentId");
theme.title = localized_text(info_node.child("m_title"), preferred_locale);
theme.provider = localized_text(info_node.child("m_provider"), preferred_locale);
theme.content_version = child_text(info_node, "m_contentVer");
theme.theme_id = vita_theme_utils::resolve_theme_id(theme.content_id, path_leaf_string(theme_dir), theme.title);
if (theme.title.empty())
theme.title = theme.theme_id;
const std::string package_thumbnail_raw = [&]() {
if (const std::string package_image = child_text(info_node, "m_packageImageFilePath"); !package_image.empty())
return package_image;
return child_text(info_node, "m_previewFilePath");
}();
if (const auto package_thumbnail = existing_asset_path(theme_dir, package_thumbnail_raw))
theme.package_thumbnail_path = *package_thumbnail;
const std::string bgm_raw_path = child_text(home_node, "m_bgmFilePath");
if (const auto bgm_path = existing_asset_path(theme_dir, bgm_raw_path)) {
theme.bgm_path = *bgm_path;
} else if (!bgm_raw_path.empty()) {
LOG_WARN("Skipping missing Vita theme BGM '{}' for theme '{}'", bgm_raw_path, theme.theme_id);
}
theme.palette.information_bar_color = parse_theme_color(child_text(info_bar_node, "m_barColor"));
theme.palette.information_indicator_color = parse_theme_color(child_text(info_bar_node, "m_indicatorColor"));
theme.palette.notify_background_color = parse_theme_color(child_text(start_node, "m_notifyBgColor"));
theme.palette.notify_border_color = parse_theme_color(child_text(start_node, "m_notifyBorderColor"));
theme.palette.notify_font_color = parse_theme_color(child_text(start_node, "m_notifyFontColor"));
theme.palette.date_color = parse_theme_color(child_text(start_node, "m_dateColor"));
int home_index = 0;
for (const auto &background_node : collect_background_nodes(home_node)) {
const std::string raw_path = child_text(background_node, "m_imageFilePath");
if (raw_path.empty())
continue;
const auto asset_path = existing_asset_path(theme_dir, raw_path);
if (!asset_path) {
LOG_WARN("Skipping missing Vita home background '{}' for theme '{}'", raw_path, theme.theme_id);
continue;
}
VitaThemeBackgroundOption option;
option.id = fmt::format("home-{}", home_index);
option.label = fmt::format("Home Background {}", home_index + 1);
option.asset_path = *asset_path;
option.source = VitaThemeBackgroundSource::Home;
option.primary_text_color = parse_theme_color(child_text(background_node, "m_fontColor"));
theme.background_options.push_back(std::move(option));
++home_index;
}
const std::string raw_lockscreen_path = child_text(start_node, "m_filePath");
if (const auto lockscreen_path = existing_asset_path(theme_dir, raw_lockscreen_path)) {
VitaThemeBackgroundOption option;
option.id = "lockscreen";
option.label = "Lockscreen";
option.asset_path = *lockscreen_path;
option.source = VitaThemeBackgroundSource::Lockscreen;
option.primary_text_color = theme.palette.notify_font_color;
theme.background_options.push_back(std::move(option));
} else if (!raw_lockscreen_path.empty()) {
LOG_WARN("Skipping missing Vita lockscreen background '{}' for theme '{}'", raw_lockscreen_path, theme.theme_id);
}
collect_directory_metrics(theme_dir, theme.installed_size, theme.updated_time);
if (theme.background_options.empty()) {
LOG_WARN("Theme '{}' has no usable background assets, skipping", theme.theme_id);
return std::nullopt;
}
return theme;
}
std::vector<VitaThemeInfo> enumerate_installed_vita_themes(
const fs::path &themes_root,
const std::string &preferred_locale) {
std::vector<VitaThemeInfo> themes;
if (!fs::exists(themes_root) || !fs::is_directory(themes_root))
return themes;
std::vector<fs::path> theme_paths;
for (fs::directory_iterator it(themes_root), end; it != end; ++it) {
if (fs::is_directory(it->path()))
theme_paths.push_back(it->path());
}
std::sort(theme_paths.begin(), theme_paths.end(), [](const fs::path &lhs, const fs::path &rhs) {
return fs_utils::path_to_utf8(lhs.filename()) < fs_utils::path_to_utf8(rhs.filename());
});
for (const auto &theme_path : theme_paths) {
if (auto theme = load_vita_theme(theme_path, preferred_locale))
themes.push_back(std::move(*theme));
}
return themes;
}
std::optional<fs::path> synthesize_vita_theme_qss(
const VitaThemeInfo &theme,
const std::string &background_option_id,
const int readability_percent,
const bool normalize_font_colors,
const std::vector<fs::path> &background_image_paths,
const int cycle_interval_seconds,
const bool cycle_enabled,
const fs::path &pref_path,
const fs::path &gui_settings_dir) {
const auto *selected_background = find_background_option(theme, background_option_id);
if (!selected_background) {
LOG_ERROR("Unable to find Vita theme background option '{}' for theme '{}'",
background_option_id,
theme.theme_id);
return std::nullopt;
}
if (!fs::exists(selected_background->asset_path)) {
LOG_ERROR("Vita theme asset is missing: {}", selected_background->asset_path);
return std::nullopt;
}
const fs::path output_directory = gui_settings_dir / "vita-themes" / theme.theme_id;
const fs::path qss_path = output_directory / "generated.qss";
const std::string stylesheet = build_stylesheet(
theme,
*selected_background,
std::clamp(readability_percent, 0, 100),
normalize_font_colors,
background_image_paths.empty() ? std::vector<fs::path>{ selected_background->asset_path } : background_image_paths,
cycle_interval_seconds,
cycle_enabled,
pref_path);
boost::system::error_code error_code;
fs::create_directories(output_directory, error_code);
if (error_code) {
LOG_ERROR("Failed to create Vita theme output directory {}: {}",
output_directory,
error_code.message());
return std::nullopt;
}
fs::ofstream output(qss_path, fs::ofstream::binary | fs::ofstream::trunc);
if (!output) {
LOG_ERROR("Failed to open generated Vita theme stylesheet path {}", qss_path);
return std::nullopt;
}
output.write(stylesheet.data(), static_cast<std::streamsize>(stylesheet.size()));
if (!output.good()) {
LOG_ERROR("Failed to write generated Vita theme stylesheet {}", qss_path);
return std::nullopt;
}
return qss_path;
}
} // namespace gui
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="M3.2 8.4 6.3 11.5 12.8 5" fill="none" stroke="#09131A" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.2"/>
</svg>

After

Width:  |  Height:  |  Size: 202 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="M3.2 8.4 6.3 11.5 12.8 5" fill="none" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.2"/>
</svg>

After

Width:  |  Height:  |  Size: 202 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="3" fill="#09131A"/>
</svg>

After

Width:  |  Height:  |  Size: 115 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="3" fill="#FFFFFF"/>
</svg>

After

Width:  |  Height:  |  Size: 115 B

+38 -10
View File
@@ -44,6 +44,7 @@
#include <util/log.h>
#include <util/string_utils.h>
#include <util/vector_utils.h>
#include <util/vita_theme_utils.h>
#include <gdbstub/functions.h>
#include <stb_image_write.h>
@@ -76,6 +77,18 @@ static const char *miniz_get_error(const ZipPtr &zip) {
return mz_zip_get_error_string(mz_zip_get_last_error(zip.get()));
}
static std::string fallback_theme_root_name(const std::string &content_path) {
std::string trimmed = content_path;
while (!trimmed.empty() && ((trimmed.back() == '/') || (trimmed.back() == '\\')))
trimmed.pop_back();
if (trimmed.empty())
return {};
const auto separator = trimmed.find_last_of("/\\");
return (separator == std::string::npos) ? trimmed : trimmed.substr(separator + 1);
}
static bool is_nonpdrm(EmuEnvState &emuenv, const fs::path &output_path) {
const auto app_license_path{ emuenv.pref_path / "ux0/license" / emuenv.app_info.app_title_id / fmt::format("{}.rif", emuenv.app_info.app_content_id) };
const auto is_patch_found_app_license = (emuenv.app_info.app_category == "gp") && fs::exists(app_license_path);
@@ -120,18 +133,32 @@ static bool set_content_path(EmuEnvState &emuenv, const bool is_theme, fs::path
return true;
}
static void set_theme_name(EmuEnvState &emuenv, const vfs::FileBuffer &buffer) {
static void set_theme_name(EmuEnvState &emuenv, const vfs::FileBuffer &buffer, const std::string &fallback_id_hint = {}) {
std::string content_id;
std::string title;
pugi::xml_document doc;
if (doc.load_buffer(buffer.data(), buffer.size())) {
const auto info = doc.child("theme").child("InfomationProperty");
const char *content_id = info.child("m_contentId").text().as_string();
const char *title = info.child("m_title").child("m_default").text().as_string();
if (content_id[0] != '\0') {
emuenv.app_info.app_content_id = content_id;
emuenv.app_info.app_title_id = content_id;
content_id = info.child("m_contentId").text().as_string();
title = info.child("m_title").child("m_default").text().as_string();
} else {
LOG_WARN("Unable to parse theme.xml metadata during install, falling back to folder/title-derived theme identity");
}
if (title[0] != '\0')
const std::string resolved_id = vita_theme_utils::resolve_theme_id(
content_id,
fallback_id_hint,
title,
buffer.empty() ? nullptr : buffer.data(),
buffer.size());
emuenv.app_info.app_content_id = resolved_id;
emuenv.app_info.app_title_id = resolved_id;
if (!title.empty()) {
emuenv.app_info.app_title = title;
} else if (emuenv.app_info.app_title.empty()) {
emuenv.app_info.app_title = resolved_id;
}
}
@@ -141,6 +168,7 @@ static bool install_archive_content(EmuEnvState &emuenv, const ZipPtr &zip, cons
vfs::FileBuffer buffer, theme;
const auto is_theme = mz_zip_reader_extract_file_to_callback(zip.get(), (content_path + theme_path).c_str(), &write_to_buffer, &theme, 0);
const std::string theme_root_name = fallback_theme_root_name(content_path);
auto output_path{ emuenv.pref_path / "ux0" };
if (mz_zip_reader_extract_file_to_callback(zip.get(), (content_path + sfo_path).c_str(), &write_to_buffer, &buffer, 0)) {
@@ -148,7 +176,7 @@ static bool install_archive_content(EmuEnvState &emuenv, const ZipPtr &zip, cons
if (!set_content_path(emuenv, is_theme, output_path))
return false;
} else if (is_theme) {
set_theme_name(emuenv, theme);
set_theme_name(emuenv, theme, theme_root_name);
output_path /= fs::path("theme") / emuenv.app_info.app_content_id;
} else {
LOG_CRITICAL("miniz error: {} extracting file: {}", miniz_get_error(zip), sfo_path);
@@ -321,7 +349,7 @@ static bool install_content(EmuEnvState &emuenv, const fs::path &content_path) {
fs::remove_all(dst_path);
} else if (fs_utils::read_data(theme_path, buffer)) {
set_theme_name(emuenv, buffer);
set_theme_name(emuenv, buffer, fs_utils::path_to_utf8(content_path.filename()));
dst_path /= fs::path("theme") / fs_utils::utf8_to_path(emuenv.app_info.app_title_id);
} else {
LOG_ERROR("Param.sfo file is missing in path", sfo_path);
@@ -434,7 +462,7 @@ static ExitCode load_app_impl(SceUID &main_module_id, EmuEnvState &emuenv, const
} else
return FileNotFound;
// Set self name from self path, can contain folder, get file name only
emuenv.self_name = fs::path(emuenv.self_path).filename().string();
emuenv.self_name = fs_utils::path_to_utf8(fs::path(emuenv.self_path).filename());
// get list of preload modules
SceUInt32 process_preload_disabled = 0;
+11
View File
@@ -7,10 +7,21 @@
<file>icons/silver.png</file>
<file>icons/gold.png</file>
<file>icons/platinum.png</file>
<file alias="themes/light/light.qss">gui-qt/resources/themes/light/light.qss</file>
<file alias="themes/light/chevron_down_dark.svg">icons/chevron_down_dark.svg</file>
<file alias="themes/light/chevron_up_dark.svg">icons/chevron_up_dark.svg</file>
<file alias="themes/dark/dark.qss">gui-qt/resources/themes/dark/dark.qss</file>
<file alias="themes/dark/chevron_down_light.svg">icons/chevron_down_light.svg</file>
<file alias="themes/dark/chevron_up_light.svg">icons/chevron_up_light.svg</file>
<file alias="themes/vita/generated.qss.in">gui-qt/resources/themes/vita/generated.qss.in</file>
<file>icons/configure.png</file>
<file>icons/controllers.png</file>
<file>icons/chevron_down_dark.svg</file>
<file>icons/chevron_down_light.svg</file>
<file>icons/check_dark.svg</file>
<file>icons/check_light.svg</file>
<file>icons/radio_dot_dark.svg</file>
<file>icons/radio_dot_light.svg</file>
<file>icons/cross_dark.svg</file>
<file>icons/cross_light.svg</file>
<file>icons/chevron_up_dark.svg</file>
+1
View File
@@ -12,6 +12,7 @@ add_library(
src/net_utils.cpp
src/string_utils.cpp
src/tracy.cpp
src/vita_theme_utils.cpp
)
# vc_runtime_checker.cpp is directly added from the main CMakeList (for some reason adding it here doesn't work)
+1
View File
@@ -29,6 +29,7 @@ std::wstring utf_to_wide(const std::string &str);
std::string wide_to_utf(const std::wstring &str);
std::string utf16_to_utf8(const std::u16string &str);
std::u16string utf8_to_utf16(const std::string &str);
std::string trim_copy(const std::string &str);
std::string remove_special_chars(std::string str);
void replace(std::string &str, const std::string &in, const std::string &out);
std::vector<uint8_t> string_to_byte_array(const std::string &string);
@@ -0,0 +1,34 @@
// Vita3K emulator project
// Copyright (C) 2026 Vita3K team
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
namespace vita_theme_utils {
std::string sanitize_theme_id(std::string value);
std::string resolve_theme_id(
const std::string &content_id,
const std::string &folder_name,
const std::string &title,
const std::uint8_t *fallback_hash_data = nullptr,
std::size_t fallback_hash_size = 0);
} // namespace vita_theme_utils
+14
View File
@@ -53,6 +53,20 @@ std::string wide_to_utf(const std::wstring &str) {
return myconv.to_bytes(str);
}
std::string trim_copy(const std::string &str) {
const auto begin = std::find_if_not(str.begin(), str.end(), [](const unsigned char ch) {
return std::isspace(ch) != 0;
});
const auto end = std::find_if_not(str.rbegin(), str.rend(), [](const unsigned char ch) {
return std::isspace(ch) != 0;
}).base();
if (begin >= end)
return {};
return std::string(begin, end);
}
std::string remove_special_chars(std::string str) {
for (char &c : str) {
switch (c) {
+81
View File
@@ -0,0 +1,81 @@
// Vita3K emulator project
// Copyright (C) 2026 Vita3K team
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <util/vita_theme_utils.h>
#include <util/string_utils.h>
#include <fmt/format.h>
#include <cctype>
namespace vita_theme_utils {
std::string sanitize_theme_id(std::string value) {
value = string_utils::trim_copy(string_utils::remove_special_chars(std::move(value)));
std::string sanitized;
sanitized.reserve(value.size());
bool last_was_separator = false;
for (const unsigned char raw_char : value) {
const char ch = static_cast<char>(raw_char);
if (std::isalnum(raw_char) || (ch == '-') || (ch == '_')) {
sanitized.push_back(ch);
last_was_separator = false;
} else if (!last_was_separator) {
sanitized.push_back('_');
last_was_separator = true;
}
}
while (!sanitized.empty() && ((sanitized.front() == '_') || (sanitized.front() == '-')))
sanitized.erase(sanitized.begin());
while (!sanitized.empty() && ((sanitized.back() == '_') || (sanitized.back() == '-')))
sanitized.pop_back();
return sanitized;
}
std::string resolve_theme_id(
const std::string &content_id,
const std::string &folder_name,
const std::string &title,
const std::uint8_t *fallback_hash_data,
const std::size_t fallback_hash_size) {
if (const std::string trimmed_content_id = string_utils::trim_copy(content_id); !trimmed_content_id.empty())
return trimmed_content_id;
if (const std::string sanitized_folder = sanitize_theme_id(folder_name); !sanitized_folder.empty())
return sanitized_folder;
if (const std::string sanitized_title = sanitize_theme_id(title); !sanitized_title.empty())
return sanitized_title;
if (fallback_hash_data && fallback_hash_size > 0) {
uint32_t hash = 2166136261u;
for (std::size_t index = 0; index < fallback_hash_size; ++index) {
hash ^= fallback_hash_data[index];
hash *= 16777619u;
}
return fmt::format("theme-{:08X}", hash);
}
return "theme";
}
} // namespace vita_theme_utils