diff options
Diffstat (limited to 'src/yuzu')
28 files changed, 1045 insertions, 71 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 30902101d..2ee21f751 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -99,6 +99,9 @@ add_executable(yuzu configuration/configure_profile_manager.cpp configuration/configure_profile_manager.h configuration/configure_profile_manager.ui + configuration/configure_ringcon.cpp + configuration/configure_ringcon.h + configuration/configure_ringcon.ui configuration/configure_network.cpp configuration/configure_network.h configuration/configure_network.ui @@ -293,7 +296,7 @@ if (YUZU_USE_QT_WEB_ENGINE) endif () if(UNIX AND NOT APPLE) - install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") + install(TARGETS yuzu) endif() if (YUZU_USE_BUNDLED_QT) diff --git a/src/yuzu/aboutdialog.ui b/src/yuzu/aboutdialog.ui index 27d81cd13..2f7ddc7f3 100644 --- a/src/yuzu/aboutdialog.ui +++ b/src/yuzu/aboutdialog.ui @@ -87,7 +87,7 @@ <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt;">yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt;">yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv3.0+.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt;">This software should not be used to play games you have not legally obtained.</span></p></body></html></string> </property> diff --git a/src/yuzu/applets/qt_web_browser.cpp b/src/yuzu/applets/qt_web_browser.cpp index 398d8f0b6..116ef54db 100644 --- a/src/yuzu/applets/qt_web_browser.cpp +++ b/src/yuzu/applets/qt_web_browser.cpp @@ -11,6 +11,9 @@ #include <QWebEngineScriptCollection> #include <QWebEngineSettings> #include <QWebEngineUrlScheme> + +#include "core/hid/input_interpreter.h" +#include "yuzu/applets/qt_web_browser_scripts.h" #endif #include "common/fs/path_util.h" diff --git a/src/yuzu/applets/qt_web_browser.h b/src/yuzu/applets/qt_web_browser.h index 79a08f509..a47059412 100644 --- a/src/yuzu/applets/qt_web_browser.h +++ b/src/yuzu/applets/qt_web_browser.h @@ -5,6 +5,7 @@ #pragma once #include <atomic> +#include <thread> #include <QObject> diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 27b0300d2..a1b819ae0 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -933,6 +933,12 @@ void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) { auto& renderer = system.Renderer(); const f32 res_scale = Settings::values.resolution_info.up_factor; + if (renderer.IsScreenshotPending()) { + LOG_WARNING(Render, + "A screenshot is already requested or in progress, ignoring the request"); + return; + } + const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)}; screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32); renderer.RequestScreenshot( diff --git a/src/yuzu/compatdb.ui b/src/yuzu/compatdb.ui index fed402176..3ca55eda6 100644 --- a/src/yuzu/compatdb.ui +++ b/src/yuzu/compatdb.ui @@ -86,7 +86,7 @@ <item row="4" column="0"> <widget class="QRadioButton" name="radioButton_Great"> <property name="text"> - <string>Great </string> + <string>Great</string> </property> </widget> </item> diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 4b943c6ba..ac26b885b 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -60,6 +60,11 @@ const std::array<int, 2> Config::default_stick_mod = { 0, }; +const std::array<int, 2> Config::default_ringcon_analogs{{ + Qt::Key_A, + Qt::Key_D, +}}; + // 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 @@ -346,6 +351,23 @@ void Config::ReadTouchscreenValues() { ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt(); } +void Config::ReadHidbusValues() { + Settings::values.enable_ring_controller = + ReadSetting(QStringLiteral("enable_ring_controller"), true).toBool(); + + 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 = + qt_config->value(QStringLiteral("ring_controller"), QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (ringcon_analogs.empty()) { + ringcon_analogs = default_param; + } +} + void Config::ReadAudioValues() { qt_config->beginGroup(QStringLiteral("Audio")); @@ -369,6 +391,7 @@ void Config::ReadControlValues() { ReadMouseValues(); ReadTouchscreenValues(); ReadMotionTouchValues(); + ReadHidbusValues(); #ifdef _WIN32 ReadBasicSetting(Settings::values.enable_raw_input); @@ -775,6 +798,7 @@ void Config::ReadUIValues() { ReadBasicSetting(UISettings::values.pause_when_in_background); ReadBasicSetting(UISettings::values.mute_when_in_background); ReadBasicSetting(UISettings::values.hide_mouse); + ReadBasicSetting(UISettings::values.disable_web_applet); qt_config->endGroup(); } @@ -961,6 +985,16 @@ void Config::SaveMotionTouchValues() { qt_config->endArray(); } +void Config::SaveHidbusValues() { + WriteBasicSetting(Settings::values.enable_ring_controller); + + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); + WriteSetting(QStringLiteral("ring_controller"), + QString::fromStdString(Settings::values.ringcon_analogs), + QString::fromStdString(default_param)); +} + void Config::SaveValues() { if (global) { SaveControlValues(); @@ -1001,6 +1035,7 @@ void Config::SaveControlValues() { SaveMouseValues(); SaveTouchscreenValues(); SaveMotionTouchValues(); + SaveHidbusValues(); WriteGlobalSetting(Settings::values.use_docked_mode); WriteGlobalSetting(Settings::values.vibration_enabled); @@ -1308,6 +1343,7 @@ void Config::SaveUIValues() { WriteBasicSetting(UISettings::values.pause_when_in_background); WriteBasicSetting(UISettings::values.mute_when_in_background); WriteBasicSetting(UISettings::values.hide_mouse); + WriteBasicSetting(UISettings::values.disable_web_applet); qt_config->endGroup(); } diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index ae3e36a11..f0ab6bdaa 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -42,6 +42,7 @@ public: static const std::array<int, Settings::NativeMotion::NumMotions> default_motions; static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs; static const std::array<int, 2> default_stick_mod; + static const std::array<int, 2> default_ringcon_analogs; static const std::array<int, Settings::NativeMouseButton::NumMouseButtons> default_mouse_buttons; static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys; @@ -66,6 +67,7 @@ private: void ReadMouseValues(); void ReadTouchscreenValues(); void ReadMotionTouchValues(); + void ReadHidbusValues(); // Read functions bases off the respective config section names. void ReadAudioValues(); @@ -93,6 +95,7 @@ private: void SaveMouseValues(); void SaveTouchscreenValues(); void SaveMotionTouchValues(); + void SaveHidbusValues(); // Save functions based off the respective config section names. void SaveAudioValues(); diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui index 5d80a8c91..8ae569ee6 100644 --- a/src/yuzu/configuration/configure_cpu.ui +++ b/src/yuzu/configuration/configure_cpu.ui @@ -52,6 +52,11 @@ <string>Unsafe</string> </property> </item> + <item> + <property name="text"> + <string>Paranoid (disables most optimizations)</string> + </property> + </item> </widget> </item> </layout> diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index c1cf4050c..bd50f7a68 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -59,6 +59,13 @@ void ConfigureDebug::SetConfiguration() { ui->disable_loop_safety_checks->setChecked( Settings::values.disable_shader_loop_safety_checks.GetValue()); ui->extended_logging->setChecked(Settings::values.extended_logging.GetValue()); + +#ifdef YUZU_USE_QT_WEB_ENGINE + ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue()); +#else + ui->disable_web_applet->setEnabled(false); + ui->disable_web_applet->setText(QString::fromUtf8("Web applet not compiled")); +#endif } void ConfigureDebug::ApplyConfiguration() { @@ -80,6 +87,7 @@ void ConfigureDebug::ApplyConfiguration() { ui->disable_loop_safety_checks->isChecked(); Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked(); Settings::values.extended_logging = ui->extended_logging->isChecked(); + UISettings::values.disable_web_applet = ui->disable_web_applet->isChecked(); Debugger::ToggleConsole(); Common::Log::Filter filter; filter.ParseFilterString(Settings::values.log_filter.GetValue()); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 4dd870855..c1d90d588 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -8,49 +8,49 @@ <property name="title"> <string>Logging</string> </property> - <layout class="QGridLayout" name="gridLayout_1"> - <item row="0" column="0" colspan="2"> - <layout class="QHBoxLayout" name="horizontalLayout_1"> - <item> - <widget class="QLabel" name="label_1"> - <property name="text"> - <string>Global Log Filter</string> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="log_filter_edit"/> - </item> - </layout> - </item> - <item row="1" column="0"> - <widget class="QCheckBox" name="toggle_console"> - <property name="text"> - <string>Show Log in Console</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QPushButton" name="open_log_button"> - <property name="text"> - <string>Open Log Location</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QCheckBox" name="extended_logging"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="toolTip"> - <string>When checked, the max size of the log increases from 100 MB to 1 GB</string> - </property> + <layout class="QGridLayout" name="gridLayout_1"> + <item row="0" column="0" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout_1"> + <item> + <widget class="QLabel" name="label_1"> <property name="text"> - <string>Enable Extended Logging**</string> + <string>Global Log Filter</string> </property> - </widget> - </item> - </layout> + </widget> + </item> + <item> + <widget class="QLineEdit" name="log_filter_edit"/> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="toggle_console"> + <property name="text"> + <string>Show Log in Console</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="open_log_button"> + <property name="text"> + <string>Open Log Location</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="extended_logging"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="toolTip"> + <string>When checked, the max size of the log increases from 100 MB to 1 GB</string> + </property> + <property name="text"> + <string>Enable Extended Logging**</string> + </property> + </widget> + </item> + </layout> </widget> </item> <item> @@ -183,7 +183,7 @@ <string>Advanced</string> </property> <layout class="QGridLayout" name="gridLayout_4"> - <item> row="0" column="0"> + <item row="0" column="0"> <widget class="QCheckBox" name="quest_flag"> <property name="text"> <string>Kiosk (Quest) Mode</string> @@ -214,7 +214,14 @@ <item row="1" column="1"> <widget class="QCheckBox" name="enable_all_controllers"> <property name="text"> - <string>Enable all Controller Types</string> + <string>Enable All Controller Types</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QCheckBox" name="disable_web_applet"> + <property name="text"> + <string>Disable Web Applet</string> </property> </widget> </item> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 69458a3ff..b415a1cc4 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -25,6 +25,7 @@ #include "yuzu/configuration/configure_ui.h" #include "yuzu/configuration/configure_web.h" #include "yuzu/hotkeys.h" +#include "yuzu/uisettings.h" ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, InputCommon::InputSubsystem* input_subsystem, @@ -169,6 +170,8 @@ void ConfigureDialog::PopulateSelectionList() { void ConfigureDialog::OnLanguageChanged(const QString& locale) { emit LanguageChanged(locale); + // Reloading the game list is needed to force retranslation. + UISettings::values.is_game_list_reload_pending = true; // first apply the configuration, and then restore the display ApplyConfiguration(); RetranslateUI(); diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp index 53e629a5e..6679e9c53 100644 --- a/src/yuzu/configuration/configure_hotkeys.cpp +++ b/src/yuzu/configuration/configure_hotkeys.cpp @@ -35,8 +35,9 @@ ConfigureHotkeys::ConfigureHotkeys(Core::HID::HIDCore& hid_core, QWidget* parent ui->hotkey_list->setContextMenuPolicy(Qt::CustomContextMenu); ui->hotkey_list->setModel(model); - ui->hotkey_list->setColumnWidth(name_column, 200); - ui->hotkey_list->resizeColumnToContents(hotkey_column); + ui->hotkey_list->header()->setStretchLastSection(false); + ui->hotkey_list->header()->setSectionResizeMode(name_column, QHeaderView::ResizeMode::Stretch); + ui->hotkey_list->header()->setMinimumSectionSize(150); connect(ui->button_restore_defaults, &QPushButton::clicked, this, &ConfigureHotkeys::RestoreDefaults); @@ -76,8 +77,8 @@ void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) { } ui->hotkey_list->expandAll(); - ui->hotkey_list->resizeColumnToContents(name_column); ui->hotkey_list->resizeColumnToContents(hotkey_column); + ui->hotkey_list->resizeColumnToContents(controller_column); } void ConfigureHotkeys::changeEvent(QEvent* event) { diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 4ca74a5f7..73d7ba24b 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -20,6 +20,7 @@ #include "yuzu/configuration/configure_input_advanced.h" #include "yuzu/configuration/configure_input_player.h" #include "yuzu/configuration/configure_motion_touch.h" +#include "yuzu/configuration/configure_ringcon.h" #include "yuzu/configuration/configure_touchscreen_advanced.h" #include "yuzu/configuration/configure_vibration.h" #include "yuzu/configuration/input_profiles.h" @@ -158,6 +159,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, [this, input_subsystem] { CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem); }); + connect(advanced, &ConfigureInputAdvanced::CallRingControllerDialog, + [this, input_subsystem, &hid_core] { + CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core); + }); connect(ui->vibrationButton, &QPushButton::clicked, [this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); }); diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index 20fc2599d..8fd1f4a38 100644 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -79,13 +79,17 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent) &ConfigureInputAdvanced::UpdateUIEnabled); connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this, &ConfigureInputAdvanced::UpdateUIEnabled); + connect(ui->enable_ring_controller, &QCheckBox::stateChanged, this, + &ConfigureInputAdvanced::UpdateUIEnabled); connect(ui->debug_configure, &QPushButton::clicked, this, [this] { CallDebugControllerDialog(); }); connect(ui->touchscreen_advanced, &QPushButton::clicked, this, [this] { CallTouchscreenConfigDialog(); }); connect(ui->buttonMotionTouch, &QPushButton::clicked, this, - &ConfigureInputAdvanced::CallMotionTouchConfigDialog); + [this] { CallMotionTouchConfigDialog(); }); + connect(ui->ring_controller_configure, &QPushButton::clicked, this, + [this] { CallRingControllerDialog(); }); #ifndef _WIN32 ui->enable_raw_input->setVisible(false); @@ -132,6 +136,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() { Settings::values.enable_raw_input = ui->enable_raw_input->isChecked(); Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked(); Settings::values.controller_navigation = ui->controller_navigation->isChecked(); + Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked(); } void ConfigureInputAdvanced::LoadConfiguration() { @@ -164,6 +169,7 @@ void ConfigureInputAdvanced::LoadConfiguration() { ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue()); ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue()); ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue()); + ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue()); UpdateUIEnabled(); } @@ -185,4 +191,5 @@ void ConfigureInputAdvanced::UpdateUIEnabled() { ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked()); ui->mouse_panning->setEnabled(!ui->mouse_enabled->isChecked()); ui->mouse_panning_sensitivity->setEnabled(!ui->mouse_enabled->isChecked()); + ui->ring_controller_configure->setEnabled(ui->enable_ring_controller->isChecked()); } diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h index 3083d55c1..4472cb846 100644 --- a/src/yuzu/configuration/configure_input_advanced.h +++ b/src/yuzu/configuration/configure_input_advanced.h @@ -29,6 +29,7 @@ signals: void CallMouseConfigDialog(); void CallTouchscreenConfigDialog(); void CallMotionTouchConfigDialog(); + void CallRingControllerDialog(); private: void changeEvent(QEvent* event) override; diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui index 66f2075f2..14403cb10 100644 --- a/src/yuzu/configuration/configure_input_advanced.ui +++ b/src/yuzu/configuration/configure_input_advanced.ui @@ -2603,6 +2603,20 @@ </property> </widget> </item> + <item row="4" column="0"> + <widget class="QCheckBox" name="enable_ring_controller"> + <property name="text"> + <string>Ring Controller</string> + </property> + </widget> + </item> + <item row="4" column="2"> + <widget class="QPushButton" name="ring_controller_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index ac0a9d7de..b4f5d172a 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -10,6 +10,7 @@ #include <QMenu> #include <QMessageBox> #include <QTimer> +#include "common/assert.h" #include "common/param_package.h" #include "core/hid/emulated_controller.h" #include "core/hid/hid_core.h" @@ -119,6 +120,23 @@ QString GetButtonName(Common::Input::ButtonNames button_name) { } } +QString GetDirectionName(const std::string& direction) { + if (direction == "left") { + return QObject::tr("Left"); + } + if (direction == "right") { + return QObject::tr("Right"); + } + if (direction == "up") { + return QObject::tr("Up"); + } + if (direction == "down") { + return QObject::tr("Down"); + } + UNIMPLEMENTED_MSG("Unimplemented direction name={}", direction); + return QString::fromStdString(direction); +} + void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param, const std::string& button_name) { // The poller returned a complete axis, so set all the buttons @@ -162,7 +180,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { if (common_button_name == Common::Input::ButtonNames::Value) { if (param.Has("hat")) { - const QString hat = QString::fromStdString(param.Get("direction", "")); + const QString hat = GetDirectionName(param.Get("direction", "")); return QObject::tr("%1%2Hat %3").arg(toggle, inverted, hat); } if (param.Has("axis")) { @@ -1426,10 +1444,10 @@ void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) { } void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { - event->ignore(); if (!input_setter || !event) { return; } + event->ignore(); if (event->key() != Qt::Key_Escape) { input_subsystem->GetKeyboard()->PressKey(event->key()); } diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index 21e51d749..7893a85bb 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -47,6 +47,10 @@ ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* p item_model->setHeaderData(0, Qt::Horizontal, tr("Patch Name")); item_model->setHeaderData(1, Qt::Horizontal, tr("Version")); + tree_view->header()->setStretchLastSection(false); + tree_view->header()->setSectionResizeMode(0, QHeaderView::ResizeMode::Stretch); + tree_view->header()->setMinimumSectionSize(150); + // We must register all custom types with the Qt Automoc system so that we are able to use it // with signals/slots. In this case, QList falls under the umbrella of custom types. qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); @@ -138,5 +142,5 @@ void ConfigurePerGameAddons::LoadConfiguration() { item_model->appendRow(list_items.back()); } - tree_view->setColumnWidth(0, 5 * tree_view->width() / 16); + tree_view->resizeColumnToContents(1); } diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp new file mode 100644 index 000000000..f1cdd4fed --- /dev/null +++ b/src/yuzu/configuration/configure_ringcon.cpp @@ -0,0 +1,424 @@ +// Copyright 2022 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include <QKeyEvent> +#include <QMenu> +#include <QTimer> + +#include "core/hid/emulated_devices.h" +#include "core/hid/hid_core.h" +#include "input_common/drivers/keyboard.h" +#include "input_common/drivers/mouse.h" +#include "input_common/main.h" +#include "ui_configure_ringcon.h" +#include "yuzu/bootmanager.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_ringcon.h" + +const std::array<std::string, ConfigureRingController::ANALOG_SUB_BUTTONS_NUM> + ConfigureRingController::analog_sub_buttons{{ + "left", + "right", + }}; + +namespace { + +QString GetKeyName(int key_code) { + switch (key_code) { + case Qt::Key_Shift: + return QObject::tr("Shift"); + case Qt::Key_Control: + return QObject::tr("Ctrl"); + case Qt::Key_Alt: + return QObject::tr("Alt"); + case Qt::Key_Meta: + return {}; + default: + return QKeySequence(key_code).toString(); + } +} + +QString GetButtonName(Common::Input::ButtonNames button_name) { + switch (button_name) { + case Common::Input::ButtonNames::ButtonLeft: + return QObject::tr("Left"); + case Common::Input::ButtonNames::ButtonRight: + return QObject::tr("Right"); + case Common::Input::ButtonNames::ButtonDown: + return QObject::tr("Down"); + case Common::Input::ButtonNames::ButtonUp: + return QObject::tr("Up"); + case Common::Input::ButtonNames::TriggerZ: + return QObject::tr("Z"); + case Common::Input::ButtonNames::TriggerR: + return QObject::tr("R"); + case Common::Input::ButtonNames::TriggerL: + return QObject::tr("L"); + case Common::Input::ButtonNames::ButtonA: + return QObject::tr("A"); + case Common::Input::ButtonNames::ButtonB: + return QObject::tr("B"); + case Common::Input::ButtonNames::ButtonX: + return QObject::tr("X"); + case Common::Input::ButtonNames::ButtonY: + return QObject::tr("Y"); + case Common::Input::ButtonNames::ButtonStart: + return QObject::tr("Start"); + case Common::Input::ButtonNames::L1: + return QObject::tr("L1"); + case Common::Input::ButtonNames::L2: + return QObject::tr("L2"); + case Common::Input::ButtonNames::L3: + return QObject::tr("L3"); + case Common::Input::ButtonNames::R1: + return QObject::tr("R1"); + case Common::Input::ButtonNames::R2: + return QObject::tr("R2"); + case Common::Input::ButtonNames::R3: + return QObject::tr("R3"); + case Common::Input::ButtonNames::Circle: + return QObject::tr("Circle"); + case Common::Input::ButtonNames::Cross: + return QObject::tr("Cross"); + case Common::Input::ButtonNames::Square: + return QObject::tr("Square"); + case Common::Input::ButtonNames::Triangle: + return QObject::tr("Triangle"); + case Common::Input::ButtonNames::Share: + return QObject::tr("Share"); + case Common::Input::ButtonNames::Options: + return QObject::tr("Options"); + default: + return QObject::tr("[undefined]"); + } +} + +void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param, + const std::string& button_name) { + // The poller returned a complete axis, so set all the buttons + if (input_param.Has("axis_x") && input_param.Has("axis_y")) { + analog_param = input_param; + return; + } + // Check if the current configuration has either no engine or an axis binding. + // Clears out the old binding and adds one with analog_from_button. + if (!analog_param.Has("engine") || analog_param.Has("axis_x") || analog_param.Has("axis_y")) { + analog_param = { + {"engine", "analog_from_button"}, + }; + } + analog_param.Set(button_name, input_param.Serialize()); +} +} // namespace + +ConfigureRingController::ConfigureRingController(QWidget* parent, + InputCommon::InputSubsystem* input_subsystem_, + Core::HID::HIDCore& hid_core_) + : QDialog(parent), timeout_timer(std::make_unique<QTimer>()), + poll_timer(std::make_unique<QTimer>()), input_subsystem{input_subsystem_}, + + ui(std::make_unique<Ui::ConfigureRingController>()) { + ui->setupUi(this); + + analog_map_buttons = { + ui->buttonRingAnalogPull, + ui->buttonRingAnalogPush, + }; + + emulated_device = hid_core_.GetEmulatedDevices(); + emulated_device->SaveCurrentConfig(); + emulated_device->EnableConfiguration(); + + LoadConfiguration(); + + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { + auto* const analog_button = analog_map_buttons[sub_button_id]; + + if (analog_button == nullptr) { + continue; + } + + connect(analog_button, &QPushButton::clicked, [=, this] { + HandleClick( + analog_map_buttons[sub_button_id], + [=, this](const Common::ParamPackage& params) { + Common::ParamPackage param = emulated_device->GetRingParam(); + SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]); + emulated_device->SetRingParam(param); + }, + InputCommon::Polling::InputType::Stick); + }); + + analog_button->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(analog_button, &QPushButton::customContextMenuRequested, + [=, this](const QPoint& menu_location) { + QMenu context_menu; + Common::ParamPackage param = emulated_device->GetRingParam(); + context_menu.addAction(tr("Clear"), [&] { + emulated_device->SetRingParam({}); + analog_map_buttons[sub_button_id]->setText(tr("[not set]")); + }); + context_menu.addAction(tr("Invert axis"), [&] { + const bool invert_value = param.Get("invert_x", "+") == "-"; + const std::string invert_str = invert_value ? "+" : "-"; + param.Set("invert_x", invert_str); + emulated_device->SetRingParam(param); + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; + ++sub_button_id) { + analog_map_buttons[sub_button_id]->setText( + AnalogToText(param, analog_sub_buttons[sub_button_id])); + } + }); + context_menu.exec( + analog_map_buttons[sub_button_id]->mapToGlobal(menu_location)); + }); + } + + connect(ui->sliderRingAnalogDeadzone, &QSlider::valueChanged, [=, this] { + Common::ParamPackage param = emulated_device->GetRingParam(); + const auto slider_value = ui->sliderRingAnalogDeadzone->value(); + ui->labelRingAnalogDeadzone->setText(tr("Deadzone: %1%").arg(slider_value)); + param.Set("deadzone", slider_value / 100.0f); + emulated_device->SetRingParam(param); + }); + + connect(ui->restore_defaults_button, &QPushButton::clicked, this, + &ConfigureRingController::RestoreDefaults); + + timeout_timer->setSingleShot(true); + connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); }); + + connect(poll_timer.get(), &QTimer::timeout, [this] { + const auto& params = input_subsystem->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + }); + + resize(0, 0); +} + +ConfigureRingController::~ConfigureRingController() { + emulated_device->DisableConfiguration(); +}; + +void ConfigureRingController::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigureRingController::RetranslateUI() { + ui->retranslateUi(this); +} + +void ConfigureRingController::UpdateUI() { + RetranslateUI(); + const Common::ParamPackage param = emulated_device->GetRingParam(); + + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { + auto* const analog_button = analog_map_buttons[sub_button_id]; + + if (analog_button == nullptr) { + continue; + } + + analog_button->setText(AnalogToText(param, analog_sub_buttons[sub_button_id])); + } + + const auto deadzone_label = ui->labelRingAnalogDeadzone; + const auto deadzone_slider = ui->sliderRingAnalogDeadzone; + + int slider_value = static_cast<int>(param.Get("deadzone", 0.15f) * 100); + deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value)); + deadzone_slider->setValue(slider_value); +} + +void ConfigureRingController::ApplyConfiguration() { + emulated_device->DisableConfiguration(); + emulated_device->SaveCurrentConfig(); + emulated_device->EnableConfiguration(); +} + +void ConfigureRingController::LoadConfiguration() { + UpdateUI(); +} + +void ConfigureRingController::RestoreDefaults() { + const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys( + 0, 0, Config::default_ringcon_analogs[0], Config::default_ringcon_analogs[1], 0, 0.05f); + emulated_device->SetRingParam(Common::ParamPackage(default_ring_string)); + UpdateUI(); +} + +void ConfigureRingController::HandleClick( + QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, + InputCommon::Polling::InputType type) { + button->setText(tr("[waiting]")); + button->setFocus(); + + input_setter = new_input_setter; + + input_subsystem->BeginMapping(type); + + QWidget::grabMouse(); + QWidget::grabKeyboard(); + + timeout_timer->start(2500); // Cancel after 2.5 seconds + poll_timer->start(25); // Check for new inputs every 25ms +} + +void ConfigureRingController::SetPollingResult(const Common::ParamPackage& params, bool abort) { + timeout_timer->stop(); + poll_timer->stop(); + input_subsystem->StopMapping(); + + QWidget::releaseMouse(); + QWidget::releaseKeyboard(); + + if (!abort) { + (*input_setter)(params); + } + + UpdateUI(); + + input_setter = std::nullopt; +} + +bool ConfigureRingController::IsInputAcceptable(const Common::ParamPackage& params) const { + return true; +} + +void ConfigureRingController::mousePressEvent(QMouseEvent* event) { + if (!input_setter || !event) { + return; + } + + const auto button = GRenderWindow::QtButtonToMouseButton(event->button()); + input_subsystem->GetMouse()->PressButton(0, 0, 0, 0, button); +} + +void ConfigureRingController::keyPressEvent(QKeyEvent* event) { + if (!input_setter || !event) { + return; + } + event->ignore(); + if (event->key() != Qt::Key_Escape) { + input_subsystem->GetKeyboard()->PressKey(event->key()); + } +} + +QString ConfigureRingController::ButtonToText(const Common::ParamPackage& param) { + if (!param.Has("engine")) { + return QObject::tr("[not set]"); + } + + const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); + const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : ""); + const auto common_button_name = input_subsystem->GetButtonName(param); + + // Retrieve the names from Qt + if (param.Get("engine", "") == "keyboard") { + const QString button_str = GetKeyName(param.Get("code", 0)); + return QObject::tr("%1%2").arg(toggle, button_str); + } + + if (common_button_name == Common::Input::ButtonNames::Invalid) { + return QObject::tr("[invalid]"); + } + + if (common_button_name == Common::Input::ButtonNames::Engine) { + return QString::fromStdString(param.Get("engine", "")); + } + + if (common_button_name == Common::Input::ButtonNames::Value) { + if (param.Has("hat")) { + const QString hat = QString::fromStdString(param.Get("direction", "")); + return QObject::tr("%1%2Hat %3").arg(toggle, inverted, hat); + } + if (param.Has("axis")) { + const QString axis = QString::fromStdString(param.Get("axis", "")); + return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis); + } + if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) { + const QString axis_x = QString::fromStdString(param.Get("axis_x", "")); + const QString axis_y = QString::fromStdString(param.Get("axis_y", "")); + const QString axis_z = QString::fromStdString(param.Get("axis_z", "")); + return QObject::tr("%1%2Axis %3,%4,%5").arg(toggle, inverted, axis_x, axis_y, axis_z); + } + if (param.Has("motion")) { + const QString motion = QString::fromStdString(param.Get("motion", "")); + return QObject::tr("%1%2Motion %3").arg(toggle, inverted, motion); + } + if (param.Has("button")) { + const QString button = QString::fromStdString(param.Get("button", "")); + return QObject::tr("%1%2Button %3").arg(toggle, inverted, button); + } + } + + QString button_name = GetButtonName(common_button_name); + if (param.Has("hat")) { + return QObject::tr("%1%2Hat %3").arg(toggle, inverted, button_name); + } + if (param.Has("axis")) { + return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name); + } + if (param.Has("motion")) { + return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name); + } + if (param.Has("button")) { + return QObject::tr("%1%2Button %3").arg(toggle, inverted, button_name); + } + + return QObject::tr("[unknown]"); +} + +QString ConfigureRingController::AnalogToText(const Common::ParamPackage& param, + const std::string& dir) { + if (!param.Has("engine")) { + return QObject::tr("[not set]"); + } + + if (param.Get("engine", "") == "analog_from_button") { + return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); + } + + if (!param.Has("axis_x") || !param.Has("axis_y")) { + return QObject::tr("[unknown]"); + } + + const auto engine_str = param.Get("engine", ""); + const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); + const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); + const bool invert_x = param.Get("invert_x", "+") == "-"; + const bool invert_y = param.Get("invert_y", "+") == "-"; + + if (dir == "modifier") { + return QObject::tr("[unused]"); + } + + if (dir == "left") { + const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-"); + return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str); + } + if (dir == "right") { + const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+"); + return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str); + } + if (dir == "up") { + const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+"); + return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str); + } + if (dir == "down") { + const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-"); + return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str); + } + + return QObject::tr("[unknown]"); +}
\ No newline at end of file diff --git a/src/yuzu/configuration/configure_ringcon.h b/src/yuzu/configuration/configure_ringcon.h new file mode 100644 index 000000000..cf9e54f09 --- /dev/null +++ b/src/yuzu/configuration/configure_ringcon.h @@ -0,0 +1,85 @@ +// Copyright 2022 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <memory> +#include <QDialog> + +namespace InputCommon { +class InputSubsystem; +} // namespace InputCommon + +namespace Core::HID { +class HIDCore; +class EmulatedDevices; +} // namespace Core::HID + +namespace Ui { +class ConfigureRingController; +} // namespace Ui + +class ConfigureRingController : public QDialog { + Q_OBJECT + +public: + explicit ConfigureRingController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_, + Core::HID::HIDCore& hid_core_); + ~ConfigureRingController() override; + + void ApplyConfiguration(); + +private: + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + void UpdateUI(); + + /// Load configuration settings. + void LoadConfiguration(); + + /// Restore all buttons to their default values. + void RestoreDefaults(); + + /// Called when the button was pressed. + void HandleClick(QPushButton* button, + std::function<void(const Common::ParamPackage&)> new_input_setter, + InputCommon::Polling::InputType type); + + /// Finish polling and configure input using the input_setter. + void SetPollingResult(const Common::ParamPackage& params, bool abort); + + /// Checks whether a given input can be accepted. + bool IsInputAcceptable(const Common::ParamPackage& params) const; + + /// Handle mouse button press events. + void mousePressEvent(QMouseEvent* event) override; + + /// Handle key press events. + void keyPressEvent(QKeyEvent* event) override; + + QString ButtonToText(const Common::ParamPackage& param); + + QString AnalogToText(const Common::ParamPackage& param, const std::string& dir); + + static constexpr int ANALOG_SUB_BUTTONS_NUM = 2; + + // A group of four QPushButtons represent one analog input. The buttons each represent left, + // right, respectively. + std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM> analog_map_buttons; + + static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons; + + std::unique_ptr<QTimer> timeout_timer; + std::unique_ptr<QTimer> poll_timer; + + /// This will be the the setting function when an input is awaiting configuration. + std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; + + InputCommon::InputSubsystem* input_subsystem; + Core::HID::EmulatedDevices* emulated_device; + + std::unique_ptr<Ui::ConfigureRingController> ui; +}; diff --git a/src/yuzu/configuration/configure_ringcon.ui b/src/yuzu/configuration/configure_ringcon.ui new file mode 100644 index 000000000..9ec634dd4 --- /dev/null +++ b/src/yuzu/configuration/configure_ringcon.ui @@ -0,0 +1,278 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureRingController</class> + <widget class="QDialog" name="ConfigureRingController"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>298</width> + <height>339</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Ring Controller</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label_2"> + <property name="minimumSize"> + <size> + <width>280</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>If you want to use this controller configure player 1 as right controller and player 2 as dual joycon before starting the game to allow this controller to be detected properly.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>10</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QGroupBox" name="RingAnalog"> + <property name="title"> + <string>Ring Sensor Parameters</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>0</number> + </property> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="buttonRingAnalogPullHorizontaLayout"> + <property name="spacing"> + <number>3</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonRingAnalogPullGroup"> + <property name="title"> + <string>Pull</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonRingAnalogPullVerticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QPushButton" name="buttonRingAnalogPull"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>Pull</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonRingAnalogPushGroup"> + <property name="title"> + <string>Push</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonRingAnalogPushVerticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QPushButton" name="buttonRingAnalogPush"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>Push</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="sliderRingAnalogDeadzoneVerticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>10</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <layout class="QHBoxLayout" name="sliderRingAnalogDeadzoneHorizontalLayout"> + <item> + <widget class="QLabel" name="labelRingAnalogDeadzone"> + <property name="text"> + <string>Deadzone: 0%</string> + </property> + <property name="alignment"> + <set>Qt::AlignHCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QSlider" name="sliderRingAnalogDeadzone"> + <property name="maximum"> + <number>100</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="restore_defaults_button"> + <property name="text"> + <string>Restore Defaults</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureRingController</receiver> + <slot>accept()</slot> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigureRingController</receiver> + <slot>reject()</slot> + </connection> + </connections> +</ui> diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 211a84dde..f2a986ed8 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -164,8 +164,8 @@ public: } const CompatStatus& status = iterator->second; setData(compatibility, CompatNumberRole); - setText(QObject::tr(status.text)); - setToolTip(QObject::tr(status.tooltip)); + setText(tr(status.text)); + setToolTip(tr(status.tooltip)); setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole); } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ecd2f9f24..5e26aad29 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -24,7 +24,7 @@ #include "core/file_sys/vfs_real.h" #include "core/frontend/applets/controller.h" #include "core/frontend/applets/general_frontend.h" -#include "core/frontend/applets/mii.h" +#include "core/frontend/applets/mii_edit.h" #include "core/frontend/applets/software_keyboard.h" #include "core/hid/emulated_controller.h" #include "core/hid/hid_core.h" @@ -152,7 +152,8 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif -constexpr int default_mouse_timeout = 2500; +constexpr int default_mouse_hide_timeout = 2500; +constexpr int default_mouse_center_timeout = 10; /** * "Callouts" are one-time instructional messages shown to the user. In the config settings, there @@ -287,13 +288,14 @@ GMainWindow::GMainWindow() ui->menubar->setCursor(QCursor()); statusBar()->setCursor(QCursor()); - mouse_hide_timer.setInterval(default_mouse_timeout); + mouse_hide_timer.setInterval(default_mouse_hide_timeout); connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor); connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::ShowMouseCursor); - MigrateConfigFiles(); + mouse_center_timer.setInterval(default_mouse_center_timeout); + connect(&mouse_center_timer, &QTimer::timeout, this, &GMainWindow::CenterMouseCursor); - ui->action_Fullscreen->setChecked(false); + MigrateConfigFiles(); #if defined(HAVE_SDL2) && !defined(_WIN32) SDL_InitSubSystem(SDL_INIT_VIDEO); @@ -312,17 +314,20 @@ GMainWindow::GMainWindow() } QString game_path; + bool has_gamepath = false; + bool is_fullscreen = false; for (int i = 1; i < args.size(); ++i) { // Preserves drag/drop functionality if (args.size() == 2 && !args[1].startsWith(QChar::fromLatin1('-'))) { game_path = args[1]; + has_gamepath = true; break; } // Launch game in fullscreen mode if (args[i] == QStringLiteral("-f")) { - ui->action_Fullscreen->setChecked(true); + is_fullscreen = true; continue; } @@ -365,9 +370,15 @@ GMainWindow::GMainWindow() } game_path = args[++i]; + has_gamepath = true; } } + // Override fullscreen setting if gamepath or argument is provided + if (has_gamepath || is_fullscreen) { + ui->action_Fullscreen->setChecked(is_fullscreen); + } + if (!game_path.isEmpty()) { BootGame(game_path); } @@ -582,7 +593,7 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, #ifdef YUZU_USE_QT_WEB_ENGINE // Raw input breaks with the web applet, Disable web applets if enabled - if (disable_web_applet || Settings::values.enable_raw_input) { + if (UISettings::values.disable_web_applet || Settings::values.enable_raw_input) { emit WebBrowserClosed(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/"); return; @@ -647,12 +658,12 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, connect(exit_action, &QAction::triggered, this, [this, &web_browser_view] { const auto result = QMessageBox::warning( this, tr("Disable Web Applet"), - tr("Disabling the web applet will cause it to not be shown again for the rest of the " - "emulated session. This can lead to undefined behavior and should only be used with " - "Super Mario 3D All-Stars. Are you sure you want to disable the web applet?"), + tr("Disabling the web applet can lead to undefined behavior and should only be used " + "with Super Mario 3D All-Stars. Are you sure you want to disable the web " + "applet?\n(This can be re-enabled in the Debug settings.)"), QMessageBox::Yes | QMessageBox::No); if (result == QMessageBox::Yes) { - disable_web_applet = true; + UISettings::values.disable_web_applet = true; web_browser_view.SetFinished(true); } }); @@ -1280,8 +1291,8 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p system->SetAppletFrontendSet({ std::make_unique<QtControllerSelector>(*this), // Controller Selector std::make_unique<QtErrorDisplay>(*this), // Error Display + nullptr, // Mii Editor nullptr, // Parental Controls - nullptr, // Mii editor nullptr, // Photo Viewer std::make_unique<QtProfileSelector>(*this), // Profile Selector std::make_unique<QtSoftwareKeyboard>(*this), // Software Keyboard @@ -3294,10 +3305,26 @@ void GMainWindow::ShowMouseCursor() { } } +void GMainWindow::CenterMouseCursor() { + if (emu_thread == nullptr || !Settings::values.mouse_panning) { + mouse_center_timer.stop(); + return; + } + if (!this->isActiveWindow()) { + mouse_center_timer.stop(); + return; + } + const int center_x = render_window->width() / 2; + const int center_y = render_window->height() / 2; + + QCursor::setPos(mapToGlobal({center_x, center_y})); +} + void GMainWindow::OnMouseActivity() { if (!Settings::values.mouse_panning) { ShowMouseCursor(); } + mouse_center_timer.stop(); } void GMainWindow::OnCoreError(Core::SystemResultStatus result, std::string details) { @@ -3570,6 +3597,22 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) { AcceptDropEvent(event); } +void GMainWindow::leaveEvent(QEvent* event) { + if (Settings::values.mouse_panning) { + const QRect& rect = geometry(); + QPoint position = QCursor::pos(); + + qint32 x = qBound(rect.left(), position.x(), rect.right()); + qint32 y = qBound(rect.top(), position.y(), rect.bottom()); + // Only start the timer if the mouse has left the window bound. + // The leave event is also triggered when the window looses focus. + if (x != position.x() || y != position.y()) { + mouse_center_timer.start(); + } + event->accept(); + } +} + bool GMainWindow::ConfirmChangeGame() { if (emu_thread == nullptr) return true; @@ -3645,6 +3688,14 @@ void GMainWindow::UpdateUITheme() { setStyleSheet({}); } + QPalette new_pal(qApp->palette()); + if (UISettings::IsDarkTheme()) { + new_pal.setColor(QPalette::Link, QColor(0, 190, 255, 255)); + } else { + new_pal.setColor(QPalette::Link, QColor(0, 140, 200, 255)); + } + qApp->setPalette(new_pal); + QIcon::setThemeName(current_theme); QIcon::setThemeSearchPaths(theme_paths); } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 7659c8000..b399e9b01 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -328,6 +328,7 @@ private: void UpdateUISettings(); void HideMouseCursor(); void ShowMouseCursor(); + void CenterMouseCursor(); void OpenURL(const QUrl& url); void LoadTranslation(); void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); @@ -372,6 +373,7 @@ private: bool auto_paused = false; bool auto_muted = false; QTimer mouse_hide_timer; + QTimer mouse_center_timer; // FS std::shared_ptr<FileSys::VfsFilesystem> vfs; @@ -398,9 +400,6 @@ private: // Last game booted, used for multi-process apps QString last_filename_booted; - // Disables the web applet for the rest of the emulated session - bool disable_web_applet{}; - // Applets QtSoftwareKeyboardDialog* software_keyboard = nullptr; @@ -421,4 +420,5 @@ protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; + void leaveEvent(QEvent* event) override; }; diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp index 21683576c..f683b80f7 100644 --- a/src/yuzu/uisettings.cpp +++ b/src/yuzu/uisettings.cpp @@ -15,6 +15,14 @@ const Themes themes{{ {"Midnight Blue Colorful", "colorful_midnight_blue"}, }}; +bool IsDarkTheme() { + const auto& theme = UISettings::values.theme; + return theme == QStringLiteral("qdarkstyle") || + theme == QStringLiteral("qdarkstyle_midnight_blue") || + theme == QStringLiteral("colorful_dark") || + theme == QStringLiteral("colorful_midnight_blue"); +} + Values values = {}; } // namespace UISettings diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 06e8b46da..15ba9ea17 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -17,6 +17,8 @@ namespace UISettings { +bool IsDarkTheme(); + struct ContextualShortcut { QString keyseq; QString controller_keyseq; @@ -114,6 +116,7 @@ struct Values { bool configuration_applied; bool reset_to_defaults; + Settings::BasicSetting<bool> disable_web_applet{true, "disable_web_applet"}; }; extern Values values; diff --git a/src/yuzu/util/controller_navigation.cpp b/src/yuzu/util/controller_navigation.cpp index c2b13123d..9a1868cae 100644 --- a/src/yuzu/util/controller_navigation.cpp +++ b/src/yuzu/util/controller_navigation.cpp @@ -39,7 +39,7 @@ void ControllerNavigation::TriggerButton(Settings::NativeButton::Values native_b } void ControllerNavigation::ControllerUpdateEvent(Core::HID::ControllerTriggerType type) { - std::lock_guard lock{mutex}; + std::scoped_lock lock{mutex}; if (!Settings::values.controller_navigation) { return; } |