From a99fd021d71e9862d6471858a68b3b8a344b2c83 Mon Sep 17 00:00:00 2001 From: crueter Date: Mon, 16 Jun 2025 19:05:20 -0400 Subject: [PATCH 1/4] merge Signed-off-by: crueter --- src/CMakeLists.txt | 2 +- src/eden/.gitignore | 82 +++ src/eden/CMakeLists.txt | 82 +++ src/eden/gamepad/CMakeLists.txt | 9 + src/eden/gamepad/gamepad.cpp | 78 +++ src/eden/gamepad/gamepad.h | 31 + src/eden/icons.qrc | 14 + src/eden/icons/audio.svg | 1 + src/eden/icons/back.svg | 39 ++ src/eden/icons/controls.svg | 1 + src/eden/icons/cpu.svg | 1 + src/eden/icons/debug.svg | 1 + src/eden/icons/forward.svg | 39 ++ src/eden/icons/general.svg | 1 + src/eden/icons/graphics.svg | 60 ++ src/eden/icons/help.svg | 1 + src/eden/icons/system.svg | 1 + src/eden/interface/CMakeLists.txt | 13 + src/eden/interface/MetaObjectHelper.h | 22 + src/eden/interface/QMLSetting.cpp | 261 ++++++++ src/eden/interface/QMLSetting.h | 105 ++++ src/eden/interface/SettingsInterface.cpp | 124 ++++ src/eden/interface/SettingsInterface.h | 84 +++ src/eden/interface/qt_config.cpp | 562 +++++++++++++++++ src/eden/interface/qt_config.h | 55 ++ src/eden/interface/shared_translation.cpp | 569 ++++++++++++++++++ src/eden/interface/shared_translation.h | 65 ++ src/eden/interface/uisettings.cpp | 112 ++++ src/eden/interface/uisettings.h | 282 +++++++++ src/eden/main.cpp | 56 ++ src/eden/models/CMakeLists.txt | 16 + src/eden/models/GameListModel.cpp | 70 +++ src/eden/models/GameListModel.h | 39 ++ src/eden/models/SettingsModel.cpp | 90 +++ src/eden/models/SettingsModel.h | 42 ++ src/eden/qml/CMakeLists.txt | 5 + src/eden/qml/config/CMakeLists.txt | 57 ++ src/eden/qml/config/GlobalConfigureDialog.qml | 73 +++ src/eden/qml/config/SectionHeader.qml | 8 + src/eden/qml/config/Setting.qml | 92 +++ src/eden/qml/config/TestSetting.qml | 39 ++ src/eden/qml/config/fields/BaseField.qml | 160 +++++ src/eden/qml/config/fields/ConfigCheckbox.qml | 27 + src/eden/qml/config/fields/ConfigComboBox.qml | 33 + src/eden/qml/config/fields/ConfigHexEdit.qml | 26 + src/eden/qml/config/fields/ConfigIntLine.qml | 28 + .../qml/config/fields/ConfigIntSlider.qml | 39 ++ src/eden/qml/config/fields/ConfigIntSpin.qml | 25 + .../qml/config/fields/ConfigStringEdit.qml | 22 + src/eden/qml/config/fields/ConfigTimeEdit.qml | 27 + src/eden/qml/config/fields/FieldCheckbox.qml | 17 + src/eden/qml/config/fields/FieldLabel.qml | 18 + src/eden/qml/config/icons.qrc | 1 + src/eden/qml/config/pages/SettingsList.qml | 47 ++ .../config/pages/audio/AudioGeneralPage.qml | 17 + .../qml/config/pages/cpu/CpuGeneralPage.qml | 17 + .../config/pages/debug/DebugAdvancedPage.qml | 19 + .../qml/config/pages/debug/DebugCpuPage.qml | 18 + .../config/pages/debug/DebugGeneralPage.qml | 28 + .../config/pages/debug/DebugGraphicsPage.qml | 20 + .../config/pages/general/UiGameListPage.qml | 18 + .../config/pages/general/UiGeneralPage.qml | 23 + .../config/pages/global/GlobalAudioPage.qml | 14 + .../qml/config/pages/global/GlobalCpuPage.qml | 14 + .../config/pages/global/GlobalDebugPage.qml | 17 + .../config/pages/global/GlobalGeneralPage.qml | 17 + .../pages/global/GlobalGraphicsPage.qml | 16 + .../config/pages/global/GlobalSystemPage.qml | 18 + .../qml/config/pages/global/GlobalTab.qml | 34 ++ .../pages/global/GlobalTabSwipeView.qml | 16 + .../pages/graphics/RendererAdvancedPage.qml | 17 + .../pages/graphics/RendererExtensionsPage.qml | 17 + .../config/pages/graphics/RendererPage.qml | 17 + .../qml/config/pages/system/AppletsPage.qml | 17 + .../config/pages/system/FileSystemPage.qml | 17 + .../config/pages/system/SystemCorePage.qml | 17 + .../config/pages/system/SystemGeneralPage.qml | 22 + src/eden/qml/constants/CMakeLists.txt | 23 + src/eden/qml/constants/Constants.qml | 24 + src/eden/qml/items/AnimatedDialog.qml | 79 +++ src/eden/qml/items/BetterMenu.qml | 59 ++ src/eden/qml/items/BetterMenuBar.qml | 33 + src/eden/qml/items/CMakeLists.txt | 22 + src/eden/qml/items/IconButton.qml | 23 + src/eden/qml/items/SettingsTabButton.qml | 40 ++ src/eden/qml/items/StatusBarButton.qml | 36 ++ src/eden/qml/items/VerticalTabBar.qml | 46 ++ src/eden/qml/items/fields/BetterSpinBox.qml | 68 +++ src/eden/qml/items/fields/BetterTextField.qml | 38 ++ src/eden/qml/items/fields/FieldFooter.qml | 20 + src/eden/qml/main/CMakeLists.txt | 13 + src/eden/qml/main/GameList.qml | 141 +++++ src/eden/qml/main/GamePreview.qml | 77 +++ src/eden/qml/main/Main.qml | 207 +++++++ src/eden/qml/main/StatusBar.qml | 160 +++++ 95 files changed, 5292 insertions(+), 1 deletion(-) create mode 100644 src/eden/.gitignore create mode 100644 src/eden/CMakeLists.txt create mode 100644 src/eden/gamepad/CMakeLists.txt create mode 100644 src/eden/gamepad/gamepad.cpp create mode 100644 src/eden/gamepad/gamepad.h create mode 100644 src/eden/icons.qrc create mode 100644 src/eden/icons/audio.svg create mode 100644 src/eden/icons/back.svg create mode 100644 src/eden/icons/controls.svg create mode 100644 src/eden/icons/cpu.svg create mode 100644 src/eden/icons/debug.svg create mode 100644 src/eden/icons/forward.svg create mode 100644 src/eden/icons/general.svg create mode 100644 src/eden/icons/graphics.svg create mode 100644 src/eden/icons/help.svg create mode 100644 src/eden/icons/system.svg create mode 100644 src/eden/interface/CMakeLists.txt create mode 100644 src/eden/interface/MetaObjectHelper.h create mode 100644 src/eden/interface/QMLSetting.cpp create mode 100644 src/eden/interface/QMLSetting.h create mode 100644 src/eden/interface/SettingsInterface.cpp create mode 100644 src/eden/interface/SettingsInterface.h create mode 100644 src/eden/interface/qt_config.cpp create mode 100644 src/eden/interface/qt_config.h create mode 100644 src/eden/interface/shared_translation.cpp create mode 100644 src/eden/interface/shared_translation.h create mode 100644 src/eden/interface/uisettings.cpp create mode 100644 src/eden/interface/uisettings.h create mode 100644 src/eden/main.cpp create mode 100644 src/eden/models/CMakeLists.txt create mode 100644 src/eden/models/GameListModel.cpp create mode 100644 src/eden/models/GameListModel.h create mode 100644 src/eden/models/SettingsModel.cpp create mode 100644 src/eden/models/SettingsModel.h create mode 100644 src/eden/qml/CMakeLists.txt create mode 100644 src/eden/qml/config/CMakeLists.txt create mode 100644 src/eden/qml/config/GlobalConfigureDialog.qml create mode 100644 src/eden/qml/config/SectionHeader.qml create mode 100644 src/eden/qml/config/Setting.qml create mode 100644 src/eden/qml/config/TestSetting.qml create mode 100644 src/eden/qml/config/fields/BaseField.qml create mode 100644 src/eden/qml/config/fields/ConfigCheckbox.qml create mode 100644 src/eden/qml/config/fields/ConfigComboBox.qml create mode 100644 src/eden/qml/config/fields/ConfigHexEdit.qml create mode 100644 src/eden/qml/config/fields/ConfigIntLine.qml create mode 100644 src/eden/qml/config/fields/ConfigIntSlider.qml create mode 100644 src/eden/qml/config/fields/ConfigIntSpin.qml create mode 100644 src/eden/qml/config/fields/ConfigStringEdit.qml create mode 100644 src/eden/qml/config/fields/ConfigTimeEdit.qml create mode 100644 src/eden/qml/config/fields/FieldCheckbox.qml create mode 100644 src/eden/qml/config/fields/FieldLabel.qml create mode 100644 src/eden/qml/config/icons.qrc create mode 100644 src/eden/qml/config/pages/SettingsList.qml create mode 100644 src/eden/qml/config/pages/audio/AudioGeneralPage.qml create mode 100644 src/eden/qml/config/pages/cpu/CpuGeneralPage.qml create mode 100644 src/eden/qml/config/pages/debug/DebugAdvancedPage.qml create mode 100644 src/eden/qml/config/pages/debug/DebugCpuPage.qml create mode 100644 src/eden/qml/config/pages/debug/DebugGeneralPage.qml create mode 100644 src/eden/qml/config/pages/debug/DebugGraphicsPage.qml create mode 100644 src/eden/qml/config/pages/general/UiGameListPage.qml create mode 100644 src/eden/qml/config/pages/general/UiGeneralPage.qml create mode 100644 src/eden/qml/config/pages/global/GlobalAudioPage.qml create mode 100644 src/eden/qml/config/pages/global/GlobalCpuPage.qml create mode 100644 src/eden/qml/config/pages/global/GlobalDebugPage.qml create mode 100644 src/eden/qml/config/pages/global/GlobalGeneralPage.qml create mode 100644 src/eden/qml/config/pages/global/GlobalGraphicsPage.qml create mode 100644 src/eden/qml/config/pages/global/GlobalSystemPage.qml create mode 100644 src/eden/qml/config/pages/global/GlobalTab.qml create mode 100644 src/eden/qml/config/pages/global/GlobalTabSwipeView.qml create mode 100644 src/eden/qml/config/pages/graphics/RendererAdvancedPage.qml create mode 100644 src/eden/qml/config/pages/graphics/RendererExtensionsPage.qml create mode 100644 src/eden/qml/config/pages/graphics/RendererPage.qml create mode 100644 src/eden/qml/config/pages/system/AppletsPage.qml create mode 100644 src/eden/qml/config/pages/system/FileSystemPage.qml create mode 100644 src/eden/qml/config/pages/system/SystemCorePage.qml create mode 100644 src/eden/qml/config/pages/system/SystemGeneralPage.qml create mode 100644 src/eden/qml/constants/CMakeLists.txt create mode 100644 src/eden/qml/constants/Constants.qml create mode 100644 src/eden/qml/items/AnimatedDialog.qml create mode 100644 src/eden/qml/items/BetterMenu.qml create mode 100644 src/eden/qml/items/BetterMenuBar.qml create mode 100644 src/eden/qml/items/CMakeLists.txt create mode 100644 src/eden/qml/items/IconButton.qml create mode 100644 src/eden/qml/items/SettingsTabButton.qml create mode 100644 src/eden/qml/items/StatusBarButton.qml create mode 100644 src/eden/qml/items/VerticalTabBar.qml create mode 100644 src/eden/qml/items/fields/BetterSpinBox.qml create mode 100644 src/eden/qml/items/fields/BetterTextField.qml create mode 100644 src/eden/qml/items/fields/FieldFooter.qml create mode 100644 src/eden/qml/main/CMakeLists.txt create mode 100644 src/eden/qml/main/GameList.qml create mode 100644 src/eden/qml/main/GamePreview.qml create mode 100644 src/eden/qml/main/Main.qml create mode 100644 src/eden/qml/main/StatusBar.qml diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4308534f12..08fed7669b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -212,7 +212,7 @@ if (YUZU_ROOM_STANDALONE) endif() if (ENABLE_QT) - add_subdirectory(yuzu) + add_subdirectory(eden) endif() if (ENABLE_WEB_SERVICE) diff --git a/src/eden/.gitignore b/src/eden/.gitignore new file mode 100644 index 0000000000..aa3808c5a9 --- /dev/null +++ b/src/eden/.gitignore @@ -0,0 +1,82 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* +*.qbs.user* +CMakeLists.txt.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + +# Directories with generated files +.moc/ +.obj/ +.pch/ +.rcc/ +.uic/ +/build*/ diff --git a/src/eden/CMakeLists.txt b/src/eden/CMakeLists.txt new file mode 100644 index 0000000000..702ccd3adf --- /dev/null +++ b/src/eden/CMakeLists.txt @@ -0,0 +1,82 @@ +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +find_package(Qt6 REQUIRED COMPONENTS Widgets Core Gui Quick QuickControls2) + +qt_standard_project_setup(REQUIRES 6.7) + +qt_add_executable(yuzu + main.cpp + icons.qrc +) + +add_subdirectory(qml) +add_subdirectory(interface) +add_subdirectory(models) + +set(PLUGINS + edenMainplugin + edenItemsplugin + edenConfigplugin + edenInterfaceplugin + edenConstantsplugin +) + +if (ENABLE_SDL2) + add_subdirectory(gamepad) + set(PLUGINS ${PLUGINS} edenGamepadplugin) +endif() + +target_link_libraries(yuzu + PRIVATE + Qt6::Core + Qt6::Widgets + Qt6::Gui + Qt6::Quick + Qt6::QuickControls2 + edenModels + + # Explicitly link to static plugins + ${PLUGINS} +) + +set(NATIVE_MODULES yuzu edenInterface) + +foreach(MODULE ${NATIVE_MODULES}) + target_link_libraries(${MODULE} PRIVATE common core input_common frontend_common network video_core) + target_link_libraries(${MODULE} PRIVATE Boost::headers glad fmt) + target_link_libraries(${MODULE} PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) + + target_link_libraries(${MODULE} PRIVATE Vulkan::Headers) + + if (NOT WIN32) + target_include_directories(${MODULE} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) + endif() + if (UNIX AND NOT APPLE) + target_link_libraries(${MODULE} PRIVATE Qt6::DBus) + endif() + + target_compile_definitions(${MODULE} PRIVATE + # Use QStringBuilder for string concatenation to reduce + # the overall number of temporary strings created. + -DQT_USE_QSTRINGBUILDER + + # Disable implicit type narrowing in signal/slot connect() calls. + -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT + + # Disable unsafe overloads of QProcess' start() function. + -DQT_NO_PROCESS_COMBINED_ARGUMENT_START + + # Disable implicit QString->QUrl conversions to enforce use of proper resolving functions. + -DQT_NO_URL_CAST_FROM_STRING + ) +endforeach() + +set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden") +include(GNUInstallDirs) +install(TARGETS yuzu + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/src/eden/gamepad/CMakeLists.txt b/src/eden/gamepad/CMakeLists.txt new file mode 100644 index 0000000000..f4bbb7cf54 --- /dev/null +++ b/src/eden/gamepad/CMakeLists.txt @@ -0,0 +1,9 @@ +set(CMAKE_AUTOMOC ON) + +qt_add_library(edenGamepad STATIC) + +qt_add_qml_module(edenGamepad + URI org.eden_emu.gamepad + VERSION 1.0 + SOURCES gamepad.h gamepad.cpp +) diff --git a/src/eden/gamepad/gamepad.cpp b/src/eden/gamepad/gamepad.cpp new file mode 100644 index 0000000000..daba8e35e8 --- /dev/null +++ b/src/eden/gamepad/gamepad.cpp @@ -0,0 +1,78 @@ +#include "gamepad.h" + +Gamepad::Gamepad(QObject *parent) + : QObject(parent) +{ + SDL_Init(SDL_INIT_GAMECONTROLLER); +} + +Gamepad::~Gamepad() +{ + if (controller) + SDL_GameControllerClose(controller); + SDL_Quit(); +} + +void Gamepad::openController(int deviceIndex) +{ + if (controller) { + closeController(); + } + + controller = SDL_GameControllerOpen(deviceIndex); +} + +void Gamepad::closeController() +{ + if (controller) { + SDL_GameControllerClose(controller); + controller = nullptr; + } +} +void Gamepad::pollEvents() +{ + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_CONTROLLERDEVICEADDED: + openController(event.cdevice.which); + break; + case SDL_CONTROLLERDEVICEREMOVED: + if (controller + && event.cdevice.which + == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))) { + closeController(); + } + break; + + case SDL_CONTROLLERBUTTONDOWN: + switch (event.cbutton.button) { + case SDL_CONTROLLER_BUTTON_DPAD_UP: + emit upPressed(); + break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + emit downPressed(); + break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + emit leftPressed(); + break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + emit rightPressed(); + break; + case SDL_CONTROLLER_BUTTON_A: + emit aPressed(); + break; + } + break; + + case SDL_CONTROLLERAXISMOTION: + if (event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTX + || event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTY) { + int x = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); + int y = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); + emit leftStickMoved(x, y); + } + break; + } + } +} diff --git a/src/eden/gamepad/gamepad.h b/src/eden/gamepad/gamepad.h new file mode 100644 index 0000000000..42c2b3faed --- /dev/null +++ b/src/eden/gamepad/gamepad.h @@ -0,0 +1,31 @@ +#pragma once +#include +#include +#include + +#include + +class Gamepad : public QObject { + Q_OBJECT + QML_ELEMENT +public: + explicit Gamepad(QObject *parent = nullptr); + ~Gamepad(); + + Q_INVOKABLE void pollEvents(); + +signals: + void upPressed(); + void downPressed(); + void leftPressed(); + void rightPressed(); + void aPressed(); + + void leftStickMoved(int x, int y); + +private: + SDL_GameController *controller = nullptr; + + void closeController(); + void openController(int deviceIndex); +}; diff --git a/src/eden/icons.qrc b/src/eden/icons.qrc new file mode 100644 index 0000000000..9844c5ff9e --- /dev/null +++ b/src/eden/icons.qrc @@ -0,0 +1,14 @@ + + + icons/audio.svg + icons/controls.svg + icons/cpu.svg + icons/general.svg + icons/graphics.svg + icons/system.svg + icons/debug.svg + icons/forward.svg + icons/back.svg + icons/help.svg + + diff --git a/src/eden/icons/audio.svg b/src/eden/icons/audio.svg new file mode 100644 index 0000000000..7244ec4388 --- /dev/null +++ b/src/eden/icons/audio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/eden/icons/back.svg b/src/eden/icons/back.svg new file mode 100644 index 0000000000..b14bde76b7 --- /dev/null +++ b/src/eden/icons/back.svg @@ -0,0 +1,39 @@ + + + + + + diff --git a/src/eden/icons/controls.svg b/src/eden/icons/controls.svg new file mode 100644 index 0000000000..f115376c87 --- /dev/null +++ b/src/eden/icons/controls.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/eden/icons/cpu.svg b/src/eden/icons/cpu.svg new file mode 100644 index 0000000000..683e20bf7c --- /dev/null +++ b/src/eden/icons/cpu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/eden/icons/debug.svg b/src/eden/icons/debug.svg new file mode 100644 index 0000000000..d279fc6d23 --- /dev/null +++ b/src/eden/icons/debug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/eden/icons/forward.svg b/src/eden/icons/forward.svg new file mode 100644 index 0000000000..dab7cca73c --- /dev/null +++ b/src/eden/icons/forward.svg @@ -0,0 +1,39 @@ + + + + + + diff --git a/src/eden/icons/general.svg b/src/eden/icons/general.svg new file mode 100644 index 0000000000..63cebb1c3a --- /dev/null +++ b/src/eden/icons/general.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/eden/icons/graphics.svg b/src/eden/icons/graphics.svg new file mode 100644 index 0000000000..47b5bc7ca8 --- /dev/null +++ b/src/eden/icons/graphics.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/src/eden/icons/help.svg b/src/eden/icons/help.svg new file mode 100644 index 0000000000..fef5d20286 --- /dev/null +++ b/src/eden/icons/help.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/eden/icons/system.svg b/src/eden/icons/system.svg new file mode 100644 index 0000000000..df53b9b617 --- /dev/null +++ b/src/eden/icons/system.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/eden/interface/CMakeLists.txt b/src/eden/interface/CMakeLists.txt new file mode 100644 index 0000000000..374b4ec70d --- /dev/null +++ b/src/eden/interface/CMakeLists.txt @@ -0,0 +1,13 @@ +qt_add_library(edenInterface STATIC) +qt_add_qml_module(edenInterface + URI org.eden_emu.interface + VERSION 1.0 + SOURCES + SettingsInterface.h SettingsInterface.cpp + uisettings.h uisettings.cpp + qt_config.h qt_config.cpp + shared_translation.h shared_translation.cpp + QMLSetting.h QMLSetting.cpp + MetaObjectHelper.h + +) diff --git a/src/eden/interface/MetaObjectHelper.h b/src/eden/interface/MetaObjectHelper.h new file mode 100644 index 0000000000..922e1cbb47 --- /dev/null +++ b/src/eden/interface/MetaObjectHelper.h @@ -0,0 +1,22 @@ +#ifndef METAOBJECTHELPER_H +#define METAOBJECTHELPER_H + +#include +#include +#include + +class MetaObjectHelper : public QObject { + Q_OBJECT + QML_ELEMENT + QML_SINGLETON +public: + using QObject::QObject; + Q_INVOKABLE QString typeName(QObject* object, const QString& property) const + { + QQmlProperty qmlProperty(object, property); + QMetaProperty metaProperty = qmlProperty.property(); + return metaProperty.typeName(); + } +}; + +#endif // METAOBJECTHELPER_H diff --git a/src/eden/interface/QMLSetting.cpp b/src/eden/interface/QMLSetting.cpp new file mode 100644 index 0000000000..4e559df26b --- /dev/null +++ b/src/eden/interface/QMLSetting.cpp @@ -0,0 +1,261 @@ +#include "QMLSetting.h" +#include "common/settings.h" + +QMLSetting::QMLSetting(Settings::BasicSetting *setting, QObject *parent, RequestType request) + : QObject(parent) + , m_setting(setting) +{ + + // TODO: restore/touch + /* + if (!Settings::IsConfiguringGlobal() && managed) { + restore_button = CreateRestoreGlobalButton(setting.UsingGlobal(), this); + + touch = [this]() { + LOG_DEBUG(Frontend, "Enabling custom setting for \"{}\"", setting.GetLabel()); + restore_button->setEnabled(true); + restore_button->setVisible(true); + }; + } + */ + + const auto type = setting->TypeId(); + + request = [&]() { + if (request != RequestType::Default) { + return request; + } + switch (setting->Specialization() & Settings::SpecializationTypeMask) { + case Settings::Specialization::Default: + return RequestType::Default; + case Settings::Specialization::Time: + return RequestType::DateTimeEdit; + case Settings::Specialization::Hex: + return RequestType::HexEdit; + case Settings::Specialization::RuntimeList: + // managed = false; + [[fallthrough]]; + case Settings::Specialization::List: + return RequestType::ComboBox; + case Settings::Specialization::Scalar: + return RequestType::Slider; + case Settings::Specialization::Countable: + return RequestType::SpinBox; + case Settings::Specialization::Radio: + return RequestType::RadioGroup; + default: + break; + } + return request; + }(); + + if (type == typeid(bool)) { + m_type = "bool"; + m_metaType = QMetaType::Bool; + } else if (setting->IsEnum()) { + m_metaType = QMetaType::UInt; + + if (request == RequestType::RadioGroup) { + m_type = "radio"; + // TODO: Add the options and whatnot + // see CreateRadioGroup + } else { + m_type = "enumCombo"; + } + } else if (setting->IsIntegral()) { + m_metaType = QMetaType::UInt; + + switch (request) { + case RequestType::Slider: + case RequestType::ReverseSlider: + // TODO: Reversal and multiplier + m_type = "intSlider"; + break; + case RequestType::Default: + case RequestType::LineEdit: + m_type = "intSpin"; + break; + case RequestType::DateTimeEdit: + // TODO: disabled/restrict + m_type = "time"; + break; + case RequestType::SpinBox: + // TODO: suffix + m_type = "intSpin"; + break; + case RequestType::HexEdit: + m_type = "hex"; + break; + case RequestType::ComboBox: + // TODO: Add the options and whatnot + // see CreateComboBox + m_type = "intCombo"; + break; + default: + // UNIMPLEMENTED(); + break; + } + } else if (setting->IsFloatingPoint()) { + m_metaType = QMetaType::Double; + + switch (request) { + case RequestType::Default: + case RequestType::SpinBox: + // TODO: suffix + m_type = "doubleSpin"; + break; + case RequestType::Slider: + case RequestType::ReverseSlider: + // TODO: multiplier, suffix, reversal + m_type = "doubleSlider"; + break; + default: + // UNIMPLEMENTED assert + // UNIMPLEMENTED(); + break; + } + } else if (type == typeid(std::string)) { + m_metaType = QMetaType::QString; + + switch (request) { + case RequestType::Default: + case RequestType::LineEdit: + m_type = "stringLine"; + break; + case RequestType::ComboBox: + m_type = "stringCombo"; + break; + default: + // UNIMPLEMENTED(); + break; + } + } +} + +QVariant QMLSetting::value() const +{ + QVariant var = QVariant::fromValue(QString::fromStdString(m_setting->ToString())); + var.convert(QMetaType(m_metaType)); + return var; +} + +void QMLSetting::setValue(const QVariant &newValue) +{ + QVariant var = newValue; + var.convert(QMetaType(m_metaType)); + + m_setting->LoadString(var.toString().toStdString()); + emit valueChanged(); +} + +bool QMLSetting::global() const +{ + return m_setting->UsingGlobal(); +} + +void QMLSetting::setGlobal(bool newGlobal) +{ + m_setting->SetGlobal(newGlobal); + emit globalChanged(); + emit valueChanged(); +} + +void QMLSetting::restore() +{ + std::string toSet = Settings::IsConfiguringGlobal() ? m_setting->DefaultToString() : m_setting->ToStringGlobal(); + setValue(QString::fromStdString(toSet)); + setGlobal(false); +} + +QMLSetting *QMLSetting::other() const +{ + return m_other; +} + +void QMLSetting::setOther(QMLSetting *newOther) +{ + if (m_other == newOther) + return; + m_other = newOther; + emit otherChanged(); +} + +u32 QMLSetting::max() const +{ + return std::strtol(m_setting->MaxVal().c_str(), nullptr, 0); +} + +u32 QMLSetting::min() const +{ + return std::strtol(m_setting->MinVal().c_str(), nullptr, 0); +} + +QString QMLSetting::suffix() const +{ + return m_suffix; +} + +void QMLSetting::setSuffix(const QString &newSuffix) +{ + if (m_suffix == newSuffix) + return; + m_suffix = newSuffix; + emit suffixChanged(); +} + +QStringList QMLSetting::combo() const +{ + return m_combo; +} + +void QMLSetting::setCombo(const QStringList &newCombo) +{ + if (m_combo == newCombo) + return; + m_combo = newCombo; + emit comboChanged(); +} + +QString QMLSetting::type() const +{ + return m_type; +} + +void QMLSetting::setType(const QString &newType) +{ + if (m_type == newType) + return; + m_type = newType; + emit typeChanged(); +} + +u32 QMLSetting::id() const +{ + return m_setting->Id(); +} + +QString QMLSetting::tooltip() const +{ + return m_tooltip; +} + +void QMLSetting::setTooltip(const QString &newTooltip) +{ + if (m_tooltip == newTooltip) + return; + m_tooltip = newTooltip; + emit tooltipChanged(); +} + +QString QMLSetting::label() const +{ + return m_label.isEmpty() ? QString::fromStdString(m_setting->GetLabel()) : m_label; +} + +void QMLSetting::setLabel(const QString &newLabel) +{ + if (m_label == newLabel) + return; + m_label = newLabel; + emit labelChanged(); +} diff --git a/src/eden/interface/QMLSetting.h b/src/eden/interface/QMLSetting.h new file mode 100644 index 0000000000..78349ae4f4 --- /dev/null +++ b/src/eden/interface/QMLSetting.h @@ -0,0 +1,105 @@ +#ifndef QMLSETTING_H +#define QMLSETTING_H + +#include +#include +#include "common/settings_common.h" + +enum class RequestType { + Default, + ComboBox, + SpinBox, + Slider, + ReverseSlider, + LineEdit, + HexEdit, + DateTimeEdit, + RadioGroup, + MaxEnum, +}; + +class QMLSetting : public QObject { + Q_OBJECT + +public: + explicit QMLSetting(Settings::BasicSetting *setting, QObject *parent = nullptr, RequestType request = RequestType::Default); + + QVariant value() const; + void setValue(const QVariant &newValue); + + bool global() const; + void setGlobal(bool newGlobal); + + QString label() const; + void setLabel(const QString &newLabel); + + QString tooltip() const; + void setTooltip(const QString &newTooltip); + + u32 id() const; + + QString type() const; + void setType(const QString &newType); + + QStringList combo() const; + void setCombo(const QStringList &newCombo); + + QString suffix() const; + void setSuffix(const QString &newSuffix); + + // TODO: float versions + u32 min() const; + u32 max() const; + + QMLSetting *other() const; + void setOther(QMLSetting *newOther); + +public slots: + void restore(); + +signals: + void valueChanged(); + void globalChanged(); + + void labelChanged(); + + void tooltipChanged(); + + void typeChanged(); + + void comboChanged(); + + void suffixChanged(); + + void otherChanged(); + +private: + Settings::BasicSetting *m_setting; + + QMLSetting *m_other; + + QString m_label; + QString m_tooltip; + QString m_type; + QStringList m_combo; + QString m_suffix; + + QMetaType::Type m_metaType; + + Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged FINAL) + Q_PROPERTY(bool global READ global WRITE setGlobal NOTIFY globalChanged FINAL) + Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged FINAL) + Q_PROPERTY(QString tooltip READ tooltip WRITE setTooltip NOTIFY tooltipChanged FINAL) + Q_PROPERTY(u32 id READ id FINAL CONSTANT) + Q_PROPERTY(QString type READ type WRITE setType NOTIFY typeChanged FINAL) + Q_PROPERTY(QStringList combo READ combo WRITE setCombo NOTIFY comboChanged FINAL) + Q_PROPERTY(QString suffix READ suffix WRITE setSuffix NOTIFY suffixChanged FINAL) + + Q_PROPERTY(u32 min READ min FINAL CONSTANT) + Q_PROPERTY(u32 max READ max FINAL CONSTANT) + Q_PROPERTY(QMLSetting *other READ other WRITE setOther NOTIFY otherChanged FINAL) +}; + +Q_DECLARE_METATYPE(QMLSetting *) + +#endif // QMLSETTING_H diff --git a/src/eden/interface/SettingsInterface.cpp b/src/eden/interface/SettingsInterface.cpp new file mode 100644 index 0000000000..0265440628 --- /dev/null +++ b/src/eden/interface/SettingsInterface.cpp @@ -0,0 +1,124 @@ +#include "SettingsInterface.h" +#include "common/settings.h" +#include "common/logging/log.h" +#include "uisettings.h" + +SettingsInterface::SettingsInterface(QObject* parent) + : QObject{parent} + , translations{ConfigurationShared::InitializeTranslations(parent)} + , combobox_translations{ConfigurationShared::ComboboxEnumeration(parent)} +{} + +// TODO: idExclude +SettingsModel *SettingsInterface::category(SettingsCategories::Category category, + QList idInclude, + QList idExclude) +{ + std::vector settings = Settings::values.linkage.by_category[static_cast(category)]; + std::vector uisettings = UISettings::values.linkage.by_category[static_cast(category)]; + + settings.insert(settings.end(), uisettings.begin(), uisettings.end()); + + QList settingsList; + for (Settings::BasicSetting *setting : settings) { + // paired settings get ignored + if (setting->Specialization() == Settings::Specialization::Paired) { + LOG_DEBUG(Frontend, "\"{}\" has specialization Paired: ignoring", setting->GetLabel()); + continue; + } + + if ((idInclude.empty() || idInclude.contains(setting->GetLabel())) + && (idExclude.empty() || !idExclude.contains(setting->GetLabel()))) { + settingsList.append(this->getSetting(setting)); + } + } + + SettingsModel *model = new SettingsModel(this); + model->append(settingsList); + + return model; +} + +int SettingsInterface::id(const QString &key) +{ + return setting(key)->id(); +} + +bool SettingsInterface::global() const +{ + return Settings::IsConfiguringGlobal(); +} + +void SettingsInterface::setGlobal(bool newGlobal) +{ + Settings::SetConfiguringGlobal(newGlobal); +} + +QMLSetting *SettingsInterface::getSetting(Settings::BasicSetting *setting) +{ + if (setting == nullptr) { + return nullptr; + } + + if (m_settings.contains(setting->GetLabel())) { + return m_settings.value(setting->GetLabel()); + } + + const int id = setting->Id(); + + const auto [label, tooltip] = [&]() { + const auto& setting_label = setting->GetLabel(); + if (translations->contains(id)) { + return std::pair{translations->at(id).first, translations->at(id).second}; + } + + LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label); + return std::pair{QString::fromStdString(setting_label), QString()}; + }(); + + const auto type = setting->EnumIndex(); + QStringList items; + + const ConfigurationShared::ComboboxTranslations* enumeration{nullptr}; + if (combobox_translations->contains(type)) { + enumeration = &combobox_translations->at(type); + for (const auto& [_, name] : *enumeration) { + items << name; + } + } + + // TODO: Suffix (fr) + QString suffix = ""; + + if ((setting->Specialization() & Settings::SpecializationAttributeMask) == + Settings::Specialization::Percentage) { + suffix = "%"; + } + + // paired setting (I/A) + QMLSetting *other = this->getSetting(setting->PairedSetting()); + + QMLSetting *qsetting = new QMLSetting(setting, this); + qsetting->setLabel(label); + qsetting->setTooltip(tooltip); + qsetting->setCombo(items); + qsetting->setSuffix(suffix); + qsetting->setOther(other); + + m_settings.insert(setting->GetLabel(), qsetting); + + return qsetting; +} + +QMLSetting *SettingsInterface::setting(const QString &key) +{ + std::string skey = key.toStdString(); + if (!m_settings.contains(skey)) { + Settings::BasicSetting *basicSetting = Settings::values.linkage.by_key.contains(skey) ? + Settings::values.linkage.by_key[skey] : + UISettings::values.linkage.by_key[skey]; + return getSetting(basicSetting); + } else { + return m_settings.value(skey); + } +} diff --git a/src/eden/interface/SettingsInterface.h b/src/eden/interface/SettingsInterface.h new file mode 100644 index 0000000000..4c132c3ce6 --- /dev/null +++ b/src/eden/interface/SettingsInterface.h @@ -0,0 +1,84 @@ +#ifndef SETTINGSINTERFACE_H +#define SETTINGSINTERFACE_H + +#include +#include + +#include "QMLSetting.h" +#include "shared_translation.h" +#include "yuzu/models/SettingsModel.h" + +namespace SettingsCategories { +Q_NAMESPACE + +enum class Category { + Android = static_cast(Settings::Category::Android), + Audio = static_cast(Settings::Category::Audio), + Core = static_cast(Settings::Category::Core), + Cpu = static_cast(Settings::Category::Cpu), + CpuDebug = static_cast(Settings::Category::CpuDebug), + CpuUnsafe = static_cast(Settings::Category::CpuUnsafe), + Overlay = static_cast(Settings::Category::Overlay), + Renderer = static_cast(Settings::Category::Renderer), + RendererAdvanced = static_cast(Settings::Category::RendererAdvanced), + RendererExtensions = static_cast(Settings::Category::RendererExtensions), + RendererDebug = static_cast(Settings::Category::RendererDebug), + System = static_cast(Settings::Category::System), + SystemAudio = static_cast(Settings::Category::SystemAudio), + DataStorage = static_cast(Settings::Category::DataStorage), + Debugging = static_cast(Settings::Category::Debugging), + DebuggingGraphics = static_cast(Settings::Category::DebuggingGraphics), + GpuDriver = static_cast(Settings::Category::GpuDriver), + Miscellaneous = static_cast(Settings::Category::Miscellaneous), + Network = static_cast(Settings::Category::Network), + WebService = static_cast(Settings::Category::WebService), + AddOns = static_cast(Settings::Category::AddOns), + Controls = static_cast(Settings::Category::Controls), + Ui = static_cast(Settings::Category::Ui), + UiAudio = static_cast(Settings::Category::UiAudio), + UiGeneral = static_cast(Settings::Category::UiGeneral), + UiLayout = static_cast(Settings::Category::UiLayout), + UiGameList = static_cast(Settings::Category::UiGameList), + Screenshots = static_cast(Settings::Category::Screenshots), + Shortcuts = static_cast(Settings::Category::Shortcuts), + Multiplayer = static_cast(Settings::Category::Multiplayer), + Services = static_cast(Settings::Category::Services), + Paths = static_cast(Settings::Category::Paths), + Linux = static_cast(Settings::Category::Linux), + LibraryApplet = static_cast(Settings::Category::LibraryApplet), + MaxEnum = static_cast(Settings::Category::MaxEnum), +}; +Q_ENUM_NS(Category) +} + +class SettingsInterface : public QObject { + Q_OBJECT + QML_SINGLETON + QML_ELEMENT +public: + explicit SettingsInterface(QObject* parent = nullptr); + + QMLSetting *getSetting(Settings::BasicSetting *setting); + Q_INVOKABLE QMLSetting *setting(const QString &key); + Q_INVOKABLE SettingsModel *category(SettingsCategories::Category category, + QList idInclude = {}, + QList idExclude = {}); + + Q_INVOKABLE int id(const QString &key); + + bool global() const; + void setGlobal(bool newGlobal); + +signals: + void globalChanged(); + +private: + QMap m_settings; + + std::unique_ptr translations; + std::unique_ptr combobox_translations; + + Q_PROPERTY(bool global READ global WRITE setGlobal NOTIFY globalChanged FINAL) +}; + +#endif // SETTINGSINTERFACE_H diff --git a/src/eden/interface/qt_config.cpp b/src/eden/interface/qt_config.cpp new file mode 100644 index 0000000000..f4390065c4 --- /dev/null +++ b/src/eden/interface/qt_config.cpp @@ -0,0 +1,562 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "qt_config.h" +#include "common/logging/log.h" +#include "input_common/main.h" +#include "uisettings.h" + +const std::array QtConfig::default_buttons = { + Qt::Key_C, Qt::Key_X, Qt::Key_V, Qt::Key_Z, Qt::Key_F, + Qt::Key_G, Qt::Key_Q, Qt::Key_E, Qt::Key_R, Qt::Key_T, + Qt::Key_M, Qt::Key_N, Qt::Key_Left, Qt::Key_Up, Qt::Key_Right, + Qt::Key_Down, Qt::Key_Q, Qt::Key_E, 0, 0, + Qt::Key_Q, Qt::Key_E, +}; + +const std::array QtConfig::default_motions = { + Qt::Key_7, + Qt::Key_8, +}; + +const std::array, Settings::NativeAnalog::NumAnalogs> QtConfig::default_analogs{{ + { + Qt::Key_W, + Qt::Key_S, + Qt::Key_A, + Qt::Key_D, + }, + { + Qt::Key_I, + Qt::Key_K, + Qt::Key_J, + Qt::Key_L, + }, +}}; + +const std::array QtConfig::default_stick_mod = { + Qt::Key_Shift, + 0, +}; + +const std::array QtConfig::default_ringcon_analogs{{ + Qt::Key_A, + Qt::Key_D, +}}; + +QtConfig::QtConfig(const std::string& config_name, const ConfigType config_type) + : Config(config_type) { + Initialize(config_name); + if (config_type != ConfigType::InputProfile) { + ReadQtValues(); + SaveQtValues(); + } +} + +QtConfig::~QtConfig() { + if (global) { + QtConfig::SaveAllValues(); + } +} + +void QtConfig::ReloadAllValues() { + Reload(); + ReadQtValues(); + SaveQtValues(); +} + +void QtConfig::SaveAllValues() { + SaveValues(); + SaveQtValues(); +} + +void QtConfig::ReadQtValues() { + if (global) { + ReadUIValues(); + } + ReadQtControlValues(); +} + +void QtConfig::ReadQtPlayerValues(const std::size_t player_index) { + std::string player_prefix; + if (type != ConfigType::InputProfile) { + player_prefix.append("player_").append(ToString(player_index)).append("_"); + } + + auto& player = Settings::values.players.GetValue()[player_index]; + if (IsCustomConfig()) { + const auto profile_name = + ReadStringSetting(std::string(player_prefix).append("profile_name")); + if (profile_name.empty()) { + // Use the global input config + player = Settings::values.players.GetValue(true)[player_index]; + player.profile_name = ""; + return; + } + } + + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + auto& player_buttons = player.buttons[i]; + + player_buttons = ReadStringSetting( + std::string(player_prefix).append(Settings::NativeButton::mapping[i]), default_param); + if (player_buttons.empty()) { + player_buttons = default_param; + } + } + + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + auto& player_analogs = player.analogs[i]; + + player_analogs = ReadStringSetting( + std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), default_param); + if (player_analogs.empty()) { + player_analogs = default_param; + } + } + + for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); + auto& player_motions = player.motions[i]; + + player_motions = ReadStringSetting( + std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), default_param); + if (player_motions.empty()) { + player_motions = default_param; + } + } +} + +void QtConfig::ReadHidbusValues() { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); + auto& ringcon_analogs = Settings::values.ringcon_analogs; + + ringcon_analogs = ReadStringSetting(std::string("ring_controller"), default_param); + if (ringcon_analogs.empty()) { + ringcon_analogs = default_param; + } +} + +void QtConfig::ReadDebugControlValues() { + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + auto& debug_pad_buttons = Settings::values.debug_pad_buttons[i]; + + debug_pad_buttons = ReadStringSetting( + std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), default_param); + if (debug_pad_buttons.empty()) { + debug_pad_buttons = default_param; + } + } + + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i]; + + debug_pad_analogs = ReadStringSetting( + std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), default_param); + if (debug_pad_analogs.empty()) { + debug_pad_analogs = default_param; + } + } +} + +void QtConfig::ReadQtControlValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::Controls)); + + Settings::values.players.SetGlobal(!IsCustomConfig()); + for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { + ReadQtPlayerValues(p); + } + if (IsCustomConfig()) { + EndGroup(); + return; + } + ReadDebugControlValues(); + ReadHidbusValues(); + + EndGroup(); +} + +void QtConfig::ReadPathValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::Paths)); + + UISettings::values.roms_path = ReadStringSetting(std::string("romsPath")); + UISettings::values.game_dir_deprecated = + ReadStringSetting(std::string("gameListRootDir"), std::string(".")); + UISettings::values.game_dir_deprecated_deepscan = + ReadBooleanSetting(std::string("gameListDeepScan"), std::make_optional(false)); + + const int gamedirs_size = BeginArray(std::string("gamedirs")); + for (int i = 0; i < gamedirs_size; ++i) { + SetArrayIndex(i); + UISettings::GameDir game_dir; + game_dir.path = ReadStringSetting(std::string("path")); + game_dir.deep_scan = + ReadBooleanSetting(std::string("deep_scan"), std::make_optional(false)); + game_dir.expanded = ReadBooleanSetting(std::string("expanded"), std::make_optional(true)); + UISettings::values.game_dirs.append(game_dir); + } + EndArray(); + + // Create NAND and SD card directories if empty, these are not removable through the UI, + // also carries over old game list settings if present + if (UISettings::values.game_dirs.empty()) { + UISettings::GameDir game_dir; + game_dir.path = std::string("SDMC"); + game_dir.expanded = true; + UISettings::values.game_dirs.append(game_dir); + game_dir.path = std::string("UserNAND"); + UISettings::values.game_dirs.append(game_dir); + game_dir.path = std::string("SysNAND"); + UISettings::values.game_dirs.append(game_dir); + if (UISettings::values.game_dir_deprecated != std::string(".")) { + game_dir.path = UISettings::values.game_dir_deprecated; + game_dir.deep_scan = UISettings::values.game_dir_deprecated_deepscan; + UISettings::values.game_dirs.append(game_dir); + } + } + UISettings::values.recent_files = + QString::fromStdString(ReadStringSetting(std::string("recentFiles"))) + .split(QStringLiteral(", "), Qt::SkipEmptyParts, Qt::CaseSensitive); + + ReadCategory(Settings::Category::Paths); + + EndGroup(); +} + +void QtConfig::ReadShortcutValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts)); + + for (const auto& [name, group, shortcut] : UISettings::default_hotkeys) { + BeginGroup(group); + BeginGroup(name); + + // No longer using ReadSetting for shortcut.second as it inaccurately returns a value of 1 + // for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open + // a file dialog in windowed mode + UISettings::values.shortcuts.push_back( + {name, + group, + {ReadStringSetting(std::string("KeySeq"), shortcut.keyseq), + ReadStringSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq), + shortcut.context, + ReadBooleanSetting(std::string("Repeat"), std::optional(shortcut.repeat))}}); + + EndGroup(); // name + EndGroup(); // group + } + + EndGroup(); +} + +void QtConfig::ReadUIValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::Ui)); + + UISettings::values.theme = ReadStringSetting( + std::string("theme"), + std::string(UISettings::themes[static_cast(UISettings::default_theme)].second)); + + ReadUIGamelistValues(); + ReadUILayoutValues(); + ReadPathValues(); + ReadScreenshotValues(); + ReadShortcutValues(); + ReadMultiplayerValues(); + + ReadCategory(Settings::Category::Ui); + ReadCategory(Settings::Category::UiGeneral); + + EndGroup(); +} + +void QtConfig::ReadUIGamelistValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList)); + + ReadCategory(Settings::Category::UiGameList); + + const int favorites_size = BeginArray("favorites"); + for (int i = 0; i < favorites_size; i++) { + SetArrayIndex(i); + UISettings::values.favorited_ids.append( + ReadUnsignedIntegerSetting(std::string("program_id"))); + } + EndArray(); + + EndGroup(); +} + +void QtConfig::ReadUILayoutValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList)); + + ReadCategory(Settings::Category::UiLayout); + + EndGroup(); +} + +void QtConfig::ReadMultiplayerValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::Multiplayer)); + + ReadCategory(Settings::Category::Multiplayer); + + // Read ban list back + int size = BeginArray(std::string("username_ban_list")); + UISettings::values.multiplayer_ban_list.first.resize(size); + for (int i = 0; i < size; ++i) { + SetArrayIndex(i); + UISettings::values.multiplayer_ban_list.first[i] = + ReadStringSetting(std::string("username"), std::string("")); + } + EndArray(); + + size = BeginArray(std::string("ip_ban_list")); + UISettings::values.multiplayer_ban_list.second.resize(size); + for (int i = 0; i < size; ++i) { + UISettings::values.multiplayer_ban_list.second[i] = + ReadStringSetting("username", std::string("")); + } + EndArray(); + + EndGroup(); +} + +void QtConfig::SaveQtValues() +{ + if (global) { + LOG_DEBUG(Config, "Saving global Qt configuration values"); + SaveUIValues(); + } else { + LOG_DEBUG(Config, "Saving Qt configuration values"); + } + SaveQtControlValues(); + + WriteToIni(); +} + +void QtConfig::SaveQtPlayerValues(const std::size_t player_index) { + std::string player_prefix; + if (type != ConfigType::InputProfile) { + player_prefix = std::string("player_").append(ToString(player_index)).append("_"); + } + + const auto& player = Settings::values.players.GetValue()[player_index]; + if (IsCustomConfig() && player.profile_name.empty()) { + // No custom profile selected + return; + } + + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + WriteStringSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]), + player.buttons[i], std::make_optional(default_param)); + } + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + WriteStringSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), + player.analogs[i], std::make_optional(default_param)); + } + for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); + WriteStringSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), + player.motions[i], std::make_optional(default_param)); + } +} + +void QtConfig::SaveDebugControlValues() { + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + WriteStringSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), + Settings::values.debug_pad_buttons[i], + std::make_optional(default_param)); + } + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + WriteStringSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), + Settings::values.debug_pad_analogs[i], + std::make_optional(default_param)); + } +} + +void QtConfig::SaveHidbusValues() { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); + WriteStringSetting(std::string("ring_controller"), Settings::values.ringcon_analogs, + std::make_optional(default_param)); +} + +void QtConfig::SaveQtControlValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::Controls)); + + Settings::values.players.SetGlobal(!IsCustomConfig()); + for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { + SaveQtPlayerValues(p); + } + if (IsCustomConfig()) { + EndGroup(); + return; + } + SaveDebugControlValues(); + SaveHidbusValues(); + + EndGroup(); +} + +void QtConfig::SavePathValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::Paths)); + + WriteCategory(Settings::Category::Paths); + + WriteStringSetting(std::string("romsPath"), UISettings::values.roms_path); + BeginArray(std::string("gamedirs")); + for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) { + SetArrayIndex(i); + const auto& game_dir = UISettings::values.game_dirs[i]; + WriteStringSetting(std::string("path"), game_dir.path); + WriteBooleanSetting(std::string("deep_scan"), game_dir.deep_scan, + std::make_optional(false)); + WriteBooleanSetting(std::string("expanded"), game_dir.expanded, std::make_optional(true)); + } + EndArray(); + + WriteStringSetting(std::string("recentFiles"), + UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString()); + + EndGroup(); +} + +void QtConfig::SaveShortcutValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts)); + + // Lengths of UISettings::values.shortcuts & default_hotkeys are same. + // However, their ordering must also be the same. + for (std::size_t i = 0; i < UISettings::default_hotkeys.size(); i++) { + const auto& [name, group, shortcut] = UISettings::values.shortcuts[i]; + const auto& default_hotkey = UISettings::default_hotkeys[i].shortcut; + + BeginGroup(group); + BeginGroup(name); + + WriteStringSetting(std::string("KeySeq"), shortcut.keyseq, + std::make_optional(default_hotkey.keyseq)); + WriteStringSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq, + std::make_optional(default_hotkey.controller_keyseq)); + WriteIntegerSetting(std::string("Context"), shortcut.context, + std::make_optional(default_hotkey.context)); + WriteBooleanSetting(std::string("Repeat"), shortcut.repeat, + std::make_optional(default_hotkey.repeat)); + + EndGroup(); // name + EndGroup(); // group + } + + EndGroup(); +} + +void QtConfig::SaveUIValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::Ui)); + + WriteCategory(Settings::Category::Ui); + WriteCategory(Settings::Category::UiGeneral); + + WriteStringSetting( + std::string("theme"), UISettings::values.theme, + std::make_optional(std::string( + UISettings::themes[static_cast(UISettings::default_theme)].second))); + + SaveUIGamelistValues(); + SaveUILayoutValues(); + SavePathValues(); + SaveScreenshotValues(); + SaveShortcutValues(); + SaveMultiplayerValues(); + + EndGroup(); +} + +void QtConfig::SaveUIGamelistValues() +{ + BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList)); + + WriteCategory(Settings::Category::UiGameList); + + BeginArray(std::string("favorites")); + for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) { + SetArrayIndex(i); + WriteIntegerSetting(std::string("program_id"), UISettings::values.favorited_ids[i]); + } + EndArray(); // favorites + + EndGroup(); +} + +void QtConfig::SaveUILayoutValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::UiLayout)); + + WriteCategory(Settings::Category::UiLayout); + + EndGroup(); +} + +void QtConfig::SaveMultiplayerValues() { + BeginGroup(std::string("Multiplayer")); + + WriteCategory(Settings::Category::Multiplayer); + + // Write ban list + BeginArray(std::string("username_ban_list")); + for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.first.size(); ++i) { + SetArrayIndex(static_cast(i)); + WriteStringSetting(std::string("username"), + UISettings::values.multiplayer_ban_list.first[i]); + } + EndArray(); // username_ban_list + + BeginArray(std::string("ip_ban_list")); + for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.second.size(); ++i) { + SetArrayIndex(static_cast(i)); + WriteStringSetting(std::string("ip"), UISettings::values.multiplayer_ban_list.second[i]); + } + EndArray(); // ip_ban_list + + EndGroup(); +} + +std::vector& QtConfig::FindRelevantList(Settings::Category category) { + auto& map = Settings::values.linkage.by_category; + if (map.contains(category)) { + return Settings::values.linkage.by_category[category]; + } + return UISettings::values.linkage.by_category[category]; +} + +void QtConfig::ReadQtControlPlayerValues(std::size_t player_index) { + BeginGroup(Settings::TranslateCategory(Settings::Category::Controls)); + + ReadPlayerValues(player_index); + ReadQtPlayerValues(player_index); + + EndGroup(); +} + +void QtConfig::SaveQtControlPlayerValues(std::size_t player_index) { + BeginGroup(Settings::TranslateCategory(Settings::Category::Controls)); + + LOG_DEBUG(Config, "Saving players control configuration values"); + SavePlayerValues(player_index); + SaveQtPlayerValues(player_index); + + EndGroup(); + + WriteToIni(); +} diff --git a/src/eden/interface/qt_config.h b/src/eden/interface/qt_config.h new file mode 100644 index 0000000000..dc2dceb4d7 --- /dev/null +++ b/src/eden/interface/qt_config.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "frontend_common/config.h" + +class QtConfig final : public Config { +public: + explicit QtConfig(const std::string& config_name = "qt-config", + ConfigType config_type = ConfigType::GlobalConfig); + ~QtConfig() override; + + void ReloadAllValues() override; + void SaveAllValues() override; + + void ReadQtControlPlayerValues(std::size_t player_index); + void SaveQtControlPlayerValues(std::size_t player_index); + +protected: + void ReadQtValues(); + void ReadQtPlayerValues(std::size_t player_index); + void ReadQtControlValues(); + void ReadHidbusValues() override; + void ReadDebugControlValues() override; + void ReadPathValues() override; + void ReadShortcutValues() override; + void ReadUIValues() override; + void ReadUIGamelistValues() override; + void ReadUILayoutValues() override; + void ReadMultiplayerValues() override; + + void SaveQtValues(); + void SaveQtPlayerValues(std::size_t player_index); + void SaveQtControlValues(); + void SaveHidbusValues() override; + void SaveDebugControlValues() override; + void SavePathValues() override; + void SaveShortcutValues() override; + void SaveUIValues() override; + void SaveUIGamelistValues() override; + void SaveUILayoutValues() override; + void SaveMultiplayerValues() override; + + std::vector& FindRelevantList(Settings::Category category) override; + +public: + static const std::array default_buttons; + static const std::array default_motions; + static const std::array, Settings::NativeAnalog::NumAnalogs> default_analogs; + static const std::array default_stick_mod; + static const std::array default_ringcon_analogs; +}; diff --git a/src/eden/interface/shared_translation.cpp b/src/eden/interface/shared_translation.cpp new file mode 100644 index 0000000000..c0eea3d440 --- /dev/null +++ b/src/eden/interface/shared_translation.cpp @@ -0,0 +1,569 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "shared_translation.h" + +#include +#include "common/settings.h" +#include "common/settings_enums.h" +#include "common/settings_setting.h" +#include "common/time_zone.h" +#include "uisettings.h" +#include +#include +#include +#include + +namespace ConfigurationShared { + +std::unique_ptr InitializeTranslations(QObject* parent) +{ + std::unique_ptr translations = std::make_unique(); + const auto& tr = [parent](const char* text) -> QString { return parent->tr(text); }; + +#define INSERT(SETTINGS, ID, NAME, TOOLTIP) \ + translations->insert(std::pair{SETTINGS::values.ID.Id(), std::pair{(NAME), (TOOLTIP)}}) + + // A setting can be ignored by giving it a blank name + + // Applets + INSERT(Settings, cabinet_applet_mode, tr("Amiibo editor"), QString()); + INSERT(Settings, controller_applet_mode, tr("Controller configuration"), QString()); + INSERT(Settings, data_erase_applet_mode, tr("Data erase"), QString()); + INSERT(Settings, error_applet_mode, tr("Error"), QString()); + INSERT(Settings, net_connect_applet_mode, tr("Net connect"), QString()); + INSERT(Settings, player_select_applet_mode, tr("Player select"), QString()); + INSERT(Settings, swkbd_applet_mode, tr("Software keyboard"), QString()); + INSERT(Settings, mii_edit_applet_mode, tr("Mii Edit"), QString()); + INSERT(Settings, web_applet_mode, tr("Online web"), QString()); + INSERT(Settings, shop_applet_mode, tr("Shop"), QString()); + INSERT(Settings, photo_viewer_applet_mode, tr("Photo viewer"), QString()); + INSERT(Settings, offline_web_applet_mode, tr("Offline web"), QString()); + INSERT(Settings, login_share_applet_mode, tr("Login share"), QString()); + INSERT(Settings, wifi_web_auth_applet_mode, tr("Wifi web auth"), QString()); + INSERT(Settings, my_page_applet_mode, tr("My page"), QString()); + + // Audio + INSERT(Settings, sink_id, tr("Output Engine:"), QString()); + INSERT(Settings, audio_output_device_id, tr("Output Device:"), QString()); + INSERT(Settings, audio_input_device_id, tr("Input Device:"), QString()); + INSERT(Settings, audio_muted, tr("Mute audio"), QString()); + INSERT(Settings, volume, tr("Volume:"), QString()); + INSERT(Settings, dump_audio_commands, QString(), QString()); + INSERT(UISettings, mute_when_in_background, tr("Mute audio when in background"), QString()); + + // Core + INSERT( + Settings, use_multi_core, tr("Multicore CPU Emulation"), + tr("This option increases CPU emulation thread use from 1 to the Switch’s maximum of 4.\n" + "This is mainly a debug option and shouldn’t be disabled.")); + INSERT( + Settings, memory_layout_mode, tr("Memory Layout"), + tr("Increases the amount of emulated RAM from the stock 4GB of the retail Switch to the " + "developer kit's 8/6GB.\nIt’s doesn’t improve stability or performance and is intended " + "to let big texture mods fit in emulated RAM.\nEnabling it will increase memory " + "use. It is not recommended to enable unless a specific game with a texture mod needs " + "it.")); + INSERT(Settings, use_speed_limit, QString(), QString()); + INSERT(Settings, speed_limit, tr("Limit Speed Percent"), + tr("Controls the game's maximum rendering speed, but it’s up to each game if it runs " + "faster or not.\n200% for a 30 FPS game is 60 FPS, and for a " + "60 FPS game it will be 120 FPS.\nDisabling it means unlocking the framerate to the " + "maximum your PC can reach.")); + INSERT(Settings, sync_core_speed, tr("Synchronize Core Speed"), + tr("Synchronizes CPU core speed with the game's maximum rendering speed to boost FPS without affecting game speed (animations, physics, etc.).\n" + "Compatibility varies by game; many (especially older ones) may not respond well.\n" + "Can help reduce stuttering at lower framerates.")); + + // Cpu + INSERT(Settings, cpu_accuracy, tr("Accuracy:"), + tr("This setting controls the accuracy of the emulated CPU.\nDon't change this unless " + "you know what you are doing.")); + INSERT(Settings, cpu_backend, tr("Backend:"), QString()); + + // Cpu Debug + + // Cpu Unsafe + INSERT( + Settings, cpuopt_unsafe_unfuse_fma, + tr("Unfuse FMA (improve performance on CPUs without FMA)"), + tr("This option improves speed by reducing accuracy of fused-multiply-add instructions on " + "CPUs without native FMA support.")); + INSERT( + Settings, cpuopt_unsafe_reduce_fp_error, tr("Faster FRSQRTE and FRECPE"), + tr("This option improves the speed of some approximate floating-point functions by using " + "less accurate native approximations.")); + INSERT(Settings, cpuopt_unsafe_ignore_standard_fpcr, + tr("Faster ASIMD instructions (32 bits only)"), + tr("This option improves the speed of 32 bits ASIMD floating-point functions by running " + "with incorrect rounding modes.")); + INSERT(Settings, cpuopt_unsafe_inaccurate_nan, tr("Inaccurate NaN handling"), + tr("This option improves speed by removing NaN checking.\nPlease note this also reduces " + "accuracy of certain floating-point instructions.")); + INSERT(Settings, cpuopt_unsafe_fastmem_check, tr("Disable address space checks"), + tr("This option improves speed by eliminating a safety check before every memory " + "read/write in guest.\nDisabling it may allow a game to read/write the emulator's " + "memory.")); + INSERT( + Settings, cpuopt_unsafe_ignore_global_monitor, tr("Ignore global monitor"), + tr("This option improves speed by relying only on the semantics of cmpxchg to ensure " + "safety of exclusive access instructions.\nPlease note this may result in deadlocks and " + "other race conditions.")); + + // Renderer + INSERT( + Settings, renderer_backend, tr("API:"), + tr("Switches between the available graphics APIs.\nVulkan is recommended in most cases.")); + INSERT(Settings, vulkan_device, tr("Device:"), + tr("This setting selects the GPU to use with the Vulkan backend.")); + INSERT(Settings, shader_backend, tr("Shader Backend:"), + tr("The shader backend to use for the OpenGL renderer.\nGLSL is the fastest in " + "performance and the best in rendering accuracy.\n" + "GLASM is a deprecated NVIDIA-only backend that offers much better shader building " + "performance at the cost of FPS and rendering accuracy.\n" + "SPIR-V compiles the fastest, but yields poor results on most GPU drivers.")); + INSERT(Settings, resolution_setup, tr("Resolution:"), + tr("Forces the game to render at a different resolution.\nHigher resolutions require " + "much more VRAM and bandwidth.\n" + "Options lower than 1X can cause rendering issues.")); + INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QString()); + INSERT(Settings, fsr_sharpening_slider, tr("FSR Sharpness:"), + tr("Determines how sharpened the image will look while using FSR’s dynamic contrast.")); + INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"), + tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA has a " + "lower performance impact and can produce a better and more stable picture under " + "very low resolutions.")); + INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"), + tr("The method used to render the window in fullscreen.\nBorderless offers the best " + "compatibility with the on-screen keyboard that some games request for " + "input.\nExclusive " + "fullscreen may offer better performance and better Freesync/Gsync support.")); + INSERT(Settings, aspect_ratio, tr("Aspect Ratio:"), + tr("Stretches the game to fit the specified aspect ratio.\nSwitch games only support " + "16:9, so custom game mods are required to get other ratios.\nAlso controls the " + "aspect ratio of captured screenshots.")); + INSERT(Settings, use_disk_shader_cache, tr("Use disk pipeline cache"), + tr("Allows saving shaders to storage for faster loading on following game " + "boots.\nDisabling " + "it is only intended for debugging.")); + INSERT(Settings, optimize_spirv_output, tr("Optimize SPIRV output shader"), + tr("Runs an additional optimization pass over generated SPIRV shaders.\n" + "Will increase time required for shader compilation.\nMay slightly improve " + "performance.\nThis feature is experimental.")); + INSERT( + Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"), + tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled.")); + INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"), + tr("Specifies how videos should be decoded.\nIt can either use the CPU or the GPU for " + "decoding, or perform no decoding at all (black screen on videos).\n" + "In most cases, GPU decoding provides the best performance.")); + INSERT(Settings, accelerate_astc, tr("ASTC Decoding Method:"), + tr("This option controls how ASTC textures should be decoded.\n" + "CPU: Use the CPU for decoding, slowest but safest method.\n" + "GPU: Use the GPU's compute shaders to decode ASTC textures, recommended for most " + "games and users.\n" + "CPU Asynchronously: Use the CPU to decode ASTC textures as they arrive. Completely " + "eliminates ASTC decoding\nstuttering at the cost of rendering issues while the " + "texture is being decoded.")); + INSERT( + Settings, astc_recompression, tr("ASTC Recompression Method:"), + tr("Almost all desktop and laptop dedicated GPUs lack support for ASTC textures, forcing " + "the emulator to decompress to an intermediate format any card supports, RGBA8.\n" + "This option recompresses RGBA8 to either the BC1 or BC3 format, saving VRAM but " + "negatively affecting image quality.")); + INSERT(Settings, vram_usage_mode, tr("VRAM Usage Mode:"), + tr("Selects whether the emulator should prefer to conserve memory or make maximum usage " + "of available video memory for performance. Has no effect on integrated graphics. " + "Aggressive mode may severely impact the performance of other applications such as " + "recording software.")); + INSERT( + Settings, vsync_mode, tr("VSync Mode:"), + tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen " + "refresh rate.\nFIFO Relaxed is similar to FIFO but allows tearing as it recovers from " + "a slow down.\nMailbox can have lower latency than FIFO and does not tear but may drop " + "frames.\nImmediate (no synchronization) just presents whatever is available and can " + "exhibit tearing.")); + INSERT(Settings, bg_red, QString(), QString()); + INSERT(Settings, bg_green, QString(), QString()); + INSERT(Settings, bg_blue, QString(), QString()); + + // Renderer (Advanced Graphics) + INSERT(Settings, async_presentation, tr("Enable asynchronous presentation (Vulkan only)"), + tr("Slightly improves performance by moving presentation to a separate CPU thread.")); + INSERT( + Settings, renderer_force_max_clock, tr("Force maximum clocks (Vulkan only)"), + tr("Runs work in the background while waiting for graphics commands to keep the GPU from " + "lowering its clock speed.")); + INSERT(Settings, max_anisotropy, tr("Anisotropic Filtering:"), + tr("Controls the quality of texture rendering at oblique angles.\nIt’s a light setting " + "and safe to set at 16x on most GPUs.")); + INSERT(Settings, gpu_accuracy, tr("Accuracy Level:"), + tr("GPU emulation accuracy.\nMost games render fine with Normal, but High is still " + "required for some.\nParticles tend to only render correctly with High " + "accuracy.\nExtreme should only be used for debugging.\nThis option can " + "be changed while playing.\nSome games may require booting on high to render " + "properly.")); + INSERT(Settings, use_asynchronous_shaders, tr("Use asynchronous shader building (Hack)"), + tr("Enables asynchronous shader compilation, which may reduce shader stutter.\nThis " + "feature " + "is experimental.")); + INSERT(Settings, use_fast_gpu_time, tr("Use Fast GPU Time (Hack)"), + tr("Enables Fast GPU Time. This option will force most games to run at their highest " + "native resolution.")); + INSERT(Settings, use_vulkan_driver_pipeline_cache, tr("Use Vulkan pipeline cache"), + tr("Enables GPU vendor-specific pipeline cache.\nThis option can improve shader loading " + "time significantly in cases where the Vulkan driver does not store pipeline cache " + "files internally.")); + INSERT( + Settings, enable_compute_pipelines, tr("Enable Compute Pipelines (Intel Vulkan Only)"), + tr("Enable compute pipelines, required by some games.\nThis setting only exists for Intel " + "proprietary drivers, and may crash if enabled.\nCompute pipelines are always enabled " + "on all other drivers.")); + INSERT( + Settings, use_reactive_flushing, tr("Enable Reactive Flushing"), + tr("Uses reactive flushing instead of predictive flushing, allowing more accurate memory " + "syncing.")); + INSERT(Settings, use_video_framerate, tr("Sync to framerate of video playback"), + tr("Run the game at normal speed during video playback, even when the framerate is " + "unlocked.")); + INSERT(Settings, barrier_feedback_loops, tr("Barrier feedback loops"), + tr("Improves rendering of transparency effects in specific games.")); + + // Renderer (Extensions) + INSERT(Settings, dyna_state, tr("Extended Dynamic State"), + tr("Enables the VkExtendedDynamicState* extensions.\nHigher dynamic states will generally improve " + "performance, but may cause issues on certain games or devices.")); + + INSERT(Settings, provoking_vertex, tr("Provoking Vertex"), + tr("Improves lighting and vertex handling in certain games.\n" + "Only Vulkan 1.0+ devices support this extension.")); + + INSERT(Settings, descriptor_indexing, tr("Descriptor Indexing"), + tr("Improves texture & buffer handling and the Maxwell translation layer.\n" + "Some Vulkan 1.1+ and all 1.2+ devices support this extension.")); + + // Renderer (Debug) + + // System + INSERT(Settings, rng_seed, tr("RNG Seed"), + tr("Controls the seed of the random number generator.\nMainly used for speedrunning " + "purposes.")); + INSERT(Settings, rng_seed_enabled, QString(), QString()); + INSERT(Settings, device_name, tr("Device Name"), tr("The name of the emulated Switch.")); + INSERT(Settings, custom_rtc, tr("Custom RTC Date:"), + tr("This option allows to change the emulated clock of the Switch.\n" + "Can be used to manipulate time in games.")); + INSERT(Settings, custom_rtc_enabled, QString(), QString()); + INSERT(Settings, custom_rtc_offset, QStringLiteral(" "), + QStringLiteral("The number of seconds from the current unix time")); + INSERT(Settings, language_index, tr("Language:"), + tr("Note: this can be overridden when region setting is auto-select")); + INSERT(Settings, region_index, tr("Region:"), tr("The region of the emulated Switch.")); + INSERT(Settings, time_zone_index, tr("Time Zone:"), + tr("The time zone of the emulated Switch.")); + INSERT(Settings, sound_index, tr("Sound Output Mode:"), QString()); + INSERT(Settings, use_docked_mode, tr("Console Mode:"), + tr("Selects if the console is emulated in Docked or Handheld mode.\nGames will change " + "their resolution, details and supported controllers and depending on this setting.\n" + "Setting to Handheld can help improve performance for low end systems.")); + INSERT(Settings, current_user, QString(), QString()); + + // Controls + + // Data Storage + + // Debugging + + // Debugging Graphics + + // Network + + // Web Service + + // Ui + + // Ui General + INSERT(UISettings, select_user_on_boot, tr("Prompt for user on game boot"), + tr("Ask to select a user profile on each boot, useful if multiple people use eden on " + "the same PC.")); + INSERT(UISettings, pause_when_in_background, tr("Pause emulation when in background"), + tr("This setting pauses eden when focusing other windows.")); + INSERT(UISettings, confirm_before_stopping, tr("Confirm before stopping emulation"), + tr("This setting overrides game prompts asking to confirm stopping the game.\nEnabling " + "it bypasses such prompts and directly exits the emulation.")); + INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"), + tr("This setting hides the mouse after 2.5s of inactivity.")); + INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"), + tr("Forcibly disables the use of the controller applet by guests.\nWhen a guest " + "attempts to open the controller applet, it is immediately closed.")); + + // Linux + INSERT(Settings, enable_gamemode, tr("Enable Gamemode"), QString()); + + // Ui Debugging + + // Ui Multiplayer + + // Ui Games list + INSERT(UISettings, + grid_columns, + tr("Grid View Columns"), + tr("Number of games to show per row in the grid view.")); + +#undef INSERT + + return translations; +} + +std::unique_ptr ComboboxEnumeration(QObject* parent) +{ + std::unique_ptr translations = + std::make_unique(); + const auto& tr = [&](const char* text, const char* context = "") { + return parent->tr(text, context); + }; + +#define PAIR(ENUM, VALUE, TRANSLATION) {static_cast(Settings::ENUM::VALUE), (TRANSLATION)} + + // Intentionally skipping VSyncMode to let the UI fill that one out + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(AppletMode, HLE, tr("Custom frontend")), + PAIR(AppletMode, LLE, tr("Real applet")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(SpirvOptimizeMode, Never, tr("Never")), + PAIR(SpirvOptimizeMode, OnLoad, tr("On Load")), + PAIR(SpirvOptimizeMode, Always, tr("Always")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(AstcDecodeMode, Cpu, tr("CPU")), + PAIR(AstcDecodeMode, Gpu, tr("GPU")), + PAIR(AstcDecodeMode, CpuAsynchronous, tr("CPU Asynchronous")), + }}); + translations->insert( + {Settings::EnumMetadata::Index(), + { + PAIR(AstcRecompression, Uncompressed, tr("Uncompressed (Best quality)")), + PAIR(AstcRecompression, Bc1, tr("BC1 (Low quality)")), + PAIR(AstcRecompression, Bc3, tr("BC3 (Medium quality)")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(VramUsageMode, Conservative, tr("Conservative")), + PAIR(VramUsageMode, Aggressive, tr("Aggressive")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { +#ifdef HAS_OPENGL + PAIR(RendererBackend, OpenGL, tr("OpenGL")), +#endif + PAIR(RendererBackend, Vulkan, tr("Vulkan")), + PAIR(RendererBackend, Null, tr("Null")), + }}); + translations->insert( + {Settings::EnumMetadata::Index(), + { + PAIR(ShaderBackend, Glsl, tr("GLSL")), + PAIR(ShaderBackend, Glasm, tr("GLASM (Assembly Shaders, NVIDIA Only)")), + PAIR(ShaderBackend, SpirV, tr("SPIR-V (Experimental, AMD/Mesa Only)")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(GpuAccuracy, Normal, tr("Normal")), + PAIR(GpuAccuracy, High, tr("High")), + PAIR(GpuAccuracy, Extreme, tr("Extreme")), + }}); + translations->insert( + {Settings::EnumMetadata::Index(), + { + PAIR(CpuAccuracy, Auto, tr("Auto")), + PAIR(CpuAccuracy, Accurate, tr("Accurate")), + PAIR(CpuAccuracy, Unsafe, tr("Unsafe")), + PAIR(CpuAccuracy, Paranoid, tr("Paranoid (disables most optimizations)")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(CpuBackend, Dynarmic, tr("Dynarmic")), + PAIR(CpuBackend, Nce, tr("NCE")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(FullscreenMode, Borderless, tr("Borderless Windowed")), + PAIR(FullscreenMode, Exclusive, tr("Exclusive Fullscreen")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(NvdecEmulation, Off, tr("No Video Output")), + PAIR(NvdecEmulation, Cpu, tr("CPU Video Decoding")), + PAIR(NvdecEmulation, Gpu, tr("GPU Video Decoding (Default)")), + }}); + translations->insert( + {Settings::EnumMetadata::Index(), + { + PAIR(ResolutionSetup, Res1_2X, tr("0.5X (360p/540p) [EXPERIMENTAL]")), + PAIR(ResolutionSetup, Res3_4X, tr("0.75X (540p/810p) [EXPERIMENTAL]")), + PAIR(ResolutionSetup, Res1X, tr("1X (720p/1080p)")), + PAIR(ResolutionSetup, Res3_2X, tr("1.5X (1080p/1620p) [EXPERIMENTAL]")), + PAIR(ResolutionSetup, Res2X, tr("2X (1440p/2160p)")), + PAIR(ResolutionSetup, Res3X, tr("3X (2160p/3240p)")), + PAIR(ResolutionSetup, Res4X, tr("4X (2880p/4320p)")), + PAIR(ResolutionSetup, Res5X, tr("5X (3600p/5400p)")), + PAIR(ResolutionSetup, Res6X, tr("6X (4320p/6480p)")), + PAIR(ResolutionSetup, Res7X, tr("7X (5040p/7560p)")), + PAIR(ResolutionSetup, Res8X, tr("8X (5760p/8640p)")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(ScalingFilter, NearestNeighbor, tr("Nearest Neighbor")), + PAIR(ScalingFilter, Bilinear, tr("Bilinear")), + PAIR(ScalingFilter, Bicubic, tr("Bicubic")), + PAIR(ScalingFilter, Gaussian, tr("Gaussian")), + PAIR(ScalingFilter, ScaleForce, tr("ScaleForce")), + PAIR(ScalingFilter, Fsr, tr("AMD FidelityFX™️ Super Resolution")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(AntiAliasing, None, tr("None")), + PAIR(AntiAliasing, Fxaa, tr("FXAA")), + PAIR(AntiAliasing, Smaa, tr("SMAA")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(AspectRatio, R16_9, tr("Default (16:9)")), + PAIR(AspectRatio, R4_3, tr("Force 4:3")), + PAIR(AspectRatio, R21_9, tr("Force 21:9")), + PAIR(AspectRatio, R16_10, tr("Force 16:10")), + PAIR(AspectRatio, Stretch, tr("Stretch to Window")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(AnisotropyMode, Automatic, tr("Automatic")), + PAIR(AnisotropyMode, Default, tr("Default")), + PAIR(AnisotropyMode, X2, tr("2x")), + PAIR(AnisotropyMode, X4, tr("4x")), + PAIR(AnisotropyMode, X8, tr("8x")), + PAIR(AnisotropyMode, X16, tr("16x")), + }}); + translations->insert( + {Settings::EnumMetadata::Index(), + { + PAIR(Language, Japanese, tr("Japanese (日本語)")), + PAIR(Language, EnglishAmerican, tr("American English")), + PAIR(Language, French, tr("French (français)")), + PAIR(Language, German, tr("German (Deutsch)")), + PAIR(Language, Italian, tr("Italian (italiano)")), + PAIR(Language, Spanish, tr("Spanish (español)")), + PAIR(Language, Chinese, tr("Chinese")), + PAIR(Language, Korean, tr("Korean (한국어)")), + PAIR(Language, Dutch, tr("Dutch (Nederlands)")), + PAIR(Language, Portuguese, tr("Portuguese (português)")), + PAIR(Language, Russian, tr("Russian (Русский)")), + PAIR(Language, Taiwanese, tr("Taiwanese")), + PAIR(Language, EnglishBritish, tr("British English")), + PAIR(Language, FrenchCanadian, tr("Canadian French")), + PAIR(Language, SpanishLatin, tr("Latin American Spanish")), + PAIR(Language, ChineseSimplified, tr("Simplified Chinese")), + PAIR(Language, ChineseTraditional, tr("Traditional Chinese (正體中文)")), + PAIR(Language, PortugueseBrazilian, tr("Brazilian Portuguese (português do Brasil)")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(Region, Japan, tr("Japan")), + PAIR(Region, Usa, tr("USA")), + PAIR(Region, Europe, tr("Europe")), + PAIR(Region, Australia, tr("Australia")), + PAIR(Region, China, tr("China")), + PAIR(Region, Korea, tr("Korea")), + PAIR(Region, Taiwan, tr("Taiwan")), + }}); + translations->insert( + {Settings::EnumMetadata::Index(), + { + {static_cast(Settings::TimeZone::Auto), + tr("Auto (%1)", "Auto select time zone") + .arg(QString::fromStdString( + Settings::GetTimeZoneString(Settings::TimeZone::Auto)))}, + {static_cast(Settings::TimeZone::Default), + tr("Default (%1)", "Default time zone") + .arg(QString::fromStdString(Common::TimeZone::GetDefaultTimeZone()))}, + PAIR(TimeZone, Cet, tr("CET")), + PAIR(TimeZone, Cst6Cdt, tr("CST6CDT")), + PAIR(TimeZone, Cuba, tr("Cuba")), + PAIR(TimeZone, Eet, tr("EET")), + PAIR(TimeZone, Egypt, tr("Egypt")), + PAIR(TimeZone, Eire, tr("Eire")), + PAIR(TimeZone, Est, tr("EST")), + PAIR(TimeZone, Est5Edt, tr("EST5EDT")), + PAIR(TimeZone, Gb, tr("GB")), + PAIR(TimeZone, GbEire, tr("GB-Eire")), + PAIR(TimeZone, Gmt, tr("GMT")), + PAIR(TimeZone, GmtPlusZero, tr("GMT+0")), + PAIR(TimeZone, GmtMinusZero, tr("GMT-0")), + PAIR(TimeZone, GmtZero, tr("GMT0")), + PAIR(TimeZone, Greenwich, tr("Greenwich")), + PAIR(TimeZone, Hongkong, tr("Hongkong")), + PAIR(TimeZone, Hst, tr("HST")), + PAIR(TimeZone, Iceland, tr("Iceland")), + PAIR(TimeZone, Iran, tr("Iran")), + PAIR(TimeZone, Israel, tr("Israel")), + PAIR(TimeZone, Jamaica, tr("Jamaica")), + PAIR(TimeZone, Japan, tr("Japan")), + PAIR(TimeZone, Kwajalein, tr("Kwajalein")), + PAIR(TimeZone, Libya, tr("Libya")), + PAIR(TimeZone, Met, tr("MET")), + PAIR(TimeZone, Mst, tr("MST")), + PAIR(TimeZone, Mst7Mdt, tr("MST7MDT")), + PAIR(TimeZone, Navajo, tr("Navajo")), + PAIR(TimeZone, Nz, tr("NZ")), + PAIR(TimeZone, NzChat, tr("NZ-CHAT")), + PAIR(TimeZone, Poland, tr("Poland")), + PAIR(TimeZone, Portugal, tr("Portugal")), + PAIR(TimeZone, Prc, tr("PRC")), + PAIR(TimeZone, Pst8Pdt, tr("PST8PDT")), + PAIR(TimeZone, Roc, tr("ROC")), + PAIR(TimeZone, Rok, tr("ROK")), + PAIR(TimeZone, Singapore, tr("Singapore")), + PAIR(TimeZone, Turkey, tr("Turkey")), + PAIR(TimeZone, Uct, tr("UCT")), + PAIR(TimeZone, Universal, tr("Universal")), + PAIR(TimeZone, Utc, tr("UTC")), + PAIR(TimeZone, WSu, tr("W-SU")), + PAIR(TimeZone, Wet, tr("WET")), + PAIR(TimeZone, Zulu, tr("Zulu")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(AudioMode, Mono, tr("Mono")), + PAIR(AudioMode, Stereo, tr("Stereo")), + PAIR(AudioMode, Surround, tr("Surround")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(MemoryLayout, Memory_4Gb, tr("4GB DRAM (Default)")), + PAIR(MemoryLayout, Memory_6Gb, tr("6GB DRAM (Unsafe)")), + PAIR(MemoryLayout, Memory_8Gb, tr("8GB DRAM")), + PAIR(MemoryLayout, Memory_10Gb, tr("10GB DRAM (Unsafe)")), + PAIR(MemoryLayout, Memory_12Gb, tr("12GB DRAM (Unsafe)")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(ConsoleMode, Docked, tr("Docked")), + PAIR(ConsoleMode, Handheld, tr("Handheld")), + }}); + translations->insert( + {Settings::EnumMetadata::Index(), + { + PAIR(ConfirmStop, Ask_Always, tr("Always ask (Default)")), + PAIR(ConfirmStop, Ask_Based_On_Game, tr("Only if game specifies not to stop")), + PAIR(ConfirmStop, Ask_Never, tr("Never ask")), + }}); + +#undef PAIR +#undef CTX_PAIR + + return translations; +} +} // namespace ConfigurationShared diff --git a/src/eden/interface/shared_translation.h b/src/eden/interface/shared_translation.h new file mode 100644 index 0000000000..bfd508372b --- /dev/null +++ b/src/eden/interface/shared_translation.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include "common/common_types.h" +#include "common/settings.h" + +namespace ConfigurationShared { +using TranslationMap = std::map>; +using ComboboxTranslations = std::vector>; +using ComboboxTranslationMap = std::map; + +std::unique_ptr InitializeTranslations(QObject *parent); + +std::unique_ptr ComboboxEnumeration(QObject *parent); + +static const std::map anti_aliasing_texts_map = { + {Settings::AntiAliasing::None, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "None"))}, + {Settings::AntiAliasing::Fxaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FXAA"))}, + {Settings::AntiAliasing::Smaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SMAA"))}, +}; + +static const std::map scaling_filter_texts_map = { + {Settings::ScalingFilter::NearestNeighbor, + QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Nearest"))}, + {Settings::ScalingFilter::Bilinear, + QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bilinear"))}, + {Settings::ScalingFilter::Bicubic, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bicubic"))}, + {Settings::ScalingFilter::Gaussian, + QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Gaussian"))}, + {Settings::ScalingFilter::ScaleForce, + QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "ScaleForce"))}, + {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))}, +}; + +static const std::map use_docked_mode_texts_map = { + {Settings::ConsoleMode::Docked, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))}, + {Settings::ConsoleMode::Handheld, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))}, +}; + +static const std::map gpu_accuracy_texts_map = { + {Settings::GpuAccuracy::Normal, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Normal"))}, + {Settings::GpuAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "High"))}, + {Settings::GpuAccuracy::Extreme, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Extreme"))}, +}; + +static const std::map renderer_backend_texts_map = { + {Settings::RendererBackend::Vulkan, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Vulkan"))}, + {Settings::RendererBackend::OpenGL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "OpenGL"))}, + {Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Null"))}, +}; + +static const std::map shader_backend_texts_map = { + {Settings::ShaderBackend::Glsl, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))}, + {Settings::ShaderBackend::Glasm, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))}, + {Settings::ShaderBackend::SpirV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))}, +}; + +} // namespace ConfigurationShared diff --git a/src/eden/interface/uisettings.cpp b/src/eden/interface/uisettings.cpp new file mode 100644 index 0000000000..f5203de421 --- /dev/null +++ b/src/eden/interface/uisettings.cpp @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "uisettings.h" +#include +#include "common/fs/fs.h" +#include "common/fs/path_util.h" + +#ifndef CANNOT_EXPLICITLY_INSTANTIATE +namespace Settings { +template class Setting; +template class Setting; +template class Setting; +template class Setting; +template class Setting; +template class Setting; +template class Setting; +} // namespace Settings +#endif + +namespace FS = Common::FS; + +namespace UISettings { + +const Themes themes{{ + {"Default", "default"}, + {"Default Colorful", "colorful"}, + {"Dark", "qdarkstyle"}, + {"Dark Colorful", "colorful_dark"}, + {"Midnight Blue", "qdarkstyle_midnight_blue"}, + {"Midnight Blue Colorful", "colorful_midnight_blue"}, +}}; + +bool IsDarkTheme() { + const auto& theme = UISettings::values.theme; + return theme == std::string("qdarkstyle") || theme == std::string("qdarkstyle_midnight_blue") || + theme == std::string("colorful_dark") || theme == std::string("colorful_midnight_blue"); +} + +Values values = {}; + +u32 CalculateWidth(u32 height, Settings::AspectRatio ratio) { + switch (ratio) { + case Settings::AspectRatio::R4_3: + return height * 4 / 3; + case Settings::AspectRatio::R21_9: + return height * 21 / 9; + case Settings::AspectRatio::R16_10: + return height * 16 / 10; + case Settings::AspectRatio::R16_9: + case Settings::AspectRatio::Stretch: + // TODO: Move this function wherever appropriate to implement Stretched aspect + break; + } + return height * 16 / 9; +} + +void SaveWindowState() { + const auto window_state_config_loc = + FS::PathToUTF8String(FS::GetEdenPath(FS::EdenPath::ConfigDir) / "window_state.ini"); + + void(FS::CreateParentDir(window_state_config_loc)); + QSettings config(QString::fromStdString(window_state_config_loc), QSettings::IniFormat); + + config.setValue(QStringLiteral("geometry"), values.geometry); + config.setValue(QStringLiteral("state"), values.state); + config.setValue(QStringLiteral("geometryRenderWindow"), values.renderwindow_geometry); + config.setValue(QStringLiteral("gameListHeaderState"), values.gamelist_header_state); + config.setValue(QStringLiteral("microProfileDialogGeometry"), values.microprofile_geometry); + + config.sync(); +} + +void RestoreWindowState(std::unique_ptr& qtConfig) { + const auto window_state_config_loc = + FS::PathToUTF8String(FS::GetEdenPath(FS::EdenPath::ConfigDir) / "window_state.ini"); + + // Migrate window state from old location + if (!FS::Exists(window_state_config_loc) && qtConfig->Exists("UI", "UILayout\\geometry")) { + const auto config_loc = + FS::PathToUTF8String(FS::GetEdenPath(FS::EdenPath::ConfigDir) / "qt-config.ini"); + QSettings config(QString::fromStdString(config_loc), QSettings::IniFormat); + + config.beginGroup(QStringLiteral("UI")); + config.beginGroup(QStringLiteral("UILayout")); + values.geometry = config.value(QStringLiteral("geometry")).toByteArray(); + values.state = config.value(QStringLiteral("state")).toByteArray(); + values.renderwindow_geometry = + config.value(QStringLiteral("geometryRenderWindow")).toByteArray(); + values.gamelist_header_state = + config.value(QStringLiteral("gameListHeaderState")).toByteArray(); + values.microprofile_geometry = + config.value(QStringLiteral("microProfileDialogGeometry")).toByteArray(); + config.endGroup(); + config.endGroup(); + return; + } + + void(FS::CreateParentDir(window_state_config_loc)); + const QSettings config(QString::fromStdString(window_state_config_loc), QSettings::IniFormat); + + values.geometry = config.value(QStringLiteral("geometry")).toByteArray(); + values.state = config.value(QStringLiteral("state")).toByteArray(); + values.renderwindow_geometry = + config.value(QStringLiteral("geometryRenderWindow")).toByteArray(); + values.gamelist_header_state = + config.value(QStringLiteral("gameListHeaderState")).toByteArray(); + values.microprofile_geometry = + config.value(QStringLiteral("microProfileDialogGeometry")).toByteArray(); +} + +} // namespace UISettings diff --git a/src/eden/interface/uisettings.h b/src/eden/interface/uisettings.h new file mode 100644 index 0000000000..b8ac6a1e11 --- /dev/null +++ b/src/eden/interface/uisettings.h @@ -0,0 +1,282 @@ +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include "common/common_types.h" +#include "common/settings.h" +#include "common/settings_enums.h" +#include "qt_config.h" +#include +#include +#include + +using Settings::Category; +using Settings::ConfirmStop; +using Settings::Setting; +using Settings::SwitchableSetting; + +#ifndef CANNOT_EXPLICITLY_INSTANTIATE +namespace Settings { +extern template class Setting; +extern template class Setting; +extern template class Setting; +extern template class Setting; +extern template class Setting; +extern template class Setting; +extern template class Setting; +} // namespace Settings +#endif + +namespace UISettings { + +bool IsDarkTheme(); + +struct ContextualShortcut { + std::string keyseq; + std::string controller_keyseq; + int context; + bool repeat; +}; + +struct Shortcut { + std::string name; + std::string group; + ContextualShortcut shortcut; +}; + +enum class Theme { + Default, + DefaultColorful, + Dark, + DarkColorful, + MidnightBlue, + MidnightBlueColorful, +}; + +static constexpr Theme default_theme{ +#ifdef _WIN32 + Theme::DarkColorful +#else + Theme::DefaultColorful +#endif +}; + +using Themes = std::array, 6>; +extern const Themes themes; + +struct GameDir { + std::string path; + bool deep_scan = false; + bool expanded = false; + bool operator==(const GameDir& rhs) const { + return path == rhs.path; + } + bool operator!=(const GameDir& rhs) const { + return !operator==(rhs); + } +}; + +struct Values { + Settings::Linkage linkage{1000}; + + QByteArray geometry; + QByteArray state; + + QByteArray renderwindow_geometry; + + QByteArray gamelist_header_state; + + QByteArray microprofile_geometry; + Setting microprofile_visible{linkage, false, "microProfileDialogVisible", + Category::UiLayout}; + + Setting single_window_mode{linkage, true, "singleWindowMode", Category::Ui}; + Setting fullscreen{linkage, false, "fullscreen", Category::Ui}; + Setting display_titlebar{linkage, true, "displayTitleBars", Category::Ui}; + Setting show_filter_bar{linkage, true, "showFilterBar", Category::Ui}; + Setting show_status_bar{linkage, true, "showStatusBar", Category::Ui}; + + SwitchableSetting confirm_before_stopping{linkage, + ConfirmStop::Ask_Always, + "confirmStop", + Category::UiGeneral, + Settings::Specialization::Default, + true, + true}; + + Setting first_start{linkage, true, "firstStart", Category::Ui}; + Setting pause_when_in_background{linkage, + false, + "pauseWhenInBackground", + Category::UiGeneral, + Settings::Specialization::Default, + true, + true}; + Setting mute_when_in_background{linkage, + false, + "muteWhenInBackground", + Category::UiAudio, + Settings::Specialization::Default, + true, + true}; + Setting hide_mouse{ + linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default, + true, true}; + Setting controller_applet_disabled{linkage, false, "disableControllerApplet", + Category::UiGeneral}; + // Set when Vulkan is known to crash the application + bool has_broken_vulkan = false; + + Setting select_user_on_boot{linkage, + false, + "select_user_on_boot", + Category::UiGeneral, + Settings::Specialization::Default, + true, + true}; + Setting disable_web_applet{linkage, true, "disable_web_applet", Category::Ui}; + + // Discord RPC + Setting enable_discord_presence{linkage, false, "enable_discord_presence", Category::Ui}; + + // logging + Setting show_console{linkage, false, "showConsole", Category::Ui}; + + // Screenshots + Setting enable_screenshot_save_as{linkage, true, "enable_screenshot_save_as", + Category::Screenshots}; + Setting screenshot_height{linkage, 0, "screenshot_height", Category::Screenshots}; + + std::string roms_path; + std::string game_dir_deprecated; + bool game_dir_deprecated_deepscan; + QVector game_dirs; + QStringList recent_files; + Setting language{linkage, {}, "language", Category::Paths}; + + std::string theme; + + // Shortcut name + std::vector shortcuts; + + Setting callout_flags{linkage, 0, "calloutFlags", Category::Ui}; + + // multiplayer settings + Setting multiplayer_nickname{linkage, {}, "nickname", Category::Multiplayer}; + Setting multiplayer_filter_text{linkage, {}, "filter_text", Category::Multiplayer}; + Setting multiplayer_filter_games_owned{linkage, false, "filter_games_owned", + Category::Multiplayer}; + Setting multiplayer_filter_hide_empty{linkage, false, "filter_games_hide_empty", + Category::Multiplayer}; + Setting multiplayer_filter_hide_full{linkage, false, "filter_games_hide_full", + Category::Multiplayer}; + Setting multiplayer_ip{linkage, {}, "ip", Category::Multiplayer}; + Setting multiplayer_port{linkage, 24872, 0, + UINT16_MAX, "port", Category::Multiplayer}; + Setting multiplayer_room_nickname{ + linkage, {}, "room_nickname", Category::Multiplayer}; + Setting multiplayer_room_name{linkage, {}, "room_name", Category::Multiplayer}; + Setting multiplayer_max_player{linkage, 8, 0, 8, "max_player", Category::Multiplayer}; + Setting multiplayer_room_port{linkage, 24872, 0, + UINT16_MAX, "room_port", Category::Multiplayer}; + Setting multiplayer_host_type{linkage, 0, 0, 1, "host_type", Category::Multiplayer}; + Setting multiplayer_game_id{linkage, {}, "game_id", Category::Multiplayer}; + Setting multiplayer_room_description{ + linkage, {}, "room_description", Category::Multiplayer}; + std::pair, std::vector> multiplayer_ban_list; + + // Game List + Setting show_add_ons{linkage, true, "show_add_ons", Category::UiGameList}; + Setting game_icon_size{linkage, 64, "game_icon_size", Category::UiGameList}; + Setting folder_icon_size{linkage, 48, "folder_icon_size", Category::UiGameList}; + Setting row_1_text_id{linkage, 3, "row_1_text_id", Category::UiGameList}; + Setting row_2_text_id{linkage, 2, "row_2_text_id", Category::UiGameList}; + std::atomic_bool is_game_list_reload_pending{false}; + Setting cache_game_list{linkage, true, "cache_game_list", Category::UiGameList}; + Setting favorites_expanded{linkage, true, "favorites_expanded", Category::UiGameList}; + QVector favorited_ids; + + Setting grid_columns{linkage, 4, 1, 8, "grid_columns", Category::UiGameList}; + + // Compatibility List + Setting show_compat{linkage, false, "show_compat", Category::UiGameList}; + + // Size & File Types Column + Setting show_size{linkage, true, "show_size", Category::UiGameList}; + Setting show_types{linkage, true, "show_types", Category::UiGameList}; + + // Play time + Setting show_play_time{linkage, true, "show_play_time", Category::UiGameList}; + + bool configuration_applied; + bool reset_to_defaults; + bool shortcut_already_warned{false}; +}; + +extern Values values; + +u32 CalculateWidth(u32 height, Settings::AspectRatio ratio); + +void SaveWindowState(); +void RestoreWindowState(std::unique_ptr& qtConfig); + +// This shouldn't have anything except static initializers (no functions). So +// QKeySequence(...).toString() is NOT ALLOWED HERE. +// This must be in alphabetical order according to action name as it must have the same order as +// UISetting::values.shortcuts, which is alphabetically ordered. +// clang-format off +const std::array default_hotkeys{{ + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+M"), std::string("Home+Dpad_Right"), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("-"), std::string("Home+Dpad_Down"), Qt::ApplicationShortcut, true}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("="), std::string("Home+Dpad_Up"), Qt::ApplicationShortcut, true}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+P"), std::string("Screenshot"), Qt::WidgetWithChildrenShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F8"), std::string("Home+L"), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F10"), std::string("Home+X"), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change GPU Accuracy")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F9"), std::string("Home+R"), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Continue/Pause Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F4"), std::string("Home+Plus"), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Fullscreen")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Esc"), std::string(""), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit eden")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+Q"), std::string("Home+Minus"), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F11"), std::string("Home+B"), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+O"), std::string(""), Qt::WidgetWithChildrenShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F2"), std::string("Home+A"), Qt::WidgetWithChildrenShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Browse Public Game Lobby")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+B"), std::string(""), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Create Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+N"), std::string(""), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Direct Connect to Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+C"), std::string(""), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Leave Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+L"), std::string(""), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Show Current Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+R"), std::string(""), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F6"), std::string("R+Plus+Minus"), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F5"), std::string("L+Plus+Minus"), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F7"), std::string(""), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F6"), std::string(""), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F5"), std::string(""), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F"), std::string(""), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+U"), std::string("Home+Y"), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F9"), std::string(""), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Renderdoc Capture")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string(""), std::string(""), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+S"), std::string(""), Qt::WindowShortcut, false}}, +}}; +// clang-format on + +} // namespace UISettings + +Q_DECLARE_METATYPE(UISettings::GameDir*); + +// These metatype declarations cannot be in common/settings.h because core is devoid of QT +Q_DECLARE_METATYPE(Settings::CpuAccuracy); +Q_DECLARE_METATYPE(Settings::GpuAccuracy); +Q_DECLARE_METATYPE(Settings::FullscreenMode); +Q_DECLARE_METATYPE(Settings::NvdecEmulation); +Q_DECLARE_METATYPE(Settings::ResolutionSetup); +Q_DECLARE_METATYPE(Settings::ScalingFilter); +Q_DECLARE_METATYPE(Settings::AntiAliasing); +Q_DECLARE_METATYPE(Settings::RendererBackend); +Q_DECLARE_METATYPE(Settings::ShaderBackend); +Q_DECLARE_METATYPE(Settings::AstcRecompression); +Q_DECLARE_METATYPE(Settings::AstcDecodeMode); +Q_DECLARE_METATYPE(Settings::SpirvOptimizeMode); diff --git a/src/eden/main.cpp b/src/eden/main.cpp new file mode 100644 index 0000000000..6e4126d466 --- /dev/null +++ b/src/eden/main.cpp @@ -0,0 +1,56 @@ +#include "core/core.h" +#include "interface/SettingsInterface.h" +#include "interface/qt_config.h" +#include "models/GameListModel.h" +#include +#include +#include + +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + QQuickStyle::setStyle(QObject::tr("Material")); + + QCoreApplication::setOrganizationName(QStringLiteral("yuzu")); + QCoreApplication::setApplicationName(QStringLiteral("eden")); + + /// Settings, etc + Settings::SetConfiguringGlobal(true); + QtConfig *config = new QtConfig; + config->SaveAllValues(); + + // TODO: Save all values on launch and per game etc + app.connect(&app, &QCoreApplication::aboutToQuit, &app, [config]() { + config->SaveAllValues(); + }); + + /// Expose Enums + + // Core + std::unique_ptr system = std::make_unique(); + + /// CONTEXT + QQmlApplicationEngine engine; + + // Enums + qmlRegisterUncreatableMetaObject(SettingsCategories::staticMetaObject, "org.eden_emu.interface", 1, 0, "SettingsCategories", QString()); + + // Directory List + GameListModel *gameListModel = new GameListModel(&app); + engine.rootContext()->setContextProperty(QStringLiteral("EdenGameList"), gameListModel); + + /// LOAD + QObject::connect( + &engine, + &QQmlApplicationEngine::objectCreationFailed, + &app, + []() { QCoreApplication::exit(-1); }, + Qt::QueuedConnection); + + engine.loadFromModule("org.eden_emu.main", "Main"); + + return app.exec(); +} diff --git a/src/eden/models/CMakeLists.txt b/src/eden/models/CMakeLists.txt new file mode 100644 index 0000000000..f4e1f1f92f --- /dev/null +++ b/src/eden/models/CMakeLists.txt @@ -0,0 +1,16 @@ +set(CMAKE_AUTOMOC ON) + +# TODO: This might not need to be exposed to QML? +qt_add_library(edenModels STATIC) +qt_add_qml_module(edenModels + URI org.eden_emu.models + VERSION 1.0 + SOURCES + GameListModel.h GameListModel.cpp + SettingsModel.h SettingsModel.cpp +) + +target_link_libraries(edenModels + PRIVATE + Qt6::Gui +) diff --git a/src/eden/models/GameListModel.cpp b/src/eden/models/GameListModel.cpp new file mode 100644 index 0000000000..e20717970b --- /dev/null +++ b/src/eden/models/GameListModel.cpp @@ -0,0 +1,70 @@ +#include "GameListModel.h" + +#include + +const QStringList GameListModel::ValidSuffixes{"jpg", "png", "webp", "jpeg"}; + +GameListModel::GameListModel(QObject *parent) { + QHash rez = QStandardItemModel::roleNames(); + rez.insert(GLMRoleTypes::NAME, "name"); + rez.insert(GLMRoleTypes::PATH, "path"); + rez.insert(GLMRoleTypes::FILESIZE, "size"); + + QStandardItemModel::setItemRoleNames(rez); +} + +QVariant GameListModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (role == GLMRoleTypes::NAME) { + return itemFromIndex(index)->text(); + } + + return QStandardItemModel::data(index, role); +} + +void GameListModel::addDir(const QString &toAdd) +{ + QString name = toAdd; +#ifdef Q_OS_WINDOWS + name.replace("file:///", ""); +#else + name.replace("file://", ""); +#endif + + m_dirs << name; + reload(); +} + +void GameListModel::removeDir(const QString &toRemove) +{ + m_dirs.removeAll(toRemove); + reload(); +} + +void GameListModel::reload() +{ + clear(); + for (const QString &dir : std::as_const(m_dirs)) { + qDebug() << dir; + for (const auto &entry : QDirListing(dir, QDirListing::IteratorFlag::FilesOnly)) { + if (ValidSuffixes.contains(entry.completeSuffix().toLower())) { + QString path = entry.absoluteFilePath(); + QString name = entry.baseName(); + qreal size = entry.size(); + QString sizeString = QLocale::system().formattedDataSize(size); + + qDebug() << path << name << size; + // m_data << Game{path, name, size}; + + QStandardItem *game = new QStandardItem(name); + game->setData(path, GLMRoleTypes::PATH); + game->setData(sizeString, GLMRoleTypes::FILESIZE); + + invisibleRootItem()->appendRow(game); + } + } + } +} diff --git a/src/eden/models/GameListModel.h b/src/eden/models/GameListModel.h new file mode 100644 index 0000000000..fb6ce62955 --- /dev/null +++ b/src/eden/models/GameListModel.h @@ -0,0 +1,39 @@ +#ifndef GAMELISTMODEL_H +#define GAMELISTMODEL_H + +#include +#include + +typedef struct Game { + QString absPath; + QString name; + QString fileSize; +} Game; + +class GameListModel : public QStandardItemModel +{ + Q_OBJECT +public: + enum GLMRoleTypes { + NAME = Qt::UserRole + 1, + PATH, + FILESIZE + }; + + GameListModel(QObject *parent = nullptr); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + Q_INVOKABLE void addDir(const QString &toAdd); + Q_INVOKABLE void removeDir(const QString &toRemove); + + static const QStringList ValidSuffixes; + +private: + QStringList m_dirs; + QList m_data; + + void reload(); +}; + +#endif // GAMELISTMODEL_H diff --git a/src/eden/models/SettingsModel.cpp b/src/eden/models/SettingsModel.cpp new file mode 100644 index 0000000000..a6a9d1bb62 --- /dev/null +++ b/src/eden/models/SettingsModel.cpp @@ -0,0 +1,90 @@ +#include "SettingsModel.h" + +SettingsModel::SettingsModel(QObject* parent) : QAbstractListModel(parent) {} + +int SettingsModel::rowCount(const QModelIndex& parent) const { + return m_data.count(); +} + +QVariant SettingsModel::data(const QModelIndex& index, int role) const { + if (!index.isValid()) + return QVariant(); + + QMLSetting *s = m_data[index.row()]; + + switch (role) { + case LABEL: + return s->label(); + case TOOLTIP: + return s->tooltip(); + case VALUE: + return s->value(); + case ID: + return s->id(); + case TYPE: + return s->type(); + case COMBO: + return s->combo(); + case SUFFIX: + return s->suffix(); + case MIN: + return s->min(); + case MAX: + return s->max(); + case OTHER: + return QVariant::fromValue(s->other()); + default: + break; + } + + return QVariant(); +} + +bool SettingsModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (data(index, role) != value) { + QMLSetting *s = m_data[index.row()]; + + switch (role) { + case VALUE: + s->setValue(value); + + break; + } + emit dataChanged(index, index, {role}); + return true; + } + return false; + +} + +void SettingsModel::append(QMLSetting *setting) +{ + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + m_data << setting; + endInsertRows(); +} + +void SettingsModel::append(QList settings) +{ + for (QMLSetting *setting : settings) { + append(setting); + } +} + +QHash SettingsModel::roleNames() const +{ + QHash rez; + rez[LABEL] = "label"; + rez[TOOLTIP] = "tooltip"; + rez[VALUE] = "value"; + rez[ID] = "id"; + rez[TYPE] = "type"; + rez[COMBO] = "combo"; + rez[SUFFIX] = "suffix"; + rez[MIN] = "min"; + rez[MAX] = "max"; + rez[OTHER] = "other"; + + return rez; +} diff --git a/src/eden/models/SettingsModel.h b/src/eden/models/SettingsModel.h new file mode 100644 index 0000000000..0edaab7982 --- /dev/null +++ b/src/eden/models/SettingsModel.h @@ -0,0 +1,42 @@ +#ifndef SETTINGSMODEL_H +#define SETTINGSMODEL_H + +#include +#include "yuzu/interface/QMLSetting.h" + +class SettingsModel : public QAbstractListModel { + Q_OBJECT + +public: + enum SMTypes { + LABEL = Qt::UserRole + 1, + TOOLTIP, + ID, + VALUE, + TYPE, + COMBO, // combobox translations + SUFFIX, + MIN, + MAX, + OTHER + }; + + explicit SettingsModel(QObject* parent = nullptr); + + // Basic functionality: + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + void append(QMLSetting *setting); + void append(QList settings); + +protected: + QHash roleNames() const override; + +private: + QList m_data; +}; + +#endif // SETTINGSMODEL_H diff --git a/src/eden/qml/CMakeLists.txt b/src/eden/qml/CMakeLists.txt new file mode 100644 index 0000000000..958f84c3dd --- /dev/null +++ b/src/eden/qml/CMakeLists.txt @@ -0,0 +1,5 @@ +add_subdirectory(config) +add_subdirectory(constants) +add_subdirectory(items) + +add_subdirectory(main) diff --git a/src/eden/qml/config/CMakeLists.txt b/src/eden/qml/config/CMakeLists.txt new file mode 100644 index 0000000000..94b0bf7a0e --- /dev/null +++ b/src/eden/qml/config/CMakeLists.txt @@ -0,0 +1,57 @@ +set(CMAKE_AUTOMOC ON) + +qt_add_library(edenConfig STATIC) +qt_add_qml_module(edenConfig + URI org.eden_emu.config + VERSION 1.0 + + QML_FILES GlobalConfigureDialog.qml + QML_FILES Setting.qml + QML_FILES SectionHeader.qml + + QML_FILES pages/SettingsList.qml + + QML_FILES pages/global/GlobalTab.qml + QML_FILES pages/global/GlobalTabSwipeView.qml + QML_FILES pages/global/GlobalGeneralPage.qml + + QML_FILES pages/global/GlobalSystemPage.qml + QML_FILES pages/global/GlobalCpuPage.qml + QML_FILES pages/global/GlobalGraphicsPage.qml + QML_FILES pages/global/GlobalAudioPage.qml + + QML_FILES pages/general/UiGeneralPage.qml + QML_FILES pages/general/UiGameListPage.qml + QML_FILES + + QML_FILES pages/graphics/RendererPage.qml + QML_FILES pages/graphics/RendererAdvancedPage.qml + QML_FILES pages/graphics/RendererExtensionsPage.qml + + QML_FILES pages/system/SystemGeneralPage.qml + QML_FILES + QML_FILES pages/system/FileSystemPage.qml + QML_FILES pages/system/AppletsPage.qml + + QML_FILES pages/cpu/CpuGeneralPage.qml + + QML_FILES pages/audio/AudioGeneralPage.qml + QML_FILES pages/global/GlobalDebugPage.qml + QML_FILES pages/debug/DebugGeneralPage.qml + QML_FILES pages/debug/DebugGraphicsPage.qml + QML_FILES pages/debug/DebugAdvancedPage.qml + QML_FILES pages/debug/DebugCpuPage.qml + QML_FILES fields/ConfigCheckbox.qml + QML_FILES fields/FieldLabel.qml + QML_FILES fields/ConfigComboBox.qml + QML_FILES TestSetting.qml + QML_FILES fields/ConfigIntLine.qml + QML_FILES fields/ConfigTimeEdit.qml + QML_FILES fields/ConfigIntSpin.qml + QML_FILES fields/ConfigHexEdit.qml + QML_FILES fields/ConfigStringEdit.qml + QML_FILES fields/FieldCheckbox.qml + QML_FILES fields/ConfigIntSlider.qml + QML_FILES pages/system/SystemCorePage.qml + QML_FILES fields/BaseField.qml +) diff --git a/src/eden/qml/config/GlobalConfigureDialog.qml b/src/eden/qml/config/GlobalConfigureDialog.qml new file mode 100644 index 0000000000..869a682858 --- /dev/null +++ b/src/eden/qml/config/GlobalConfigureDialog.qml @@ -0,0 +1,73 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.constants +import org.eden_emu.items +import org.eden_emu.interface + +Dialog { + preferredWidth: 1280 + + title: "Configuration" + standardButtons: Dialog.Ok | Dialog.Cancel + + onOpened: { + for (var tab in tabBar.children) { + console.log(tab) + } + } + + onAccepted: { + + } + + VerticalTabBar { + id: tabBar + + anchors { + top: parent.top + topMargin: 55 * Constants.scalar + + left: parent.left + bottom: parent.bottom + } + contentWidth: 100 + + currentIndex: swipe.currentIndex + + Repeater { + model: ["General", "System", "CPU", "Graphics", "Audio", "Debug", "Controls"] + + SettingsTabButton { + required property string modelData + label: modelData + onClicked: tabBar.currentIndex = TabBar.index + } + } + } + + SwipeView { + id: swipe + currentIndex: tabBar.currentIndex + + orientation: Qt.Vertical + anchors { + left: tabBar.right + right: parent.right + top: parent.top + bottom: parent.bottom + + leftMargin: 5 * Constants.scalar + } + + clip: true + + GlobalGeneralPage {} + GlobalSystemPage {} + GlobalCpuPage {} + GlobalGraphicsPage {} + GlobalAudioPage {} + GlobalDebugPage {} + } +} diff --git a/src/eden/qml/config/SectionHeader.qml b/src/eden/qml/config/SectionHeader.qml new file mode 100644 index 0000000000..2a69504bd2 --- /dev/null +++ b/src/eden/qml/config/SectionHeader.qml @@ -0,0 +1,8 @@ +import QtQuick + +import org.eden_emu.constants + +Text { + color: Constants.text + font.pixelSize: 16 +} diff --git a/src/eden/qml/config/Setting.qml b/src/eden/qml/config/Setting.qml new file mode 100644 index 0000000000..5da913689c --- /dev/null +++ b/src/eden/qml/config/Setting.qml @@ -0,0 +1,92 @@ +import QtQuick + +import Qt.labs.qmlmodels + +import org.eden_emu.config + +// TODO: make settings independently available (model vs setting? +DelegateChooser { + id: chooser + role: "type" + + DelegateChoice { + roleValue: "bool" + + ConfigCheckbox { + setting: model + width: ListView.view.width + } + } + + DelegateChoice { + roleValue: "enumCombo" + + ConfigComboBox { + setting: model + width: ListView.view.width + } + } + + DelegateChoice { + roleValue: "intCombo" + + ConfigComboBox { + setting: model + width: ListView.view.width + } + } + + DelegateChoice { + roleValue: "intLine" + + ConfigIntLine { + setting: model + width: ListView.view.width + } + } + + DelegateChoice { + roleValue: "intSlider" + + ConfigIntSlider { + setting: model + width: ListView.view.width + } + } + + DelegateChoice { + roleValue: "time" + + ConfigTimeEdit { + setting: model + width: ListView.view.width + } + } + + DelegateChoice { + roleValue: "intSpin" + + ConfigIntSpin { + setting: model + width: ListView.view.width + } + } + + DelegateChoice { + roleValue: "stringLine" + + ConfigStringEdit { + setting: model + width: ListView.view.width + } + } + + DelegateChoice { + roleValue: "hex" + + ConfigHexEdit { + setting: model + width: ListView.view.width + } + } +} diff --git a/src/eden/qml/config/TestSetting.qml b/src/eden/qml/config/TestSetting.qml new file mode 100644 index 0000000000..e8d6e36d50 --- /dev/null +++ b/src/eden/qml/config/TestSetting.qml @@ -0,0 +1,39 @@ +import QtQuick +import QtQuick.Layouts + +import org.eden_emu.constants + +Column { + topPadding: 5 * Constants.scalar + leftPadding: 10 * Constants.scalar + + RowLayout { + uniformCellSizes: true + Text { + Layout.fillWidth: true + text: model.label + color: Constants.text + font.pixelSize: 16 + + height: 40 + } + + Text { + Layout.fillWidth: true + text: model.value + color: "lightblue" + font.pixelSize: 14 + + height: 40 + horizontalAlignment: Text.AlignRight + } + } + + Text { + text: model.type + " " + typeof model.value + " " + model.other + color: "lightgray" + font.pixelSize: 12 + + height: 25 + } +} diff --git a/src/eden/qml/config/fields/BaseField.qml b/src/eden/qml/config/fields/BaseField.qml new file mode 100644 index 0000000000..092f69f3eb --- /dev/null +++ b/src/eden/qml/config/fields/BaseField.qml @@ -0,0 +1,160 @@ +import QtQuick +import QtQuick.Layouts + +import org.eden_emu.items +import org.eden_emu.config +import org.eden_emu.constants + +Item { + id: field + property var setting + property var value + property bool showLabel: true + + property alias enable: enable.checked + property Item contentItem + + clip: true + height: content.height + (helpText.height + helpText.anchors.topMargin) + + function apply() { + setting.value = value + } + + function sync() { + value = setting.value + } + + RowLayout { + id: content + height: 45 * Constants.scalar + + spacing: 0 + + anchors { + left: parent.left + right: parent.right + } + + z: 2 + + IconButton { + label: "help" + icon.width: 20 + icon.height: 20 + + onClicked: helpText.toggle() + visible: setting.tooltip !== "" + z: 2 + } + + FieldCheckbox { + id: enable + setting: field.setting + z: 2 + } + + RowLayout { + Layout.fillWidth: true + uniformCellSizes: true + spacing: 0 + z: 2 + + FieldLabel { + z: 2 + id: label + setting: field.setting + } + + children: showLabel ? [label, contentItem] : [contentItem] + } + } + + Rectangle { + color: Constants.dialog + anchors.fill: content + z: 0 + } + + Text { + id: helpText + + anchors { + left: parent.left + leftMargin: 20 * Constants.scalar + right: parent.right + rightMargin: 20 * Constants.scalar + + top: content.bottom + topMargin: -height + } + + z: -1 + + text: setting.tooltip + color: Constants.subText + font.pixelSize: 12 * Constants.scalar + wrapMode: Text.WordWrap + + visible: false + opacity: 0 + + function toggle() { + if (visible) { + hideAnim.start() + } else { + showAnim.start() + } + } + + ParallelAnimation { + id: showAnim + + SmoothedAnimation { + target: helpText + property: "opacity" + from: 0 + to: 1 + + velocity: 3 + } + + SmoothedAnimation { + target: helpText + property: "anchors.topMargin" + from: -helpText.height + to: 0 + + duration: 300 + velocity: -1 + } + + onStarted: helpText.visible = true + } + + ParallelAnimation { + id: hideAnim + + SmoothedAnimation { + target: helpText + property: "opacity" + from: 1 + to: 0 + + velocity: 3 + } + + SmoothedAnimation { + target: helpText + property: "anchors.topMargin" + from: 0 + to: -helpText.height + + duration: 300 + velocity: -1 + } + + onFinished: helpText.visible = false + } + } +} diff --git a/src/eden/qml/config/fields/ConfigCheckbox.qml b/src/eden/qml/config/fields/ConfigCheckbox.qml new file mode 100644 index 0000000000..43c84e2248 --- /dev/null +++ b/src/eden/qml/config/fields/ConfigCheckbox.qml @@ -0,0 +1,27 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.constants + +BaseField { + showLabel: false + // TODO: global/custom + contentItem: CheckBox { + id: control + + Layout.rightMargin: 10 * Constants.scalar + Layout.fillWidth: true + + font.pixelSize: 15 * Constants.scalar + indicator.implicitHeight: 25 * Constants.scalar + indicator.implicitWidth: 25 * Constants.scalar + + text: setting.label + checked: setting.value + + onClicked: setting.value = checked + + checkable: true + } +} diff --git a/src/eden/qml/config/fields/ConfigComboBox.qml b/src/eden/qml/config/fields/ConfigComboBox.qml new file mode 100644 index 0000000000..c4762813b7 --- /dev/null +++ b/src/eden/qml/config/fields/ConfigComboBox.qml @@ -0,0 +1,33 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts +import QtQuick.Controls.Material.impl + +import org.eden_emu.constants +import org.eden_emu.config + +BaseField { + contentItem: ComboBox { + id: control + enabled: enable + + Layout.fillWidth: true + Layout.rightMargin: 10 * Constants.scalar + + font.pixelSize: 14 * Constants.scalar + model: setting.combo + currentIndex: value + + background: MaterialTextContainer { + implicitWidth: 120 + implicitHeight: 40 * Constants.scalar + + outlineColor: (enabled + && control.hovered) ? control.Material.primaryTextColor : control.Material.hintTextColor + focusedOutlineColor: control.Material.accentColor + controlHasActiveFocus: control.activeFocus + controlHasText: true + horizontalPadding: control.Material.textFieldHorizontalPadding + } + } +} diff --git a/src/eden/qml/config/fields/ConfigHexEdit.qml b/src/eden/qml/config/fields/ConfigHexEdit.qml new file mode 100644 index 0000000000..983b0e94c7 --- /dev/null +++ b/src/eden/qml/config/fields/ConfigHexEdit.qml @@ -0,0 +1,26 @@ +import QtQuick +import QtQuick.Layouts + +import org.eden_emu.items +import org.eden_emu.config +import org.eden_emu.constants + +BaseField { + contentItem: BetterTextField { + enabled: enable + + Layout.fillWidth: true + Layout.rightMargin: 10 * Constants.scalar + + validator: RegularExpressionValidator { + regularExpression: /[0-9a-fA-F]{0,8}/ + } + + font.pixelSize: 15 * Constants.scalar + + text: Number(setting.value).toString(16) + suffix: setting.suffix + + onTextEdited: setting.value = Number("0x" + text) + } +} diff --git a/src/eden/qml/config/fields/ConfigIntLine.qml b/src/eden/qml/config/fields/ConfigIntLine.qml new file mode 100644 index 0000000000..7f1653207c --- /dev/null +++ b/src/eden/qml/config/fields/ConfigIntLine.qml @@ -0,0 +1,28 @@ +import QtQuick +import QtQuick.Layouts + +import org.eden_emu.items +import org.eden_emu.config +import org.eden_emu.constants + +BaseField { + contentItem: BetterTextField { + enabled: enable + + Layout.fillWidth: true + Layout.rightMargin: 10 * Constants.scalar + + inputMethodHints: Qt.ImhDigitsOnly + validator: IntValidator { + bottom: setting.min + top: setting.max + } + + font.pixelSize: 15 * Constants.scalar + + text: setting.value + suffix: setting.suffix + + onTextEdited: setting.value = parseInt(text) + } +} diff --git a/src/eden/qml/config/fields/ConfigIntSlider.qml b/src/eden/qml/config/fields/ConfigIntSlider.qml new file mode 100644 index 0000000000..eac6f66993 --- /dev/null +++ b/src/eden/qml/config/fields/ConfigIntSlider.qml @@ -0,0 +1,39 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.items +import org.eden_emu.config +import org.eden_emu.constants + +// Lots of cancer but idrc +BaseField { + contentItem: RowLayout { + Layout.fillWidth: true + + Slider { + Layout.fillWidth: true + + from: setting.min + to: setting.max + stepSize: 1 + + value: setting.value + + onMoved: setting.value = value + + Layout.rightMargin: 10 * Constants.scalar + + snapMode: Slider.SnapAlways + } + + Text { + font.pixelSize: 14 * Constants.scalar + color: Constants.text + + text: setting.value + setting.suffix + + Layout.rightMargin: 10 * Constants.scalar + } + } +} diff --git a/src/eden/qml/config/fields/ConfigIntSpin.qml b/src/eden/qml/config/fields/ConfigIntSpin.qml new file mode 100644 index 0000000000..7d65e90663 --- /dev/null +++ b/src/eden/qml/config/fields/ConfigIntSpin.qml @@ -0,0 +1,25 @@ +import QtQuick +import QtQuick.Layouts + +import org.eden_emu.items +import org.eden_emu.config +import org.eden_emu.constants + +BaseField { + contentItem: BetterSpinBox { + enabled: enable + + Layout.fillWidth: true + Layout.rightMargin: 10 * Constants.scalar + + from: setting.min + to: setting.max + + font.pixelSize: 15 * Constants.scalar + + value: setting.value + label: setting.suffix + + onValueModified: setting.value = value + } +} diff --git a/src/eden/qml/config/fields/ConfigStringEdit.qml b/src/eden/qml/config/fields/ConfigStringEdit.qml new file mode 100644 index 0000000000..32873c96b2 --- /dev/null +++ b/src/eden/qml/config/fields/ConfigStringEdit.qml @@ -0,0 +1,22 @@ +import QtQuick +import QtQuick.Layouts + +import org.eden_emu.items +import org.eden_emu.config +import org.eden_emu.constants + +BaseField { + contentItem: BetterTextField { + enabled: enable + + Layout.fillWidth: true + Layout.rightMargin: 10 * Constants.scalar + + font.pixelSize: 15 * Constants.scalar + + text: setting.value + suffix: setting.suffix + + onTextEdited: setting.value = text + } +} diff --git a/src/eden/qml/config/fields/ConfigTimeEdit.qml b/src/eden/qml/config/fields/ConfigTimeEdit.qml new file mode 100644 index 0000000000..e070102ca3 --- /dev/null +++ b/src/eden/qml/config/fields/ConfigTimeEdit.qml @@ -0,0 +1,27 @@ +import QtQuick +import QtQuick.Layouts + +import org.eden_emu.items +import org.eden_emu.config +import org.eden_emu.constants + +BaseField { + // TODO: real impl + contentItem: BetterTextField { + enabled: enable + + Layout.fillWidth: true + Layout.rightMargin: 10 * Constants.scalar + + inputMethodHints: Qt.ImhDigitsOnly + validator: IntValidator { + bottom: setting.min + top: setting.max + } + + font.pixelSize: 15 * Constants.scalar + + text: setting.value + suffix: setting.suffix + } +} diff --git a/src/eden/qml/config/fields/FieldCheckbox.qml b/src/eden/qml/config/fields/FieldCheckbox.qml new file mode 100644 index 0000000000..f1bc4039e6 --- /dev/null +++ b/src/eden/qml/config/fields/FieldCheckbox.qml @@ -0,0 +1,17 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import org.eden_emu.constants + +CheckBox { + property var setting + + indicator.implicitHeight: 25 * Constants.scalar + indicator.implicitWidth: 25 * Constants.scalar + + checked: setting.other !== null ? setting.other.value : true + onClicked: setting.other.value = checked + + visible: setting.other !== null +} diff --git a/src/eden/qml/config/fields/FieldLabel.qml b/src/eden/qml/config/fields/FieldLabel.qml new file mode 100644 index 0000000000..71ef3ad228 --- /dev/null +++ b/src/eden/qml/config/fields/FieldLabel.qml @@ -0,0 +1,18 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import org.eden_emu.constants + +Text { + property var setting + + text: setting.label + color: Constants.text + font.pixelSize: 14 * Constants.scalar + + height: 50 * Constants.scalar + ToolTip.text: setting.tooltip + + Layout.fillWidth: true +} diff --git a/src/eden/qml/config/icons.qrc b/src/eden/qml/config/icons.qrc new file mode 100644 index 0000000000..7646d2b36c --- /dev/null +++ b/src/eden/qml/config/icons.qrc @@ -0,0 +1 @@ + diff --git a/src/eden/qml/config/pages/SettingsList.qml b/src/eden/qml/config/pages/SettingsList.qml new file mode 100644 index 0000000000..13a1b4fdbf --- /dev/null +++ b/src/eden/qml/config/pages/SettingsList.qml @@ -0,0 +1,47 @@ +import QtQuick +import QtQuick.Layouts + +import org.eden_emu.config +import org.eden_emu.constants +import org.eden_emu.interface + +ColumnLayout { + required property int category + + property bool inset: false + property string header: "" + property list idInclude: [] + property list idExclude: [] + + SectionHeader { + text: header + visible: header != "" + } + + ListView { + clip: true + boundsBehavior: Flickable.StopAtBounds + + interactive: false + + implicitHeight: contentHeight + delegate: Setting {} + + Layout.fillHeight: true + Layout.fillWidth: true + Layout.leftMargin: 5 * Constants.scalar + spacing: 8 * Constants.scalar + + model: SettingsInterface.category(category, idInclude, idExclude) + + Rectangle { + anchors.fill: parent + color: "transparent" + + border { + color: inset ? Constants.text : "transparent" + width: 1 + } + } + } +} diff --git a/src/eden/qml/config/pages/audio/AudioGeneralPage.qml b/src/eden/qml/config/pages/audio/AudioGeneralPage.qml new file mode 100644 index 0000000000..83217cb420 --- /dev/null +++ b/src/eden/qml/config/pages/audio/AudioGeneralPage.qml @@ -0,0 +1,17 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.interface +import org.eden_emu.config + +ScrollView { + id: scroll + ColumnLayout { + width: scroll.width - scroll.effectiveScrollBarWidth + + SettingsList { + category: SettingsCategories.Audio + } + } +} diff --git a/src/eden/qml/config/pages/cpu/CpuGeneralPage.qml b/src/eden/qml/config/pages/cpu/CpuGeneralPage.qml new file mode 100644 index 0000000000..49e52a87f9 --- /dev/null +++ b/src/eden/qml/config/pages/cpu/CpuGeneralPage.qml @@ -0,0 +1,17 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.interface +import org.eden_emu.config + +ScrollView { + id: scroll + ColumnLayout { + width: scroll.width - scroll.effectiveScrollBarWidth + + SettingsList { + category: SettingsCategories.Cpu + } + } +} diff --git a/src/eden/qml/config/pages/debug/DebugAdvancedPage.qml b/src/eden/qml/config/pages/debug/DebugAdvancedPage.qml new file mode 100644 index 0000000000..c5ee3b73b3 --- /dev/null +++ b/src/eden/qml/config/pages/debug/DebugAdvancedPage.qml @@ -0,0 +1,19 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.interface +import org.eden_emu.config + +ScrollView { + id: scroll + + ColumnLayout { + width: scroll.width - scroll.effectiveScrollBarWidth + + // TODO: filter + SettingsList { + category: SettingsCategories.Debugging + } + } +} diff --git a/src/eden/qml/config/pages/debug/DebugCpuPage.qml b/src/eden/qml/config/pages/debug/DebugCpuPage.qml new file mode 100644 index 0000000000..67c9f612d1 --- /dev/null +++ b/src/eden/qml/config/pages/debug/DebugCpuPage.qml @@ -0,0 +1,18 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.interface +import org.eden_emu.config + +ScrollView { + id: scroll + + ColumnLayout { + width: scroll.width - scroll.effectiveScrollBarWidth + + SettingsList { + category: SettingsCategories.CpuDebug + } + } +} diff --git a/src/eden/qml/config/pages/debug/DebugGeneralPage.qml b/src/eden/qml/config/pages/debug/DebugGeneralPage.qml new file mode 100644 index 0000000000..7fa39e315e --- /dev/null +++ b/src/eden/qml/config/pages/debug/DebugGeneralPage.qml @@ -0,0 +1,28 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.interface +import org.eden_emu.config + +ScrollView { + id: scroll + + ColumnLayout { + width: scroll.width - scroll.effectiveScrollBarWidth + + RowLayout { + Layout.fillWidth: true + + // TODO: split + SettingsList { + category: SettingsCategories.Debugging + } + + // TODO: wrong category? + SettingsList { + category: SettingsCategories.Miscellaneous + } + } + } +} diff --git a/src/eden/qml/config/pages/debug/DebugGraphicsPage.qml b/src/eden/qml/config/pages/debug/DebugGraphicsPage.qml new file mode 100644 index 0000000000..8f34c525ee --- /dev/null +++ b/src/eden/qml/config/pages/debug/DebugGraphicsPage.qml @@ -0,0 +1,20 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.interface +import org.eden_emu.config + +ScrollView { + id: scroll + + ColumnLayout { + width: scroll.width - scroll.effectiveScrollBarWidth + + SettingsList { + Layout.fillWidth: true + + category: SettingsCategories.DebuggingGraphics + } + } +} diff --git a/src/eden/qml/config/pages/general/UiGameListPage.qml b/src/eden/qml/config/pages/general/UiGameListPage.qml new file mode 100644 index 0000000000..7ec92a11ba --- /dev/null +++ b/src/eden/qml/config/pages/general/UiGameListPage.qml @@ -0,0 +1,18 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.interface +import org.eden_emu.config + +ScrollView { + id: scroll + // TODO: language, theme + ColumnLayout { + width: scroll.width - scroll.effectiveScrollBarWidth + + SettingsList { + category: SettingsCategories.UiGameList + } + } +} diff --git a/src/eden/qml/config/pages/general/UiGeneralPage.qml b/src/eden/qml/config/pages/general/UiGeneralPage.qml new file mode 100644 index 0000000000..06ad55d49c --- /dev/null +++ b/src/eden/qml/config/pages/general/UiGeneralPage.qml @@ -0,0 +1,23 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.interface +import org.eden_emu.config + +ScrollView { + id: scroll + ColumnLayout { + width: scroll.width - scroll.effectiveScrollBarWidth + + SettingsList { + category: SettingsCategories.UiGeneral + // onContentHeightChanged: console.log(height, parent.height) + } + + SettingsList { + category: SettingsCategories.Linux + visible: Qt.platform.os === "linux" + } + } +} diff --git a/src/eden/qml/config/pages/global/GlobalAudioPage.qml b/src/eden/qml/config/pages/global/GlobalAudioPage.qml new file mode 100644 index 0000000000..ddfdef51f8 --- /dev/null +++ b/src/eden/qml/config/pages/global/GlobalAudioPage.qml @@ -0,0 +1,14 @@ +import QtQuick + +import org.eden_emu.config + +GlobalTab { + tabs: ["Audio"] + + GlobalTabSwipeView { + id: swipe + currentIndex: tabBar.currentIndex + + AudioGeneralPage {} + } +} diff --git a/src/eden/qml/config/pages/global/GlobalCpuPage.qml b/src/eden/qml/config/pages/global/GlobalCpuPage.qml new file mode 100644 index 0000000000..bcca4f17ae --- /dev/null +++ b/src/eden/qml/config/pages/global/GlobalCpuPage.qml @@ -0,0 +1,14 @@ +import QtQuick + +import org.eden_emu.config + +GlobalTab { + tabs: ["CPU"] + + GlobalTabSwipeView { + id: swipe + currentIndex: tabBar.currentIndex + + CpuGeneralPage {} + } +} diff --git a/src/eden/qml/config/pages/global/GlobalDebugPage.qml b/src/eden/qml/config/pages/global/GlobalDebugPage.qml new file mode 100644 index 0000000000..85df6bd1e4 --- /dev/null +++ b/src/eden/qml/config/pages/global/GlobalDebugPage.qml @@ -0,0 +1,17 @@ +import QtQuick + +import org.eden_emu.config + +GlobalTab { + tabs: ["General", "Graphics", "Advanced", "CPU"] + + GlobalTabSwipeView { + id: swipe + currentIndex: tabBar.currentIndex + + DebugGeneralPage {} + DebugGraphicsPage {} + DebugAdvancedPage {} + DebugCpuPage {} + } +} diff --git a/src/eden/qml/config/pages/global/GlobalGeneralPage.qml b/src/eden/qml/config/pages/global/GlobalGeneralPage.qml new file mode 100644 index 0000000000..30a782d5a8 --- /dev/null +++ b/src/eden/qml/config/pages/global/GlobalGeneralPage.qml @@ -0,0 +1,17 @@ +import QtQuick + +import org.eden_emu.config + +GlobalTab { + tabs: ["General", "Hotkeys", "Game List"] + + GlobalTabSwipeView { + id: swipe + currentIndex: tabBar.currentIndex + + // TODO: platform-specific stuff + UiGeneralPage {} + Item {} + UiGameListPage {} + } +} diff --git a/src/eden/qml/config/pages/global/GlobalGraphicsPage.qml b/src/eden/qml/config/pages/global/GlobalGraphicsPage.qml new file mode 100644 index 0000000000..af42494591 --- /dev/null +++ b/src/eden/qml/config/pages/global/GlobalGraphicsPage.qml @@ -0,0 +1,16 @@ +import QtQuick + +import org.eden_emu.config + +GlobalTab { + tabs: ["Graphics", "Advanced", "Extensions"] + + GlobalTabSwipeView { + id: swipe + currentIndex: tabBar.currentIndex + + RendererPage {} + RendererAdvancedPage {} + RendererExtensionsPage {} + } +} diff --git a/src/eden/qml/config/pages/global/GlobalSystemPage.qml b/src/eden/qml/config/pages/global/GlobalSystemPage.qml new file mode 100644 index 0000000000..20f0780c97 --- /dev/null +++ b/src/eden/qml/config/pages/global/GlobalSystemPage.qml @@ -0,0 +1,18 @@ +import QtQuick + +import org.eden_emu.config + +GlobalTab { + tabs: ["System", "Core", "Profiles", "Filesystem", "Applets"] + + GlobalTabSwipeView { + id: swipe + currentIndex: tabBar.currentIndex + + SystemGeneralPage {} + SystemCorePage {} + Item {} + FileSystemPage {} + AppletsPage {} + } +} diff --git a/src/eden/qml/config/pages/global/GlobalTab.qml b/src/eden/qml/config/pages/global/GlobalTab.qml new file mode 100644 index 0000000000..68a262aedc --- /dev/null +++ b/src/eden/qml/config/pages/global/GlobalTab.qml @@ -0,0 +1,34 @@ +import QtQuick 2.15 +import QtQuick.Controls.Material + +import org.eden_emu.constants + +Item { + required property list tabs + property alias tabBar: tabBar + + TabBar { + id: tabBar + currentIndex: swipe.currentIndex + + anchors { + top: parent.top + left: parent.left + right: parent.right + } + + Repeater { + model: tabs + + TabButton { + font.pixelSize: 16 * Constants.scalar + text: modelData + } + } + + background: Rectangle { + color: tabBar.Material.backgroundColor + radius: 8 * Constants.scalar + } + } +} diff --git a/src/eden/qml/config/pages/global/GlobalTabSwipeView.qml b/src/eden/qml/config/pages/global/GlobalTabSwipeView.qml new file mode 100644 index 0000000000..b5f746d02e --- /dev/null +++ b/src/eden/qml/config/pages/global/GlobalTabSwipeView.qml @@ -0,0 +1,16 @@ +import QtQuick 2.15 +import QtQuick.Controls.Material + +import org.eden_emu.constants + +SwipeView { + anchors { + top: tabBar.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + + leftMargin: 20 * Constants.scalar + topMargin: 10 * Constants.scalar + } +} diff --git a/src/eden/qml/config/pages/graphics/RendererAdvancedPage.qml b/src/eden/qml/config/pages/graphics/RendererAdvancedPage.qml new file mode 100644 index 0000000000..d9699a5120 --- /dev/null +++ b/src/eden/qml/config/pages/graphics/RendererAdvancedPage.qml @@ -0,0 +1,17 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.interface +import org.eden_emu.config + +ScrollView { + id: scroll + ColumnLayout { + width: scroll.width - scroll.effectiveScrollBarWidth + + SettingsList { + category: SettingsCategories.RendererAdvanced + } + } +} diff --git a/src/eden/qml/config/pages/graphics/RendererExtensionsPage.qml b/src/eden/qml/config/pages/graphics/RendererExtensionsPage.qml new file mode 100644 index 0000000000..ce6f5536d7 --- /dev/null +++ b/src/eden/qml/config/pages/graphics/RendererExtensionsPage.qml @@ -0,0 +1,17 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.interface +import org.eden_emu.config + +ScrollView { + id: scroll + ColumnLayout { + width: scroll.width - scroll.effectiveScrollBarWidth + + SettingsList { + category: SettingsCategories.RendererExtensions + } + } +} diff --git a/src/eden/qml/config/pages/graphics/RendererPage.qml b/src/eden/qml/config/pages/graphics/RendererPage.qml new file mode 100644 index 0000000000..99b95a2699 --- /dev/null +++ b/src/eden/qml/config/pages/graphics/RendererPage.qml @@ -0,0 +1,17 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.interface +import org.eden_emu.config + +ScrollView { + id: scroll + ColumnLayout { + width: scroll.width - scroll.effectiveScrollBarWidth + + SettingsList { + category: SettingsCategories.Renderer + } + } +} diff --git a/src/eden/qml/config/pages/system/AppletsPage.qml b/src/eden/qml/config/pages/system/AppletsPage.qml new file mode 100644 index 0000000000..4759fe1546 --- /dev/null +++ b/src/eden/qml/config/pages/system/AppletsPage.qml @@ -0,0 +1,17 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.interface +import org.eden_emu.config + +ScrollView { + id: scroll + ColumnLayout { + width: scroll.width - scroll.effectiveScrollBarWidth + + SettingsList { + category: SettingsCategories.LibraryApplet + } + } +} diff --git a/src/eden/qml/config/pages/system/FileSystemPage.qml b/src/eden/qml/config/pages/system/FileSystemPage.qml new file mode 100644 index 0000000000..ad0b230e5b --- /dev/null +++ b/src/eden/qml/config/pages/system/FileSystemPage.qml @@ -0,0 +1,17 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.interface +import org.eden_emu.config + +ScrollView { + id: scroll + ColumnLayout { + width: scroll.width - scroll.effectiveScrollBarWidth + + SettingsList { + category: SettingsCategories.DataStorage + } + } +} diff --git a/src/eden/qml/config/pages/system/SystemCorePage.qml b/src/eden/qml/config/pages/system/SystemCorePage.qml new file mode 100644 index 0000000000..913e9a456f --- /dev/null +++ b/src/eden/qml/config/pages/system/SystemCorePage.qml @@ -0,0 +1,17 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.interface +import org.eden_emu.config + +ScrollView { + id: scroll + ColumnLayout { + width: scroll.width - scroll.effectiveScrollBarWidth + + SettingsList { + category: SettingsCategories.Core + } + } +} diff --git a/src/eden/qml/config/pages/system/SystemGeneralPage.qml b/src/eden/qml/config/pages/system/SystemGeneralPage.qml new file mode 100644 index 0000000000..dab840e5ad --- /dev/null +++ b/src/eden/qml/config/pages/system/SystemGeneralPage.qml @@ -0,0 +1,22 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.interface +import org.eden_emu.config + +ScrollView { + id: scroll + ColumnLayout { + width: scroll.width - scroll.effectiveScrollBarWidth + + SettingsList { + category: SettingsCategories.Network + } + + SettingsList { + category: SettingsCategories.System + idExclude: ["custom_rtc", "custom_rtc_offset", "current_user"] + } + } +} diff --git a/src/eden/qml/constants/CMakeLists.txt b/src/eden/qml/constants/CMakeLists.txt new file mode 100644 index 0000000000..27e8a6a6c1 --- /dev/null +++ b/src/eden/qml/constants/CMakeLists.txt @@ -0,0 +1,23 @@ +set(CMAKE_AUTOMOC ON) + +find_package(Qt6 REQUIRED COMPONENTS Quick) + +set_source_files_properties(Constants.qml + PROPERTIES + QT_QML_SINGLETON_TYPE true +) + +qt_add_library(edenConstants STATIC) +qt_add_qml_module(edenConstants + URI org.eden_emu.constants + OUTPUT_DIRECTORY EdenConstants + VERSION 1.0 + QML_FILES + + Constants.qml +) + +target_link_libraries(edenConstants + PRIVATE + Qt6::Quick +) diff --git a/src/eden/qml/constants/Constants.qml b/src/eden/qml/constants/Constants.qml new file mode 100644 index 0000000000..6ca069e3b8 --- /dev/null +++ b/src/eden/qml/constants/Constants.qml @@ -0,0 +1,24 @@ +pragma Singleton + +import QtQuick + +QtObject { + readonly property int width: 1200 + readonly property int height: 1000 + + property color accent: "#FF4444" + property color accentPressed: "#ff5252" + + readonly property color bg: "#111111" + readonly property color dialog: "#222222" + readonly property color dialogButton: "#000000" + readonly property color sub: "#181818" + + readonly property color button: "#1E1E1E" + readonly property color buttonHighlighted: "#4A4A4A" + + readonly property color text: "#EEEEEE" + readonly property color subText: "#AAAAAA" + + property real scalar: 1.0 +} diff --git a/src/eden/qml/items/AnimatedDialog.qml b/src/eden/qml/items/AnimatedDialog.qml new file mode 100644 index 0000000000..98fc2e8d9e --- /dev/null +++ b/src/eden/qml/items/AnimatedDialog.qml @@ -0,0 +1,79 @@ +import QtQuick +import QtQuick.Controls.Material + +import org.eden_emu.constants + +Dialog { + id: dia + + property int preferredWidth: Overlay.overlay.width / 2 + property int preferredHeight: Overlay.overlay.height / 1.25 + + width: Math.min(preferredWidth, Overlay.overlay.width) + height: Math.min(preferredHeight, Overlay.overlay.height) + + property int radius: 12 + property bool colorful: false + + anchors.centerIn: Overlay.overlay + + enter: Transition { + NumberAnimation { + property: "opacity" + duration: 200 + + from: 0.0 + to: 1.0 + } + } + + exit: Transition { + NumberAnimation { + property: "opacity" + duration: 200 + + from: 1.0 + to: 0.0 + } + } + + header: Rectangle { + topLeftRadius: dia.radius + topRightRadius: dia.radius + + color: colorful ? Qt.alpha(Constants.accent, 0.5) : Constants.dialog + + height: 50 + + Text { + anchors.fill: parent + font.pixelSize: Math.round(25) + + text: title + color: Constants.text + + font.bold: true + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } + } + + background: Rectangle { + radius: dia.radius + + color: Constants.dialog + } + + footer: DialogButtonBox { + id: control + + background: Item {} + + delegate: Button { + id: btn + } + } + Overlay.modal: Item {} + Overlay.modeless: Item {} +} diff --git a/src/eden/qml/items/BetterMenu.qml b/src/eden/qml/items/BetterMenu.qml new file mode 100644 index 0000000000..51a38451e5 --- /dev/null +++ b/src/eden/qml/items/BetterMenu.qml @@ -0,0 +1,59 @@ +import QtQuick +import QtQuick.Controls + +import org.eden_emu.constants + +Menu { + background: Rectangle { + implicitWidth: 200 + implicitHeight: 40 + color: Constants.button + + radius: 10 + } + + function fixAmpersands(originalText) { + var regex = /&(\w)/g + return originalText.replace(regex, "$1") + } + + delegate: MenuItem { + id: control + + font.pixelSize: 14 + + background: Rectangle { + color: control.down || control.hovered + || control.highlighted ? Constants.buttonHighlighted : Constants.button + } + + contentItem: Item { + Text { + anchors { + left: parent.left + leftMargin: 5 + (control.checkable ? control.indicator.width : 0) + verticalCenter: parent.verticalCenter + } + + text: fixAmpersands(control.text) + color: Constants.text + font: control.font + } + + Text { + anchors { + right: parent.right + rightMargin: 5 + verticalCenter: parent.verticalCenter + } + + Component.onCompleted: if (control.action != null + && typeof control.action.shortcut !== 'undefined') + text = control.action.shortcut + + color: Constants.text + font: control.font + } + } + } +} diff --git a/src/eden/qml/items/BetterMenuBar.qml b/src/eden/qml/items/BetterMenuBar.qml new file mode 100644 index 0000000000..8b5fd10d9a --- /dev/null +++ b/src/eden/qml/items/BetterMenuBar.qml @@ -0,0 +1,33 @@ +import QtQuick +import QtQuick.Controls + +import org.eden_emu.constants + +MenuBar { + background: Rectangle { + implicitHeight: 30 + color: Constants.button + } + + function fixAmpersands(originalText) { + var regex = /&(\w)/g + return originalText.replace(regex, "$1") + } + + delegate: MenuBarItem { + id: control + + font.pixelSize: 16 + + background: Rectangle { + color: control.down || control.hovered + || control.highlighted ? Constants.buttonHighlighted : Constants.button + } + + contentItem: Text { + text: fixAmpersands(control.text) + color: Constants.text + font: control.font + } + } +} diff --git a/src/eden/qml/items/CMakeLists.txt b/src/eden/qml/items/CMakeLists.txt new file mode 100644 index 0000000000..57ff683ec2 --- /dev/null +++ b/src/eden/qml/items/CMakeLists.txt @@ -0,0 +1,22 @@ +set(CMAKE_AUTOMOC ON) + +qt_add_library(edenItems STATIC) + +qt_add_qml_module(edenItems + URI org.eden_emu.items + VERSION 1.0 + QML_FILES + + StatusBarButton.qml + BetterMenu.qml + AnimatedDialog.qml + BetterMenuBar.qml + + SettingsTabButton.qml + IconButton.qml + QML_FILES VerticalTabBar.qml + QML_FILES + QML_FILES fields/BetterSpinBox.qml + QML_FILES fields/BetterTextField.qml + QML_FILES fields/FieldFooter.qml +) diff --git a/src/eden/qml/items/IconButton.qml b/src/eden/qml/items/IconButton.qml new file mode 100644 index 0000000000..14f28b3fff --- /dev/null +++ b/src/eden/qml/items/IconButton.qml @@ -0,0 +1,23 @@ +import QtQuick +import QtQuick.Controls.Material + +import org.eden_emu.constants + +Button { + required property string label + + bottomInset: 0 + topInset: 0 + leftPadding: 5 * Constants.scalar + rightPadding: 5 * Constants.scalar + + width: icon.width + height: icon.height + + icon.source: "qrc:/icons/" + label.toLowerCase() + ".svg" + icon.width: 45 + icon.height: 45 + icon.color: Constants.text + + background: Item {} +} diff --git a/src/eden/qml/items/SettingsTabButton.qml b/src/eden/qml/items/SettingsTabButton.qml new file mode 100644 index 0000000000..bdc0190aaa --- /dev/null +++ b/src/eden/qml/items/SettingsTabButton.qml @@ -0,0 +1,40 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.constants + +TabButton { + required property string label + + id: button + + implicitHeight: 100 * Constants.scalar + width: 95 * Constants.scalar + + contentItem: ColumnLayout { + IconButton { + label: button.label + + Layout.maximumHeight: 60 * Constants.scalar + Layout.maximumWidth: 65 * Constants.scalar + + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + + onClicked: button.clicked() + } + + Text { + font.pixelSize: 16 * Constants.scalar + text: label + + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + + color: Constants.text + } + } + + // background: Rectangle { + // color: button.Material.backgroundColor + // } +} diff --git a/src/eden/qml/items/StatusBarButton.qml b/src/eden/qml/items/StatusBarButton.qml new file mode 100644 index 0000000000..211795e337 --- /dev/null +++ b/src/eden/qml/items/StatusBarButton.qml @@ -0,0 +1,36 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import org.eden_emu.constants + +MouseArea { + id: button + + required property string text + property color textColor: Constants.text + + implicitHeight: 20 + implicitWidth: txt.width + + hoverEnabled: true + onHoveredChanged: rect.color = containsMouse ? Constants.buttonHighlighted : "transparent" + + Rectangle { + id: rect + + anchors.fill: parent + color: "transparent" + + Text { + id: txt + + font.pixelSize: 12 * Constants.scalar + leftPadding: 4 + rightPadding: 4 + + color: button.textColor + text: button.text + } + } +} diff --git a/src/eden/qml/items/VerticalTabBar.qml b/src/eden/qml/items/VerticalTabBar.qml new file mode 100644 index 0000000000..2c4fe85886 --- /dev/null +++ b/src/eden/qml/items/VerticalTabBar.qml @@ -0,0 +1,46 @@ +import QtQuick +import QtQuick.Controls.Material + +import org.eden_emu.constants + +TabBar { + clip: true + id: control + + contentItem: ListView { + model: control.contentModel + currentIndex: control.currentIndex + + spacing: control.spacing + orientation: ListView.Vertical + boundsBehavior: Flickable.StopAtBounds + flickableDirection: Flickable.AutoFlickIfNeeded + snapMode: ListView.SnapToItem + + highlightMoveDuration: 300 + highlightRangeMode: ListView.ApplyRange + preferredHighlightBegin: 40 + preferredHighlightEnd: height - 40 + + highlight: Item { + z: 2 + Rectangle { + radius: 5 * Constants.scalar + anchors { + right: parent.right + verticalCenter: parent.verticalCenter + } + + height: parent.height / 2 + width: 5 * Constants.scalar + + color: Constants.accent + } + } + } + + background: Rectangle { + color: control.Material.backgroundColor + radius: 8 * Constants.scalar + } +} diff --git a/src/eden/qml/items/fields/BetterSpinBox.qml b/src/eden/qml/items/fields/BetterSpinBox.qml new file mode 100644 index 0000000000..709b7b75e0 --- /dev/null +++ b/src/eden/qml/items/fields/BetterSpinBox.qml @@ -0,0 +1,68 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Controls.impl + +import org.eden_emu.constants + +SpinBox { + id: control + property string label: "" + + from: -0x7FFFFFFF + to: 0x7FFFFFFF + + contentItem: BetterTextField { + text: parent.textFromValue(parent.value, parent.locale) + + placeholderText: parent.label + + width: parent.width + + font: parent.font + + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + + inputMethodHints: Qt.ImhFormattedNumbersOnly + + onEditingFinished: { + control.value = parseFloat(text.replace(/,/g, "")) + valueModified() + } + } + + up.indicator: IconLabel { + icon { + source: "qrc:/icons/forward.svg" + } + + x: control.mirrored ? 0 : control.width - width + + implicitWidth: 40 * Constants.scalar + implicitHeight: 40 * Constants.scalar + + height: parent.height + width: height / 2 + } + + down.indicator: IconLabel { + icon { + source: "qrc:/icons/back.svg" + } + + x: control.mirrored ? control.width - width : 0 + + implicitWidth: 40 * Constants.scalar + implicitHeight: 40 * Constants.scalar + + height: parent.height + width: height / 2 + } + background: Item {} + + FieldFooter { + anchors { + bottom: contentItem.bottom + } + } +} diff --git a/src/eden/qml/items/fields/BetterTextField.qml b/src/eden/qml/items/fields/BetterTextField.qml new file mode 100644 index 0000000000..5d32b4022e --- /dev/null +++ b/src/eden/qml/items/fields/BetterTextField.qml @@ -0,0 +1,38 @@ +import QtQuick +import QtQuick.Controls.Material + +import org.eden_emu.constants +import org.eden_emu.items + +TextField { + property string suffix: "" + + placeholderTextColor: enabled && activeFocus ? Constants.accent : Qt.darker( + Constants.text, 1.3) + + color: enabled ? Constants.text : Qt.darker(Constants.text, 1.5) + + background: Rectangle { + color: "transparent" + } + + FieldFooter {} + + horizontalAlignment: "AlignHCenter" + + Text { + id: txt + text: suffix + + font.pixelSize: 14 * Constants.scalar + + anchors { + verticalCenter: parent.verticalCenter + right: parent.right + + rightMargin: 5 * Constants.scalar + } + + color: "gray" + } +} diff --git a/src/eden/qml/items/fields/FieldFooter.qml b/src/eden/qml/items/fields/FieldFooter.qml new file mode 100644 index 0000000000..897785a21b --- /dev/null +++ b/src/eden/qml/items/fields/FieldFooter.qml @@ -0,0 +1,20 @@ +import QtQuick +import QtQuick.Controls.Material +import org.eden_emu.constants + +Rectangle { + height: 2 * Constants.scalar + color: enabled ? Constants.text : Qt.darker(Constants.text, 1.5) + width: parent.width + + anchors { + bottom: parent.bottom + left: parent.left + } + + Behavior on color { + ColorAnimation { + duration: 250 + } + } +} diff --git a/src/eden/qml/main/CMakeLists.txt b/src/eden/qml/main/CMakeLists.txt new file mode 100644 index 0000000000..c2a9e6eed8 --- /dev/null +++ b/src/eden/qml/main/CMakeLists.txt @@ -0,0 +1,13 @@ +set(CMAKE_AUTOMOC ON) + +qt_add_library(edenMain STATIC) +qt_add_qml_module(edenMain + URI org.eden_emu.main + VERSION 1.0 + QML_FILES + + Main.qml + StatusBar.qml + GameList.qml + GamePreview.qml +) diff --git a/src/eden/qml/main/GameList.qml b/src/eden/qml/main/GameList.qml new file mode 100644 index 0000000000..1540452774 --- /dev/null +++ b/src/eden/qml/main/GameList.qml @@ -0,0 +1,141 @@ +import QtQuick +import QtQuick.Controls +import Qt.labs.platform +import QtCore + +import org.eden_emu.constants +import org.eden_emu.interface +import org.eden_emu.gamepad + +Rectangle { + id: root + + property var setting: SettingsInterface.setting("grid_columns") + + property int gx: 0 + property int gy: 0 + + readonly property int deadzone: 8000 + readonly property int repeatTimeMs: 125 + + color: Constants.bg + + // TODO: make this optional. + // Probably just make a Gamepad frontend/backend split with a null backend + Gamepad { + id: gamepad + + onUpPressed: grid.moveCurrentIndexUp() + onDownPressed: grid.moveCurrentIndexDown() + onLeftPressed: grid.moveCurrentIndexLeft() + onRightPressed: grid.moveCurrentIndexRight() + onAPressed: console.log("A pressed") + onLeftStickMoved: (x, y) => { + gx = x + gy = y + } + } + + Timer { + interval: repeatTimeMs + running: true + repeat: true + onTriggered: { + if (gx > deadzone) { + gamepad.rightPressed() + } else if (gx < -deadzone) { + gamepad.leftPressed() + } + + if (gy > deadzone) { + gamepad.downPressed() + } else if (gy < -deadzone) { + gamepad.upPressed() + } + } + } + + Timer { + interval: 16 + running: true + repeat: true + onTriggered: gamepad.pollEvents() + } + + FolderDialog { + id: openDir + folder: StandardPaths.writableLocation(StandardPaths.HomeLocation) + onAccepted: { + button.visible = false + grid.anchors.bottom = root.bottom + EdenGameList.addDir(folder) + } + } + + GridView { + id: grid + + property int cellSize: Math.floor(width / setting.value) + + highlightFollowsCurrentItem: true + clip: true + + cellWidth: cellSize + cellHeight: cellSize + 60 * Constants.scalar + + anchors { + top: parent.top + left: parent.left + right: parent.right + + bottom: button.top + + margins: 8 + } + + model: EdenGameList + + delegate: GamePreview { + id: game + + width: grid.cellSize - 20 * Constants.scalar + height: grid.cellHeight - 20 * Constants.scalar + } + + highlight: Rectangle { + color: "transparent" + z: 5 + + radius: 16 * Constants.scalar + border { + color: Constants.text + width: 3 + } + } + + focus: true + focusPolicy: "StrongFocus" + } + + Button { + id: button + font.pixelSize: 25 + + anchors { + left: parent.left + right: parent.right + + bottom: parent.bottom + + margins: 8 + } + + text: "Add Directory" + onClicked: openDir.open() + + background: Rectangle { + color: button.pressed ? Constants.accentPressed : Constants.accent + radius: 5 + } + } +} diff --git a/src/eden/qml/main/GamePreview.qml b/src/eden/qml/main/GamePreview.qml new file mode 100644 index 0000000000..c1af89bce7 --- /dev/null +++ b/src/eden/qml/main/GamePreview.qml @@ -0,0 +1,77 @@ +import QtQuick +import QtQuick.Controls +import Qt.labs.platform +import QtCore + +import org.eden_emu.constants + +Rectangle { + id: wrapper + + color: Constants.dialog + radius: 16 * Constants.scalar + + Image { + id: image + + fillMode: Image.PreserveAspectFit + source: "file://" + model.path + + clip: true + + anchors { + top: parent.top + left: parent.left + right: parent.right + + margins: 4 * Constants.scalar + } + + height: parent.height - 40 * Constants.scalar + + MouseArea { + id: mouseArea + hoverEnabled: true + + z: 3 + + x: (parent.width - parent.paintedWidth) / 2 + y: (parent.height - parent.paintedHeight) / 2 + + width: parent.paintedWidth + height: parent.paintedHeight + + onContainsMouseChanged: { + if (containsMouse) { + wrapper.GridView.view.currentIndex = index + wrapper.GridView.view.focus = true + } + } + } + } + + Text { + id: nameText + + anchors { + bottom: parent.bottom + bottomMargin: 5 * Constants.scalar + + left: parent.left + right: parent.right + } + + style: Text.Outline + styleColor: Constants.bg + + text: model.name.replace(/-/g, " ") + wrapMode: Text.WordWrap + horizontalAlignment: Qt.AlignHCenter + + font { + pixelSize: 15 * Constants.scalar + } + + color: "white" + } +} diff --git a/src/eden/qml/main/Main.qml b/src/eden/qml/main/Main.qml new file mode 100644 index 0000000000..071b052525 --- /dev/null +++ b/src/eden/qml/main/Main.qml @@ -0,0 +1,207 @@ +import QtQuick +import QtQuick.Controls.Material + +import org.eden_emu.config +import org.eden_emu.items +import org.eden_emu.constants + +ApplicationWindow { + width: Constants.width + height: Constants.height + visible: true + title: qsTr("eden") + + Material.theme: Material.Dark + Material.accent: Material.Red + + Material.roundedScale: Material.NotRounded + + GameList { + anchors { + top: parent.top + left: parent.left + right: parent.right + bottom: status.top + } + } + + /** Dialogs */ + GlobalConfigureDialog { + id: globalConfig + } + + menuBar: BetterMenuBar { + BetterMenu { + title: qsTr("&File") + contentWidth: 225 + + Action { + text: qsTr("&Install files to NAND...") + } + MenuSeparator {} + + Action { + text: qsTr("L&oad File...") + shortcut: "Ctrl+O" + } + + Action { + text: qsTr("Load &Folder...") + } + + MenuSeparator {} + + BetterMenu { + title: "&Recent Files" + } + + MenuSeparator {} + + Action { + text: qsTr("Load/Remove &Amiibo...") + shortcut: "F2" + } + + MenuSeparator {} + + Action { + text: qsTr("Open &eden Directory") + } + + MenuSeparator {} + + Action { + text: qsTr("E&xit") + shortcut: "Ctrl+Q" + } + } + BetterMenu { + title: qsTr("&Emulation") + contentWidth: 240 + + Action { + text: qsTr("&Pause") + shortcut: "F4" + } + + Action { + text: qsTr("&Stop") + shortcut: "F5" + } + + Action { + text: qsTr("&Restart") + shortcut: "F6" + } + + MenuSeparator {} + + Action { + text: qsTr("Con&figure...") + shortcut: "Ctrl+," + onTriggered: globalConfig.open() + } + + Action { + text: qsTr("Configure &Current Game...") + shortcut: "Ctrl+." + } + } + + BetterMenu { + title: qsTr("&View") + contentWidth: 260 + + Action { + text: qsTr("F&ullscreen") + shortcut: "F11" + checkable: true + } + + Action { + text: qsTr("Single &Window Mode") + checkable: true + } + + Action { + text: qsTr("Display D&ock Widget Headers") + checkable: true + } + + Action { + text: qsTr("Show &Filter Bar") + shortcut: "Ctrl+F" + checkable: true + } + + Action { + text: qsTr("Show &Status Bar") + shortcut: "Ctrl+S" + checkable: true + } + + MenuSeparator {} + } + + BetterMenu { + title: qsTr("&Tools") + contentWidth: 225 + + Action { + text: qsTr("Install &Decryption Keys") + } + + Action { + text: qsTr("Install &Firmware") + } + + Action { + text: qsTr("&Verify Installed Contents") + } + + MenuSeparator {} + + BetterMenu { + title: qsTr("&Amiibo") + } + + Action { + text: qsTr("Open A&lbum") + } + + Action { + text: qsTr("Open &Mii Editor") + } + + Action { + text: qsTr("Open Co&ntroller Menu") + } + + Action { + text: qsTr("Open &Home Menu") + } + + MenuSeparator {} + + Action { + text: qsTr("&Capture Screenshot") + shortcut: "Ctrl+P" + } + + BetterMenu { + title: "&TAS" + } + } + } + + StatusBar { + id: status + + height: 30 + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + } +} diff --git a/src/eden/qml/main/StatusBar.qml b/src/eden/qml/main/StatusBar.qml new file mode 100644 index 0000000000..39aeaed393 --- /dev/null +++ b/src/eden/qml/main/StatusBar.qml @@ -0,0 +1,160 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +import org.eden_emu.constants +import org.eden_emu.items + +ToolBar { + id: toolbar + + property string graphicsBackend: "vulkan" + property string gpuAccuracy: "high" + property string consoleMode: "docked" + property int adapting: 5 + property int antialiasing: 0 + property int volume: 100 + + property string firmwareVersion: "16.0.3" + + implicitHeight: 30 + + background: Rectangle { + color: Constants.bg + } + + // TODO: reduce duplicate code + RowLayout { + id: row + + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + + leftMargin: 10 + } + + StatusBarButton { + property alias value: toolbar.graphicsBackend + + text: value.toUpperCase() + + textColor: value === "vulkan" ? "orange" : "lightblue" + + onClicked: { + if (value === "vulkan") { + value = "opengl" + } else { + value = "vulkan" + } + } + } + + StatusBarButton { + property alias value: toolbar.gpuAccuracy + + text: value.toUpperCase() + + textColor: value === "high" ? "orange" : "lightgreen" + + onClicked: { + if (value === "high") { + value = "normal" + } else { + value = "high" + } + } + } + + StatusBarButton { + property alias value: toolbar.consoleMode + + text: value.toUpperCase() + + textColor: Constants.text + + onClicked: { + if (value === "docked") { + value = "handheld" + } else { + value = "docked" + } + } + } + + StatusBarButton { + property list choices: ["nearest", "bilinear", "bicubic", "gaussian", "scaleforce", "fsr"] + + property alias index: toolbar.adapting + property string value: choices[index] + + text: value.toUpperCase() + + onClicked: { + let newIndex = index + 1 + if (newIndex >= choices.length) { + newIndex = 0 + } + + index = newIndex + } + } + + StatusBarButton { + property list choices: ["no aa", "fxaa", "msaa"] + + property alias index: toolbar.antialiasing + property string value: choices[index] + + text: value.toUpperCase() + + onClicked: { + let newIndex = index + 1 + if (newIndex >= choices.length) { + newIndex = 0 + } + + index = newIndex + } + } + + StatusBarButton { + id: volumeButton + + property alias value: toolbar.volume + + text: "VOLUME: " + value + "%" + + onClicked: { + volumeSlider.open() + } + + onWheel: wheel => { + value += wheel.angleDelta.y / 120 + } + } + } + + Popup { + id: volumeSlider + + width: 200 + height: 50 + + x: volumeButton.x + y: volumeButton.y - height + + focus: true + + Slider { + value: volumeButton.value + onMoved: volumeButton.value = value + + from: 0 + to: 200 + + anchors.fill: parent + } + } +} From fba3a7e6f6acae2d36e1defd4aaf1c6ea5990cf5 Mon Sep 17 00:00:00 2001 From: crueter Date: Mon, 16 Jun 2025 20:46:40 -0400 Subject: [PATCH 2/4] tmp Signed-off-by: crueter --- .gitignore | 1 + src/CMakeLists.txt | 1 - src/eden/CMakeLists.txt | 1 + src/eden/interface/CMakeLists.txt | 1 + src/eden/interface/QMLConfig.h | 26 +++++++++++++++ src/eden/interface/SettingsInterface.h | 2 +- src/eden/interface/qt_config.cpp | 3 ++ src/eden/main.cpp | 27 +++++++++------- src/eden/models/SettingsModel.cpp | 2 ++ src/eden/models/SettingsModel.h | 2 +- src/eden/qml/CMakeLists.txt | 1 + src/eden/qml/config/GlobalConfigureDialog.qml | 21 ++++++++---- src/eden/qml/config/fields/BaseField.qml | 10 +++++- src/eden/qml/config/fields/ConfigCheckbox.qml | 2 +- src/eden/qml/config/fields/ConfigHexEdit.qml | 4 +-- src/eden/qml/config/fields/ConfigIntLine.qml | 4 +-- .../qml/config/fields/ConfigIntSlider.qml | 7 ++-- src/eden/qml/config/fields/ConfigIntSpin.qml | 5 +-- .../qml/config/fields/ConfigStringEdit.qml | 4 +-- src/eden/qml/config/fields/ConfigTimeEdit.qml | 2 +- .../config/pages/global/GlobalAudioPage.qml | 2 ++ .../qml/config/pages/global/GlobalCpuPage.qml | 1 + .../config/pages/global/GlobalDebugPage.qml | 1 + .../config/pages/global/GlobalGeneralPage.qml | 1 + .../pages/global/GlobalGraphicsPage.qml | 1 + .../config/pages/global/GlobalSystemPage.qml | 1 + src/eden/qml/util/CMakeLists.txt | 23 +++++++++++++ src/eden/qml/util/Util.qml | 32 +++++++++++++++++++ 28 files changed, 152 insertions(+), 36 deletions(-) create mode 100644 src/eden/interface/QMLConfig.h create mode 100644 src/eden/qml/util/CMakeLists.txt create mode 100644 src/eden/qml/util/Util.qml diff --git a/.gitignore b/.gitignore index 1eb65efae3..77b51f937e 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ eden-windows-msvc artifacts *.AppImage* *.patch +.cache diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 08fed7669b..4687779067 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -114,7 +114,6 @@ else() -Werror=all -Werror=extra - -Werror=missing-declarations -Werror=shadow -Werror=unused diff --git a/src/eden/CMakeLists.txt b/src/eden/CMakeLists.txt index 702ccd3adf..50d9b30db7 100644 --- a/src/eden/CMakeLists.txt +++ b/src/eden/CMakeLists.txt @@ -21,6 +21,7 @@ set(PLUGINS edenConfigplugin edenInterfaceplugin edenConstantsplugin + edenUtilplugin ) if (ENABLE_SDL2) diff --git a/src/eden/interface/CMakeLists.txt b/src/eden/interface/CMakeLists.txt index 374b4ec70d..290dec22b9 100644 --- a/src/eden/interface/CMakeLists.txt +++ b/src/eden/interface/CMakeLists.txt @@ -9,5 +9,6 @@ qt_add_qml_module(edenInterface shared_translation.h shared_translation.cpp QMLSetting.h QMLSetting.cpp MetaObjectHelper.h + SOURCES QMLConfig.h ) diff --git a/src/eden/interface/QMLConfig.h b/src/eden/interface/QMLConfig.h new file mode 100644 index 0000000000..83b7f174cb --- /dev/null +++ b/src/eden/interface/QMLConfig.h @@ -0,0 +1,26 @@ +#ifndef QMLCONFIG_H +#define QMLCONFIG_H + +#include "eden/interface/qt_config.h" + +#include + +class QMLConfig : public QObject { + Q_OBJECT + + QtConfig *m_config; + +public: + QMLConfig() + : m_config{new QtConfig} + {} + + Q_INVOKABLE inline void reload() { + m_config->ReloadAllValues(); + } + Q_INVOKABLE inline void save() { + m_config->SaveAllValues(); + } +}; + +#endif // QMLCONFIG_H diff --git a/src/eden/interface/SettingsInterface.h b/src/eden/interface/SettingsInterface.h index 4c132c3ce6..c5a0617f26 100644 --- a/src/eden/interface/SettingsInterface.h +++ b/src/eden/interface/SettingsInterface.h @@ -6,7 +6,7 @@ #include "QMLSetting.h" #include "shared_translation.h" -#include "yuzu/models/SettingsModel.h" +#include "eden/models/SettingsModel.h" namespace SettingsCategories { Q_NAMESPACE diff --git a/src/eden/interface/qt_config.cpp b/src/eden/interface/qt_config.cpp index f4390065c4..9cd8f58c3d 100644 --- a/src/eden/interface/qt_config.cpp +++ b/src/eden/interface/qt_config.cpp @@ -534,6 +534,9 @@ void QtConfig::SaveMultiplayerValues() { std::vector& QtConfig::FindRelevantList(Settings::Category category) { auto& map = Settings::values.linkage.by_category; + // if (!map[category].empty()) { + // return map[category]; + // } if (map.contains(category)) { return Settings::values.linkage.by_category[category]; } diff --git a/src/eden/main.cpp b/src/eden/main.cpp index 6e4126d466..810d293c36 100644 --- a/src/eden/main.cpp +++ b/src/eden/main.cpp @@ -1,10 +1,10 @@ -#include "core/core.h" -#include "interface/SettingsInterface.h" -#include "interface/qt_config.h" -#include "models/GameListModel.h" #include #include #include +#include "core/core.h" +#include "interface/QMLConfig.h" +#include "models/GameListModel.h" +#include "interface/SettingsInterface.h" #include @@ -14,18 +14,18 @@ int main(int argc, char *argv[]) QQuickStyle::setStyle(QObject::tr("Material")); - QCoreApplication::setOrganizationName(QStringLiteral("yuzu")); + QCoreApplication::setOrganizationName(QStringLiteral("eden-emu")); QCoreApplication::setApplicationName(QStringLiteral("eden")); + QApplication::setDesktopFileName(QStringLiteral("org.eden-emu.eden")); /// Settings, etc Settings::SetConfiguringGlobal(true); - QtConfig *config = new QtConfig; - config->SaveAllValues(); + QMLConfig *config = new QMLConfig; - // TODO: Save all values on launch and per game etc - app.connect(&app, &QCoreApplication::aboutToQuit, &app, [config]() { - config->SaveAllValues(); - }); + // // TODO: Save all values on launch and per game etc + // app.connect(&app, &QCoreApplication::aboutToQuit, &app, [config]() { + // config->save(); + // }); /// Expose Enums @@ -34,13 +34,16 @@ int main(int argc, char *argv[]) /// CONTEXT QQmlApplicationEngine engine; + auto ctx = engine.rootContext(); + + ctx->setContextProperty(QStringLiteral("QtConfig"), QVariant::fromValue(config)); // Enums qmlRegisterUncreatableMetaObject(SettingsCategories::staticMetaObject, "org.eden_emu.interface", 1, 0, "SettingsCategories", QString()); // Directory List GameListModel *gameListModel = new GameListModel(&app); - engine.rootContext()->setContextProperty(QStringLiteral("EdenGameList"), gameListModel); + ctx->setContextProperty(QStringLiteral("EdenGameList"), gameListModel); /// LOAD QObject::connect( diff --git a/src/eden/models/SettingsModel.cpp b/src/eden/models/SettingsModel.cpp index a6a9d1bb62..6627ef2c72 100644 --- a/src/eden/models/SettingsModel.cpp +++ b/src/eden/models/SettingsModel.cpp @@ -47,7 +47,9 @@ bool SettingsModel::setData(const QModelIndex &index, const QVariant &value, int switch (role) { case VALUE: + qDebug() << "Before" << s->value(); s->setValue(value); + qDebug() << "After" << s->value(); break; } diff --git a/src/eden/models/SettingsModel.h b/src/eden/models/SettingsModel.h index 0edaab7982..4f7ff9860e 100644 --- a/src/eden/models/SettingsModel.h +++ b/src/eden/models/SettingsModel.h @@ -2,7 +2,7 @@ #define SETTINGSMODEL_H #include -#include "yuzu/interface/QMLSetting.h" +#include "eden/interface/QMLSetting.h" class SettingsModel : public QAbstractListModel { Q_OBJECT diff --git a/src/eden/qml/CMakeLists.txt b/src/eden/qml/CMakeLists.txt index 958f84c3dd..293061b88f 100644 --- a/src/eden/qml/CMakeLists.txt +++ b/src/eden/qml/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(config) add_subdirectory(constants) add_subdirectory(items) +add_subdirectory(util) add_subdirectory(main) diff --git a/src/eden/qml/config/GlobalConfigureDialog.qml b/src/eden/qml/config/GlobalConfigureDialog.qml index 869a682858..50d81a5469 100644 --- a/src/eden/qml/config/GlobalConfigureDialog.qml +++ b/src/eden/qml/config/GlobalConfigureDialog.qml @@ -5,21 +5,28 @@ import QtQuick.Layouts import org.eden_emu.constants import org.eden_emu.items import org.eden_emu.interface +import org.eden_emu.util + +AnimatedDialog { + property list configs -Dialog { preferredWidth: 1280 title: "Configuration" standardButtons: Dialog.Ok | Dialog.Cancel - onOpened: { - for (var tab in tabBar.children) { - console.log(tab) - } - } + Component.onCompleted: configs = Util.searchItem(swipe, "BaseField") onAccepted: { - + configs.forEach(config => { + config.apply() + // console.log(config.setting.label) + }) + QtConfig.save() + } + onRejected: { + configs.forEach(config => config.sync()) + QtConfig.reload() } VerticalTabBar { diff --git a/src/eden/qml/config/fields/BaseField.qml b/src/eden/qml/config/fields/BaseField.qml index 092f69f3eb..3d045a3b7b 100644 --- a/src/eden/qml/config/fields/BaseField.qml +++ b/src/eden/qml/config/fields/BaseField.qml @@ -14,11 +14,19 @@ Item { property alias enable: enable.checked property Item contentItem + readonly property string typeName: "BaseField" + clip: true height: content.height + (helpText.height + helpText.anchors.topMargin) + Component.onCompleted: sync() + function apply() { - setting.value = value + if (setting.value !== value) { + console.log(setting.label, "previous value:", setting.value, + "new value:", value) + setting.value = value + } } function sync() { diff --git a/src/eden/qml/config/fields/ConfigCheckbox.qml b/src/eden/qml/config/fields/ConfigCheckbox.qml index 43c84e2248..846be6147d 100644 --- a/src/eden/qml/config/fields/ConfigCheckbox.qml +++ b/src/eden/qml/config/fields/ConfigCheckbox.qml @@ -20,7 +20,7 @@ BaseField { text: setting.label checked: setting.value - onClicked: setting.value = checked + onClicked: value = checked checkable: true } diff --git a/src/eden/qml/config/fields/ConfigHexEdit.qml b/src/eden/qml/config/fields/ConfigHexEdit.qml index 983b0e94c7..0e4a3c1974 100644 --- a/src/eden/qml/config/fields/ConfigHexEdit.qml +++ b/src/eden/qml/config/fields/ConfigHexEdit.qml @@ -18,9 +18,9 @@ BaseField { font.pixelSize: 15 * Constants.scalar - text: Number(setting.value).toString(16) + text: Number(value).toString(16) suffix: setting.suffix - onTextEdited: setting.value = Number("0x" + text) + onTextEdited: value = Number("0x" + text) } } diff --git a/src/eden/qml/config/fields/ConfigIntLine.qml b/src/eden/qml/config/fields/ConfigIntLine.qml index 7f1653207c..6f48b72492 100644 --- a/src/eden/qml/config/fields/ConfigIntLine.qml +++ b/src/eden/qml/config/fields/ConfigIntLine.qml @@ -20,9 +20,9 @@ BaseField { font.pixelSize: 15 * Constants.scalar - text: setting.value + text: value suffix: setting.suffix - onTextEdited: setting.value = parseInt(text) + onTextEdited: value = parseInt(text) } } diff --git a/src/eden/qml/config/fields/ConfigIntSlider.qml b/src/eden/qml/config/fields/ConfigIntSlider.qml index eac6f66993..9428a0c791 100644 --- a/src/eden/qml/config/fields/ConfigIntSlider.qml +++ b/src/eden/qml/config/fields/ConfigIntSlider.qml @@ -8,6 +8,7 @@ import org.eden_emu.constants // Lots of cancer but idrc BaseField { + id: field contentItem: RowLayout { Layout.fillWidth: true @@ -18,9 +19,9 @@ BaseField { to: setting.max stepSize: 1 - value: setting.value + value: field.value - onMoved: setting.value = value + onMoved: field.value = value Layout.rightMargin: 10 * Constants.scalar @@ -31,7 +32,7 @@ BaseField { font.pixelSize: 14 * Constants.scalar color: Constants.text - text: setting.value + setting.suffix + text: field.value + setting.suffix Layout.rightMargin: 10 * Constants.scalar } diff --git a/src/eden/qml/config/fields/ConfigIntSpin.qml b/src/eden/qml/config/fields/ConfigIntSpin.qml index 7d65e90663..3fe0a82183 100644 --- a/src/eden/qml/config/fields/ConfigIntSpin.qml +++ b/src/eden/qml/config/fields/ConfigIntSpin.qml @@ -6,6 +6,7 @@ import org.eden_emu.config import org.eden_emu.constants BaseField { + id: field contentItem: BetterSpinBox { enabled: enable @@ -17,9 +18,9 @@ BaseField { font.pixelSize: 15 * Constants.scalar - value: setting.value + value: field.value label: setting.suffix - onValueModified: setting.value = value + onValueModified: field.value = value } } diff --git a/src/eden/qml/config/fields/ConfigStringEdit.qml b/src/eden/qml/config/fields/ConfigStringEdit.qml index 32873c96b2..11c58ac7a3 100644 --- a/src/eden/qml/config/fields/ConfigStringEdit.qml +++ b/src/eden/qml/config/fields/ConfigStringEdit.qml @@ -14,9 +14,9 @@ BaseField { font.pixelSize: 15 * Constants.scalar - text: setting.value + text: value suffix: setting.suffix - onTextEdited: setting.value = text + onTextEdited: value = text } } diff --git a/src/eden/qml/config/fields/ConfigTimeEdit.qml b/src/eden/qml/config/fields/ConfigTimeEdit.qml index e070102ca3..66c731c7ed 100644 --- a/src/eden/qml/config/fields/ConfigTimeEdit.qml +++ b/src/eden/qml/config/fields/ConfigTimeEdit.qml @@ -21,7 +21,7 @@ BaseField { font.pixelSize: 15 * Constants.scalar - text: setting.value + text: value suffix: setting.suffix } } diff --git a/src/eden/qml/config/pages/global/GlobalAudioPage.qml b/src/eden/qml/config/pages/global/GlobalAudioPage.qml index ddfdef51f8..73ed7f20fe 100644 --- a/src/eden/qml/config/pages/global/GlobalAudioPage.qml +++ b/src/eden/qml/config/pages/global/GlobalAudioPage.qml @@ -3,6 +3,8 @@ import QtQuick import org.eden_emu.config GlobalTab { + property alias swipe: swipe + tabs: ["Audio"] GlobalTabSwipeView { diff --git a/src/eden/qml/config/pages/global/GlobalCpuPage.qml b/src/eden/qml/config/pages/global/GlobalCpuPage.qml index bcca4f17ae..457565ede7 100644 --- a/src/eden/qml/config/pages/global/GlobalCpuPage.qml +++ b/src/eden/qml/config/pages/global/GlobalCpuPage.qml @@ -3,6 +3,7 @@ import QtQuick import org.eden_emu.config GlobalTab { + property alias swipe: swipe tabs: ["CPU"] GlobalTabSwipeView { diff --git a/src/eden/qml/config/pages/global/GlobalDebugPage.qml b/src/eden/qml/config/pages/global/GlobalDebugPage.qml index 85df6bd1e4..bbbbfbf29f 100644 --- a/src/eden/qml/config/pages/global/GlobalDebugPage.qml +++ b/src/eden/qml/config/pages/global/GlobalDebugPage.qml @@ -3,6 +3,7 @@ import QtQuick import org.eden_emu.config GlobalTab { + property alias swipe: swipe tabs: ["General", "Graphics", "Advanced", "CPU"] GlobalTabSwipeView { diff --git a/src/eden/qml/config/pages/global/GlobalGeneralPage.qml b/src/eden/qml/config/pages/global/GlobalGeneralPage.qml index 30a782d5a8..feae9cd482 100644 --- a/src/eden/qml/config/pages/global/GlobalGeneralPage.qml +++ b/src/eden/qml/config/pages/global/GlobalGeneralPage.qml @@ -3,6 +3,7 @@ import QtQuick import org.eden_emu.config GlobalTab { + property alias swipe: swipe tabs: ["General", "Hotkeys", "Game List"] GlobalTabSwipeView { diff --git a/src/eden/qml/config/pages/global/GlobalGraphicsPage.qml b/src/eden/qml/config/pages/global/GlobalGraphicsPage.qml index af42494591..67380be9e4 100644 --- a/src/eden/qml/config/pages/global/GlobalGraphicsPage.qml +++ b/src/eden/qml/config/pages/global/GlobalGraphicsPage.qml @@ -3,6 +3,7 @@ import QtQuick import org.eden_emu.config GlobalTab { + property alias swipe: swipe tabs: ["Graphics", "Advanced", "Extensions"] GlobalTabSwipeView { diff --git a/src/eden/qml/config/pages/global/GlobalSystemPage.qml b/src/eden/qml/config/pages/global/GlobalSystemPage.qml index 20f0780c97..1fa34e0d26 100644 --- a/src/eden/qml/config/pages/global/GlobalSystemPage.qml +++ b/src/eden/qml/config/pages/global/GlobalSystemPage.qml @@ -3,6 +3,7 @@ import QtQuick import org.eden_emu.config GlobalTab { + property alias swipe: swipe tabs: ["System", "Core", "Profiles", "Filesystem", "Applets"] GlobalTabSwipeView { diff --git a/src/eden/qml/util/CMakeLists.txt b/src/eden/qml/util/CMakeLists.txt new file mode 100644 index 0000000000..a2243aa611 --- /dev/null +++ b/src/eden/qml/util/CMakeLists.txt @@ -0,0 +1,23 @@ +set(CMAKE_AUTOMOC ON) + +find_package(Qt6 REQUIRED COMPONENTS Quick) + +set_source_files_properties(Util.qml + PROPERTIES + QT_QML_SINGLETON_TYPE true +) + +qt_add_library(edenUtil STATIC) +qt_add_qml_module(edenUtil + URI org.eden_emu.util + OUTPUT_DIRECTORY EdenUtil + VERSION 1.0 + QML_FILES + + Util.qml +) + +target_link_libraries(edenUtil + PRIVATE + Qt6::Quick +) diff --git a/src/eden/qml/util/Util.qml b/src/eden/qml/util/Util.qml new file mode 100644 index 0000000000..03d2a09beb --- /dev/null +++ b/src/eden/qml/util/Util.qml @@ -0,0 +1,32 @@ +pragma Singleton + +import QtQuick + +QtObject { + /** + * Recursively search an Item for children matching the specified type. + * @return A list of found items. + */ + function searchItem(parent, typeName) { + let results = [] + + // Search contentChildren too for views/layouts/etc + let children = parent.children + if (parent.contentChildren) { + children = parent.contentChildren + } + + for (var i = 0; i < children.length; ++i) { + let child = children[i] + + if (child.typeName === typeName) { + results.push(child) + } + + let childResults = searchItem(child, typeName) + results = results.concat(childResults) + } + + return results + } +} From a8c7b007be19550fca1a4bf18f6e9ae11cb1a9c9 Mon Sep 17 00:00:00 2001 From: crueter Date: Mon, 16 Jun 2025 21:53:19 -0400 Subject: [PATCH 3/4] Working configuration Signed-off-by: crueter --- src/eden/interface/qt_config.cpp | 7 +- src/eden/interface/shared_translation.cpp | 275 +++++++++++++----- src/eden/models/SettingsModel.cpp | 2 - src/eden/qml/config/fields/BaseField.qml | 12 +- src/eden/qml/config/fields/ConfigCheckbox.qml | 28 +- src/eden/qml/config/fields/FieldCheckbox.qml | 9 +- 6 files changed, 228 insertions(+), 105 deletions(-) diff --git a/src/eden/interface/qt_config.cpp b/src/eden/interface/qt_config.cpp index 9cd8f58c3d..5dcad01eb1 100644 --- a/src/eden/interface/qt_config.cpp +++ b/src/eden/interface/qt_config.cpp @@ -534,11 +534,8 @@ void QtConfig::SaveMultiplayerValues() { std::vector& QtConfig::FindRelevantList(Settings::Category category) { auto& map = Settings::values.linkage.by_category; - // if (!map[category].empty()) { - // return map[category]; - // } - if (map.contains(category)) { - return Settings::values.linkage.by_category[category]; + if (!map[category].empty()) { + return map[category]; } return UISettings::values.linkage.by_category[category]; } diff --git a/src/eden/interface/shared_translation.cpp b/src/eden/interface/shared_translation.cpp index c0eea3d440..59c1011465 100644 --- a/src/eden/interface/shared_translation.cpp +++ b/src/eden/interface/shared_translation.cpp @@ -1,14 +1,14 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "shared_translation.h" +#include "eden/interface/shared_translation.h" #include #include "common/settings.h" #include "common/settings_enums.h" #include "common/settings_setting.h" #include "common/time_zone.h" -#include "uisettings.h" +#include "yuzu/uisettings.h" #include #include #include @@ -21,7 +21,7 @@ std::unique_ptr InitializeTranslations(QObject* parent) std::unique_ptr translations = std::make_unique(); const auto& tr = [parent](const char* text) -> QString { return parent->tr(text); }; -#define INSERT(SETTINGS, ID, NAME, TOOLTIP) \ +#define INSERT(SETTINGS, ID, NAME, TOOLTIP) \ translations->insert(std::pair{SETTINGS::values.ID.Id(), std::pair{(NAME), (TOOLTIP)}}) // A setting can be ignored by giving it a blank name @@ -54,110 +54,166 @@ std::unique_ptr InitializeTranslations(QObject* parent) // Core INSERT( - Settings, use_multi_core, tr("Multicore CPU Emulation"), + Settings, + use_multi_core, + tr("Multicore CPU Emulation"), tr("This option increases CPU emulation thread use from 1 to the Switch’s maximum of 4.\n" "This is mainly a debug option and shouldn’t be disabled.")); INSERT( - Settings, memory_layout_mode, tr("Memory Layout"), + Settings, + memory_layout_mode, + tr("Memory Layout"), tr("Increases the amount of emulated RAM from the stock 4GB of the retail Switch to the " "developer kit's 8/6GB.\nIt’s doesn’t improve stability or performance and is intended " "to let big texture mods fit in emulated RAM.\nEnabling it will increase memory " "use. It is not recommended to enable unless a specific game with a texture mod needs " "it.")); INSERT(Settings, use_speed_limit, QString(), QString()); - INSERT(Settings, speed_limit, tr("Limit Speed Percent"), + INSERT(Settings, + speed_limit, + tr("Limit Speed Percent"), tr("Controls the game's maximum rendering speed, but it’s up to each game if it runs " "faster or not.\n200% for a 30 FPS game is 60 FPS, and for a " "60 FPS game it will be 120 FPS.\nDisabling it means unlocking the framerate to the " "maximum your PC can reach.")); - INSERT(Settings, sync_core_speed, tr("Synchronize Core Speed"), - tr("Synchronizes CPU core speed with the game's maximum rendering speed to boost FPS without affecting game speed (animations, physics, etc.).\n" - "Compatibility varies by game; many (especially older ones) may not respond well.\n" - "Can help reduce stuttering at lower framerates.")); + INSERT(Settings, + sync_core_speed, + tr("Synchronize Core Speed"), + tr("Synchronizes CPU core speed with the game's maximum rendering speed to boost FPS " + "without affecting game speed (animations, physics, etc.).\n" + "Compatibility varies by game; many (especially older ones) may not respond well.\n" + "Can help reduce stuttering at lower framerates.")); // Cpu - INSERT(Settings, cpu_accuracy, tr("Accuracy:"), + INSERT(Settings, + cpu_accuracy, + tr("Accuracy:"), tr("This setting controls the accuracy of the emulated CPU.\nDon't change this unless " "you know what you are doing.")); INSERT(Settings, cpu_backend, tr("Backend:"), QString()); + INSERT(Settings, use_fast_cpu_time, QString(), QString()); + INSERT(Settings, + fast_cpu_time, + tr("Fast CPU Time"), + tr("Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, " + "and certain games may behave improperly.\nUse Boost (1700MHz) to run at the Switch's highest native " + "clock, or Fast (2000MHz) to run at 2x clock.")); + INSERT(Settings, cpu_backend, tr("Backend:"), QString()); + // Cpu Debug // Cpu Unsafe INSERT( - Settings, cpuopt_unsafe_unfuse_fma, + Settings, + cpuopt_unsafe_unfuse_fma, tr("Unfuse FMA (improve performance on CPUs without FMA)"), tr("This option improves speed by reducing accuracy of fused-multiply-add instructions on " "CPUs without native FMA support.")); INSERT( - Settings, cpuopt_unsafe_reduce_fp_error, tr("Faster FRSQRTE and FRECPE"), + Settings, + cpuopt_unsafe_reduce_fp_error, + tr("Faster FRSQRTE and FRECPE"), tr("This option improves the speed of some approximate floating-point functions by using " "less accurate native approximations.")); - INSERT(Settings, cpuopt_unsafe_ignore_standard_fpcr, + INSERT(Settings, + cpuopt_unsafe_ignore_standard_fpcr, tr("Faster ASIMD instructions (32 bits only)"), tr("This option improves the speed of 32 bits ASIMD floating-point functions by running " "with incorrect rounding modes.")); - INSERT(Settings, cpuopt_unsafe_inaccurate_nan, tr("Inaccurate NaN handling"), + INSERT(Settings, + cpuopt_unsafe_inaccurate_nan, + tr("Inaccurate NaN handling"), tr("This option improves speed by removing NaN checking.\nPlease note this also reduces " "accuracy of certain floating-point instructions.")); - INSERT(Settings, cpuopt_unsafe_fastmem_check, tr("Disable address space checks"), + INSERT(Settings, + cpuopt_unsafe_fastmem_check, + tr("Disable address space checks"), tr("This option improves speed by eliminating a safety check before every memory " "read/write in guest.\nDisabling it may allow a game to read/write the emulator's " "memory.")); INSERT( - Settings, cpuopt_unsafe_ignore_global_monitor, tr("Ignore global monitor"), + Settings, + cpuopt_unsafe_ignore_global_monitor, + tr("Ignore global monitor"), tr("This option improves speed by relying only on the semantics of cmpxchg to ensure " "safety of exclusive access instructions.\nPlease note this may result in deadlocks and " "other race conditions.")); // Renderer INSERT( - Settings, renderer_backend, tr("API:"), + Settings, + renderer_backend, + tr("API:"), tr("Switches between the available graphics APIs.\nVulkan is recommended in most cases.")); - INSERT(Settings, vulkan_device, tr("Device:"), + INSERT(Settings, + vulkan_device, + tr("Device:"), tr("This setting selects the GPU to use with the Vulkan backend.")); - INSERT(Settings, shader_backend, tr("Shader Backend:"), + INSERT(Settings, + shader_backend, + tr("Shader Backend:"), tr("The shader backend to use for the OpenGL renderer.\nGLSL is the fastest in " "performance and the best in rendering accuracy.\n" "GLASM is a deprecated NVIDIA-only backend that offers much better shader building " "performance at the cost of FPS and rendering accuracy.\n" "SPIR-V compiles the fastest, but yields poor results on most GPU drivers.")); - INSERT(Settings, resolution_setup, tr("Resolution:"), + INSERT(Settings, + resolution_setup, + tr("Resolution:"), tr("Forces the game to render at a different resolution.\nHigher resolutions require " "much more VRAM and bandwidth.\n" "Options lower than 1X can cause rendering issues.")); INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QString()); - INSERT(Settings, fsr_sharpening_slider, tr("FSR Sharpness:"), + INSERT(Settings, + fsr_sharpening_slider, + tr("FSR Sharpness:"), tr("Determines how sharpened the image will look while using FSR’s dynamic contrast.")); - INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"), + INSERT(Settings, + anti_aliasing, + tr("Anti-Aliasing Method:"), tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA has a " "lower performance impact and can produce a better and more stable picture under " "very low resolutions.")); - INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"), + INSERT(Settings, + fullscreen_mode, + tr("Fullscreen Mode:"), tr("The method used to render the window in fullscreen.\nBorderless offers the best " "compatibility with the on-screen keyboard that some games request for " "input.\nExclusive " "fullscreen may offer better performance and better Freesync/Gsync support.")); - INSERT(Settings, aspect_ratio, tr("Aspect Ratio:"), + INSERT(Settings, + aspect_ratio, + tr("Aspect Ratio:"), tr("Stretches the game to fit the specified aspect ratio.\nSwitch games only support " "16:9, so custom game mods are required to get other ratios.\nAlso controls the " "aspect ratio of captured screenshots.")); - INSERT(Settings, use_disk_shader_cache, tr("Use disk pipeline cache"), + INSERT(Settings, + use_disk_shader_cache, + tr("Use disk pipeline cache"), tr("Allows saving shaders to storage for faster loading on following game " "boots.\nDisabling " "it is only intended for debugging.")); - INSERT(Settings, optimize_spirv_output, tr("Optimize SPIRV output shader"), - tr("Runs an additional optimization pass over generated SPIRV shaders.\n" - "Will increase time required for shader compilation.\nMay slightly improve " - "performance.\nThis feature is experimental.")); + INSERT(Settings, + optimize_spirv_output, + tr("Optimize SPIRV output shader"), + tr("Runs an additional optimization pass over generated SPIRV shaders.\n" + "Will increase time required for shader compilation.\nMay slightly improve " + "performance.\nThis feature is experimental.")); INSERT( - Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"), + Settings, + use_asynchronous_gpu_emulation, + tr("Use asynchronous GPU emulation"), tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled.")); - INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"), + INSERT(Settings, + nvdec_emulation, + tr("NVDEC emulation:"), tr("Specifies how videos should be decoded.\nIt can either use the CPU or the GPU for " "decoding, or perform no decoding at all (black screen on videos).\n" "In most cases, GPU decoding provides the best performance.")); - INSERT(Settings, accelerate_astc, tr("ASTC Decoding Method:"), + INSERT(Settings, + accelerate_astc, + tr("ASTC Decoding Method:"), tr("This option controls how ASTC textures should be decoded.\n" "CPU: Use the CPU for decoding, slowest but safest method.\n" "GPU: Use the GPU's compute shaders to decode ASTC textures, recommended for most " @@ -166,18 +222,24 @@ std::unique_ptr InitializeTranslations(QObject* parent) "eliminates ASTC decoding\nstuttering at the cost of rendering issues while the " "texture is being decoded.")); INSERT( - Settings, astc_recompression, tr("ASTC Recompression Method:"), + Settings, + astc_recompression, + tr("ASTC Recompression Method:"), tr("Almost all desktop and laptop dedicated GPUs lack support for ASTC textures, forcing " "the emulator to decompress to an intermediate format any card supports, RGBA8.\n" "This option recompresses RGBA8 to either the BC1 or BC3 format, saving VRAM but " "negatively affecting image quality.")); - INSERT(Settings, vram_usage_mode, tr("VRAM Usage Mode:"), + INSERT(Settings, + vram_usage_mode, + tr("VRAM Usage Mode:"), tr("Selects whether the emulator should prefer to conserve memory or make maximum usage " "of available video memory for performance. Has no effect on integrated graphics. " "Aggressive mode may severely impact the performance of other applications such as " "recording software.")); INSERT( - Settings, vsync_mode, tr("VSync Mode:"), + Settings, + vsync_mode, + tr("VSync Mode:"), tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen " "refresh rate.\nFIFO Relaxed is similar to FIFO but allows tearing as it recovers from " "a slow down.\nMailbox can have lower latency than FIFO and does not tear but may drop " @@ -188,81 +250,121 @@ std::unique_ptr InitializeTranslations(QObject* parent) INSERT(Settings, bg_blue, QString(), QString()); // Renderer (Advanced Graphics) - INSERT(Settings, async_presentation, tr("Enable asynchronous presentation (Vulkan only)"), + INSERT(Settings, + async_presentation, + tr("Enable asynchronous presentation (Vulkan only)"), tr("Slightly improves performance by moving presentation to a separate CPU thread.")); INSERT( - Settings, renderer_force_max_clock, tr("Force maximum clocks (Vulkan only)"), + Settings, + renderer_force_max_clock, + tr("Force maximum clocks (Vulkan only)"), tr("Runs work in the background while waiting for graphics commands to keep the GPU from " "lowering its clock speed.")); - INSERT(Settings, max_anisotropy, tr("Anisotropic Filtering:"), + INSERT(Settings, + max_anisotropy, + tr("Anisotropic Filtering:"), tr("Controls the quality of texture rendering at oblique angles.\nIt’s a light setting " "and safe to set at 16x on most GPUs.")); - INSERT(Settings, gpu_accuracy, tr("Accuracy Level:"), + INSERT(Settings, + gpu_accuracy, + tr("Accuracy Level:"), tr("GPU emulation accuracy.\nMost games render fine with Normal, but High is still " "required for some.\nParticles tend to only render correctly with High " "accuracy.\nExtreme should only be used for debugging.\nThis option can " "be changed while playing.\nSome games may require booting on high to render " "properly.")); - INSERT(Settings, use_asynchronous_shaders, tr("Use asynchronous shader building (Hack)"), + INSERT(Settings, + use_asynchronous_shaders, + tr("Use asynchronous shader building (Hack)"), tr("Enables asynchronous shader compilation, which may reduce shader stutter.\nThis " "feature " "is experimental.")); - INSERT(Settings, use_fast_gpu_time, tr("Use Fast GPU Time (Hack)"), - tr("Enables Fast GPU Time. This option will force most games to run at their highest " - "native resolution.")); - INSERT(Settings, use_vulkan_driver_pipeline_cache, tr("Use Vulkan pipeline cache"), + INSERT(Settings, use_fast_gpu_time, QString(), QString()); + INSERT(Settings, + fast_gpu_time, + tr("Fast GPU Time (Hack)"), + tr("Overclocks the emulated GPU to increase dynamic resolution and render " + "distance.\nUse 128 for maximal performance and 512 for maximal graphics fidelity.")); + + INSERT(Settings, + use_vulkan_driver_pipeline_cache, + tr("Use Vulkan pipeline cache"), tr("Enables GPU vendor-specific pipeline cache.\nThis option can improve shader loading " "time significantly in cases where the Vulkan driver does not store pipeline cache " "files internally.")); INSERT( - Settings, enable_compute_pipelines, tr("Enable Compute Pipelines (Intel Vulkan Only)"), + Settings, + enable_compute_pipelines, + tr("Enable Compute Pipelines (Intel Vulkan Only)"), tr("Enable compute pipelines, required by some games.\nThis setting only exists for Intel " "proprietary drivers, and may crash if enabled.\nCompute pipelines are always enabled " "on all other drivers.")); INSERT( - Settings, use_reactive_flushing, tr("Enable Reactive Flushing"), + Settings, + use_reactive_flushing, + tr("Enable Reactive Flushing"), tr("Uses reactive flushing instead of predictive flushing, allowing more accurate memory " "syncing.")); - INSERT(Settings, use_video_framerate, tr("Sync to framerate of video playback"), + INSERT(Settings, + use_video_framerate, + tr("Sync to framerate of video playback"), tr("Run the game at normal speed during video playback, even when the framerate is " "unlocked.")); - INSERT(Settings, barrier_feedback_loops, tr("Barrier feedback loops"), + INSERT(Settings, + barrier_feedback_loops, + tr("Barrier feedback loops"), tr("Improves rendering of transparency effects in specific games.")); // Renderer (Extensions) - INSERT(Settings, dyna_state, tr("Extended Dynamic State"), - tr("Enables the VkExtendedDynamicState* extensions.\nHigher dynamic states will generally improve " + INSERT(Settings, + dyna_state, + tr("Extended Dynamic State"), + tr("Enables the VkExtendedDynamicState* extensions.\nHigher dynamic states will " + "generally improve " "performance, but may cause issues on certain games or devices.")); - INSERT(Settings, provoking_vertex, tr("Provoking Vertex"), + INSERT(Settings, + provoking_vertex, + tr("Provoking Vertex"), tr("Improves lighting and vertex handling in certain games.\n" "Only Vulkan 1.0+ devices support this extension.")); - INSERT(Settings, descriptor_indexing, tr("Descriptor Indexing"), + INSERT(Settings, + descriptor_indexing, + tr("Descriptor Indexing"), tr("Improves texture & buffer handling and the Maxwell translation layer.\n" "Some Vulkan 1.1+ and all 1.2+ devices support this extension.")); // Renderer (Debug) // System - INSERT(Settings, rng_seed, tr("RNG Seed"), + INSERT(Settings, + rng_seed, + tr("RNG Seed"), tr("Controls the seed of the random number generator.\nMainly used for speedrunning " "purposes.")); INSERT(Settings, rng_seed_enabled, QString(), QString()); INSERT(Settings, device_name, tr("Device Name"), tr("The name of the emulated Switch.")); - INSERT(Settings, custom_rtc, tr("Custom RTC Date:"), + INSERT(Settings, + custom_rtc, + tr("Custom RTC Date:"), tr("This option allows to change the emulated clock of the Switch.\n" "Can be used to manipulate time in games.")); INSERT(Settings, custom_rtc_enabled, QString(), QString()); - INSERT(Settings, custom_rtc_offset, QStringLiteral(" "), + INSERT(Settings, + custom_rtc_offset, + QStringLiteral(" "), QStringLiteral("The number of seconds from the current unix time")); - INSERT(Settings, language_index, tr("Language:"), + INSERT(Settings, + language_index, + tr("Language:"), tr("Note: this can be overridden when region setting is auto-select")); INSERT(Settings, region_index, tr("Region:"), tr("The region of the emulated Switch.")); - INSERT(Settings, time_zone_index, tr("Time Zone:"), - tr("The time zone of the emulated Switch.")); + INSERT(Settings, time_zone_index, tr("Time Zone:"), tr("The time zone of the emulated Switch.")); INSERT(Settings, sound_index, tr("Sound Output Mode:"), QString()); - INSERT(Settings, use_docked_mode, tr("Console Mode:"), + INSERT(Settings, + use_docked_mode, + tr("Console Mode:"), tr("Selects if the console is emulated in Docked or Handheld mode.\nGames will change " "their resolution, details and supported controllers and depending on this setting.\n" "Setting to Handheld can help improve performance for low end systems.")); @@ -283,19 +385,33 @@ std::unique_ptr InitializeTranslations(QObject* parent) // Ui // Ui General - INSERT(UISettings, select_user_on_boot, tr("Prompt for user on game boot"), + INSERT(UISettings, + select_user_on_boot, + tr("Prompt for user on game boot"), tr("Ask to select a user profile on each boot, useful if multiple people use eden on " "the same PC.")); - INSERT(UISettings, pause_when_in_background, tr("Pause emulation when in background"), + INSERT(UISettings, + pause_when_in_background, + tr("Pause emulation when in background"), tr("This setting pauses eden when focusing other windows.")); - INSERT(UISettings, confirm_before_stopping, tr("Confirm before stopping emulation"), + INSERT(UISettings, + confirm_before_stopping, + tr("Confirm before stopping emulation"), tr("This setting overrides game prompts asking to confirm stopping the game.\nEnabling " "it bypasses such prompts and directly exits the emulation.")); - INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"), + INSERT(UISettings, + hide_mouse, + tr("Hide mouse on inactivity"), tr("This setting hides the mouse after 2.5s of inactivity.")); - INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"), + INSERT(UISettings, + controller_applet_disabled, + tr("Disable controller applet"), tr("Forcibly disables the use of the controller applet by guests.\nWhen a guest " "attempts to open the controller applet, it is immediately closed.")); + INSERT(UISettings, + check_for_updates, + tr("Check for updates"), + tr("Whether or not to check for updates upon startup.")); // Linux INSERT(Settings, enable_gamemode, tr("Enable Gamemode"), QString()); @@ -305,10 +421,6 @@ std::unique_ptr InitializeTranslations(QObject* parent) // Ui Multiplayer // Ui Games list - INSERT(UISettings, - grid_columns, - tr("Grid View Columns"), - tr("Number of games to show per row in the grid view.")); #undef INSERT @@ -317,8 +429,7 @@ std::unique_ptr InitializeTranslations(QObject* parent) std::unique_ptr ComboboxEnumeration(QObject* parent) { - std::unique_ptr translations = - std::make_unique(); + std::unique_ptr translations = std::make_unique(); const auto& tr = [&](const char* text, const char* context = "") { return parent->tr(text, context); }; @@ -332,11 +443,11 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) PAIR(AppletMode, LLE, tr("Real applet")), }}); translations->insert({Settings::EnumMetadata::Index(), - { - PAIR(SpirvOptimizeMode, Never, tr("Never")), - PAIR(SpirvOptimizeMode, OnLoad, tr("On Load")), - PAIR(SpirvOptimizeMode, Always, tr("Always")), - }}); + { + PAIR(SpirvOptimizeMode, Never, tr("Never")), + PAIR(SpirvOptimizeMode, OnLoad, tr("On Load")), + PAIR(SpirvOptimizeMode, Always, tr("Always")), + }}); translations->insert({Settings::EnumMetadata::Index(), { PAIR(AstcDecodeMode, Cpu, tr("CPU")), @@ -403,6 +514,7 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) translations->insert( {Settings::EnumMetadata::Index(), { + PAIR(ResolutionSetup, Res1_4X, tr("0.25X (180p/270p) [EXPERIMENTAL]")), PAIR(ResolutionSetup, Res1_2X, tr("0.5X (360p/540p) [EXPERIMENTAL]")), PAIR(ResolutionSetup, Res3_4X, tr("0.75X (540p/810p) [EXPERIMENTAL]")), PAIR(ResolutionSetup, Res1X, tr("1X (720p/1080p)")), @@ -553,6 +665,11 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) PAIR(ConsoleMode, Docked, tr("Docked")), PAIR(ConsoleMode, Handheld, tr("Handheld")), }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(CpuClock, Boost, tr("Boost (1700MHz)")), + PAIR(CpuClock, Fast, tr("Fast (2000MHz)")), + }}); translations->insert( {Settings::EnumMetadata::Index(), { @@ -560,6 +677,12 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) PAIR(ConfirmStop, Ask_Based_On_Game, tr("Only if game specifies not to stop")), PAIR(ConfirmStop, Ask_Never, tr("Never ask")), }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(GpuOverclock, Low, tr("Low (128)")), + PAIR(GpuOverclock, Medium, tr("Medium (256)")), + PAIR(GpuOverclock, High, tr("High (512)")), + }}); #undef PAIR #undef CTX_PAIR diff --git a/src/eden/models/SettingsModel.cpp b/src/eden/models/SettingsModel.cpp index 6627ef2c72..a6a9d1bb62 100644 --- a/src/eden/models/SettingsModel.cpp +++ b/src/eden/models/SettingsModel.cpp @@ -47,9 +47,7 @@ bool SettingsModel::setData(const QModelIndex &index, const QVariant &value, int switch (role) { case VALUE: - qDebug() << "Before" << s->value(); s->setValue(value); - qDebug() << "After" << s->value(); break; } diff --git a/src/eden/qml/config/fields/BaseField.qml b/src/eden/qml/config/fields/BaseField.qml index 3d045a3b7b..dc9f22d92b 100644 --- a/src/eden/qml/config/fields/BaseField.qml +++ b/src/eden/qml/config/fields/BaseField.qml @@ -10,6 +10,7 @@ Item { property var setting property var value property bool showLabel: true + property bool forceCheckbox: false property alias enable: enable.checked property Item contentItem @@ -23,19 +24,19 @@ Item { function apply() { if (setting.value !== value) { - console.log(setting.label, "previous value:", setting.value, - "new value:", value) setting.value = value } } function sync() { - value = setting.value + if (value !== setting.value) { + value = setting.value + } } RowLayout { id: content - height: 45 * Constants.scalar + height: 50 * Constants.scalar spacing: 0 @@ -52,7 +53,7 @@ Item { icon.height: 20 onClicked: helpText.toggle() - visible: setting.tooltip !== "" + icon.color: setting.tooltip !== "" ? Constants.text : Constants.dialog z: 2 } @@ -60,6 +61,7 @@ Item { id: enable setting: field.setting z: 2 + force: field.forceCheckbox } RowLayout { diff --git a/src/eden/qml/config/fields/ConfigCheckbox.qml b/src/eden/qml/config/fields/ConfigCheckbox.qml index 846be6147d..e40d28833b 100644 --- a/src/eden/qml/config/fields/ConfigCheckbox.qml +++ b/src/eden/qml/config/fields/ConfigCheckbox.qml @@ -5,23 +5,23 @@ import QtQuick.Layouts import org.eden_emu.constants BaseField { - showLabel: false - // TODO: global/custom - contentItem: CheckBox { - id: control + forceCheckbox: true + // // TODO: global/custom + // contentItem: CheckBox { + // id: control - Layout.rightMargin: 10 * Constants.scalar - Layout.fillWidth: true + // Layout.rightMargin: 10 * Constants.scalar + // Layout.fillWidth: true - font.pixelSize: 15 * Constants.scalar - indicator.implicitHeight: 25 * Constants.scalar - indicator.implicitWidth: 25 * Constants.scalar + // font.pixelSize: 15 * Constants.scalar + // indicator.implicitHeight: 25 * Constants.scalar + // indicator.implicitWidth: 25 * Constants.scalar - text: setting.label - checked: setting.value + // text: setting.label + // checked: setting.value - onClicked: value = checked + // onClicked: value = checked - checkable: true - } + // checkable: true + // } } diff --git a/src/eden/qml/config/fields/FieldCheckbox.qml b/src/eden/qml/config/fields/FieldCheckbox.qml index f1bc4039e6..0e4e00d6a8 100644 --- a/src/eden/qml/config/fields/FieldCheckbox.qml +++ b/src/eden/qml/config/fields/FieldCheckbox.qml @@ -5,13 +5,16 @@ import QtQuick.Layouts import org.eden_emu.constants CheckBox { + property bool force: false property var setting + property var other: setting.other === null ? setting : setting.other indicator.implicitHeight: 25 * Constants.scalar indicator.implicitWidth: 25 * Constants.scalar - checked: setting.other !== null ? setting.other.value : true - onClicked: setting.other.value = checked + checked: visible ? other.value : true + onClicked: if (visible) + other.value = checked - visible: setting.other !== null + visible: setting.other !== null || force } From bf4dbef16c0e1b31f4dfc1660cdbed5c053513f2 Mon Sep 17 00:00:00 2001 From: crueter Date: Tue, 17 Jun 2025 00:29:05 -0400 Subject: [PATCH 4/4] carousel/list view Signed-off-by: crueter --- src/eden/interface/qt_config.cpp | 26 ++--- src/eden/interface/uisettings.h | 24 +++-- src/eden/models/GameListModel.cpp | 3 - src/eden/qml/main/CMakeLists.txt | 7 +- src/eden/qml/main/GameCarousel.qml | 94 +++++++++++++++++++ src/eden/qml/main/GameGrid.qml | 44 +++++++++ .../{GamePreview.qml => GameGridCard.qml} | 0 src/eden/qml/main/GameList.qml | 68 ++++++-------- src/eden/qml/main/MarqueeText.qml | 43 +++++++++ 9 files changed, 244 insertions(+), 65 deletions(-) create mode 100644 src/eden/qml/main/GameCarousel.qml create mode 100644 src/eden/qml/main/GameGrid.qml rename src/eden/qml/main/{GamePreview.qml => GameGridCard.qml} (100%) create mode 100644 src/eden/qml/main/MarqueeText.qml diff --git a/src/eden/interface/qt_config.cpp b/src/eden/interface/qt_config.cpp index 5dcad01eb1..463bbc72a8 100644 --- a/src/eden/interface/qt_config.cpp +++ b/src/eden/interface/qt_config.cpp @@ -282,13 +282,13 @@ void QtConfig::ReadUIGamelistValues() { ReadCategory(Settings::Category::UiGameList); - const int favorites_size = BeginArray("favorites"); - for (int i = 0; i < favorites_size; i++) { - SetArrayIndex(i); - UISettings::values.favorited_ids.append( - ReadUnsignedIntegerSetting(std::string("program_id"))); - } - EndArray(); + // const int favorites_size = BeginArray("favorites"); + // for (int i = 0; i < favorites_size; i++) { + // SetArrayIndex(i); + // UISettings::values.favorited_ids.append( + // ReadUnsignedIntegerSetting(std::string("program_id"))); + // } + // EndArray(); EndGroup(); } @@ -490,12 +490,12 @@ void QtConfig::SaveUIGamelistValues() WriteCategory(Settings::Category::UiGameList); - BeginArray(std::string("favorites")); - for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) { - SetArrayIndex(i); - WriteIntegerSetting(std::string("program_id"), UISettings::values.favorited_ids[i]); - } - EndArray(); // favorites + // BeginArray(std::string("favorites")); + // for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) { + // SetArrayIndex(i); + // WriteIntegerSetting(std::string("program_id"), UISettings::values.favorited_ids[i]); + // } + // EndArray(); // favorites EndGroup(); } diff --git a/src/eden/interface/uisettings.h b/src/eden/interface/uisettings.h index b8ac6a1e11..aaedb99e8b 100644 --- a/src/eden/interface/uisettings.h +++ b/src/eden/interface/uisettings.h @@ -59,6 +59,12 @@ enum class Theme { MidnightBlueColorful, }; +enum class GameView { + Grid, + List, + Carousel, +}; + static constexpr Theme default_theme{ #ifdef _WIN32 Theme::DarkColorful @@ -192,15 +198,15 @@ struct Values { std::pair, std::vector> multiplayer_ban_list; // Game List - Setting show_add_ons{linkage, true, "show_add_ons", Category::UiGameList}; - Setting game_icon_size{linkage, 64, "game_icon_size", Category::UiGameList}; - Setting folder_icon_size{linkage, 48, "folder_icon_size", Category::UiGameList}; - Setting row_1_text_id{linkage, 3, "row_1_text_id", Category::UiGameList}; - Setting row_2_text_id{linkage, 2, "row_2_text_id", Category::UiGameList}; - std::atomic_bool is_game_list_reload_pending{false}; - Setting cache_game_list{linkage, true, "cache_game_list", Category::UiGameList}; - Setting favorites_expanded{linkage, true, "favorites_expanded", Category::UiGameList}; - QVector favorited_ids; + // Setting show_add_ons{linkage, true, "show_add_ons", Category::UiGameList}; + // Setting game_icon_size{linkage, 64, "game_icon_size", Category::UiGameList}; + // Setting folder_icon_size{linkage, 48, "folder_icon_size", Category::UiGameList}; + // Setting row_1_text_id{linkage, 3, "row_1_text_id", Category::UiGameList}; + // Setting row_2_text_id{linkage, 2, "row_2_text_id", Category::UiGameList}; + // std::atomic_bool is_game_list_reload_pending{false}; + // Setting cache_game_list{linkage, true, "cache_game_list", Category::UiGameList}; + // Setting favorites_expanded{linkage, true, "favorites_expanded", Category::UiGameList}; + // QVector favorited_ids; Setting grid_columns{linkage, 4, 1, 8, "grid_columns", Category::UiGameList}; diff --git a/src/eden/models/GameListModel.cpp b/src/eden/models/GameListModel.cpp index e20717970b..61c51ac08f 100644 --- a/src/eden/models/GameListModel.cpp +++ b/src/eden/models/GameListModel.cpp @@ -56,9 +56,6 @@ void GameListModel::reload() qreal size = entry.size(); QString sizeString = QLocale::system().formattedDataSize(size); - qDebug() << path << name << size; - // m_data << Game{path, name, size}; - QStandardItem *game = new QStandardItem(name); game->setData(path, GLMRoleTypes::PATH); game->setData(sizeString, GLMRoleTypes::FILESIZE); diff --git a/src/eden/qml/main/CMakeLists.txt b/src/eden/qml/main/CMakeLists.txt index c2a9e6eed8..c0441d539d 100644 --- a/src/eden/qml/main/CMakeLists.txt +++ b/src/eden/qml/main/CMakeLists.txt @@ -9,5 +9,10 @@ qt_add_qml_module(edenMain Main.qml StatusBar.qml GameList.qml - GamePreview.qml + GameGridCard.qml + MarqueeText.qml + GameGrid.qml + + GameCarouselCard.qml + GameCarousel.qml ) diff --git a/src/eden/qml/main/GameCarousel.qml b/src/eden/qml/main/GameCarousel.qml new file mode 100644 index 0000000000..7989d46d71 --- /dev/null +++ b/src/eden/qml/main/GameCarousel.qml @@ -0,0 +1,94 @@ +import QtQuick +import QtQuick.Controls +import Qt.labs.platform +import QtCore + +import org.eden_emu.constants +import org.eden_emu.interface + +ListView { + id: carousel + + focus: true + focusPolicy: Qt.StrongFocus + + model: EdenGameList + orientation: ListView.Horizontal + clip: false + flickDeceleration: 1000 + snapMode: ListView.SnapToItem + + onHeightChanged: console.log(width, height) + + spacing: 20 + + Keys.enabled: true + Keys.onRightPressed: incrementCurrentIndex() + Keys.onLeftPressed: decrementCurrentIndex() + + onCurrentIndexChanged: scrollToCenter() + + highlight: Rectangle { + id: hg + clip: false + z: 3 + + color: "transparent" + border { + color: "deepskyblue" + width: 4 * Constants.scalar + } + + radius: 8 * Constants.scalar + + // TODO: marquee + Text { + function toTitleCase(str) { + return str.replace(/\w\S*/g, text => text.charAt(0).toUpperCase( + ) + text.substring(1).toLowerCase()) + } + + property var item: carousel.currentItem + + text: toTitleCase(item.title) + font.pixelSize: 22 * Constants.scalar + color: "lightblue" + + anchors { + bottom: hg.top + + bottomMargin: 10 * Constants.scalar + left: hg.left + right: hg.right + } + + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + } + } + + highlightFollowsCurrentItem: true + highlightMoveDuration: 300 + highlightMoveVelocity: -1 + + delegate: GameCarouselCard { + id: game + width: 300 + height: 300 + } + + function scrollToCenter() { + let targetX = currentIndex * 320 - (width - 320) / 2 + let min = 0 + let max = contentWidth + + contentX = Math.max(min, Math.min(max, targetX)) + } + + Behavior on contentX { + NumberAnimation { + duration: 300 + easing.type: Easing.OutQuad + } + } +} diff --git a/src/eden/qml/main/GameGrid.qml b/src/eden/qml/main/GameGrid.qml new file mode 100644 index 0000000000..8094ae4cdd --- /dev/null +++ b/src/eden/qml/main/GameGrid.qml @@ -0,0 +1,44 @@ +import QtQuick +import QtQuick.Controls +import Qt.labs.platform +import QtCore + +import org.eden_emu.constants +import org.eden_emu.interface +import org.eden_emu.gamepad + +GridView { + property var setting + id: grid + + property int cellSize: Math.floor(width / setting.value) + + highlightFollowsCurrentItem: true + clip: true + + cellWidth: cellSize + cellHeight: cellSize + 60 * Constants.scalar + + model: EdenGameList + + delegate: GameGridCard { + id: game + + width: grid.cellSize - 20 * Constants.scalar + height: grid.cellHeight - 20 * Constants.scalar + } + + highlight: Rectangle { + color: "transparent" + z: 5 + + radius: 16 * Constants.scalar + border { + color: Constants.text + width: 3 + } + } + + focus: true + focusPolicy: "StrongFocus" +} diff --git a/src/eden/qml/main/GamePreview.qml b/src/eden/qml/main/GameGridCard.qml similarity index 100% rename from src/eden/qml/main/GamePreview.qml rename to src/eden/qml/main/GameGridCard.qml diff --git a/src/eden/qml/main/GameList.qml b/src/eden/qml/main/GameList.qml index 1540452774..81937d1f87 100644 --- a/src/eden/qml/main/GameList.qml +++ b/src/eden/qml/main/GameList.qml @@ -20,15 +20,16 @@ Rectangle { color: Constants.bg - // TODO: make this optional. - // Probably just make a Gamepad frontend/backend split with a null backend + // TODO: use the original yuzu backend for dis Gamepad { id: gamepad - onUpPressed: grid.moveCurrentIndexUp() - onDownPressed: grid.moveCurrentIndexDown() - onLeftPressed: grid.moveCurrentIndexLeft() - onRightPressed: grid.moveCurrentIndexRight() + // onUpPressed: grid.moveCurrentIndexUp() + // onDownPressed: grid.moveCurrentIndexDown() + // onLeftPressed: grid.moveCurrentIndexLeft() + // onRightPressed: grid.moveCurrentIndexRight() + onLeftPressed: carousel.decrementCurrentIndex() + onRightPressed: carousel.incrementCurrentIndex() onAPressed: console.log("A pressed") onLeftStickMoved: (x, y) => { gx = x @@ -54,67 +55,56 @@ Rectangle { } } } - Timer { interval: 16 running: true repeat: true onTriggered: gamepad.pollEvents() } - FolderDialog { id: openDir folder: StandardPaths.writableLocation(StandardPaths.HomeLocation) onAccepted: { button.visible = false - grid.anchors.bottom = root.bottom + view.anchors.bottom = root.bottom EdenGameList.addDir(folder) } } - GridView { - id: grid + // GameGrid { + // setting: parent.setting - property int cellSize: Math.floor(width / setting.value) + // id: grid - highlightFollowsCurrentItem: true - clip: true - - cellWidth: cellSize - cellHeight: cellSize + 60 * Constants.scalar + // anchors.bottom: button.top + // anchors.left: parent.left + // anchors.margins: 8 + // anchors.right: parent.right + // anchors.top: parent.top + // } + Item { + id: view anchors { - top: parent.top + bottom: button.top left: parent.left right: parent.right - - bottom: button.top - - margins: 8 + top: parent.top + margins: 8 * Constants.scalar } - model: EdenGameList + GameCarousel { + id: carousel - delegate: GamePreview { - id: game + height: 300 - width: grid.cellSize - 20 * Constants.scalar - height: grid.cellHeight - 20 * Constants.scalar - } + anchors { + right: view.right + left: view.left - highlight: Rectangle { - color: "transparent" - z: 5 - - radius: 16 * Constants.scalar - border { - color: Constants.text - width: 3 + verticalCenter: view.verticalCenter } } - - focus: true - focusPolicy: "StrongFocus" } Button { diff --git a/src/eden/qml/main/MarqueeText.qml b/src/eden/qml/main/MarqueeText.qml new file mode 100644 index 0000000000..7a70c53141 --- /dev/null +++ b/src/eden/qml/main/MarqueeText.qml @@ -0,0 +1,43 @@ +import QtQuick + +Item { + required property string text + + property int spacing: 30 + property int startDelay: 2000 + property int speed: 40 + + property alias font: t1.font + property alias color: t1.color + + id: root + + width: t1.width + spacing + height: t1.height + clip: true + + Text { + id: t1 + + SequentialAnimation on x { + loops: Animation.Infinite + running: true + + PauseAnimation { + duration: root.startDelay + } + + NumberAnimation { + from: root.width + to: -t1.width + duration: (root.width + t1.width) * 1000 / root.speed + easing.type: Easing.Linear + } + } + + Text { + x: root.width + text: t1.text + } + } +}