diff options
Diffstat (limited to 'src/yuzu/configuration')
81 files changed, 14905 insertions, 4167 deletions
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 3b9ab38dd..3c423a271 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -5,52 +5,59 @@  #include <array>  #include <QKeySequence>  #include <QSettings> +#include "common/common_paths.h"  #include "common/file_util.h" -#include "configure_input_simple.h"  #include "core/hle/service/acc/profile_manager.h"  #include "core/hle/service/hid/controllers/npad.h"  #include "input_common/main.h"  #include "input_common/udp/client.h"  #include "yuzu/configuration/config.h" -#include "yuzu/uisettings.h" -Config::Config() { -    // TODO: Don't hardcode the path; let the frontend decide where to put the config files. -    qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "qt-config.ini"; -    FileUtil::CreateFullPath(qt_config_loc); -    qt_config = -        std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat); -    Reload(); +namespace FS = Common::FS; + +Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) { +    global = config_type == ConfigType::GlobalConfig; + +    Initialize(config_name);  }  Config::~Config() { -    Save(); +    if (global) { +        Save(); +    }  }  const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = { -    Qt::Key_A, Qt::Key_S, Qt::Key_Z,    Qt::Key_X,  Qt::Key_3,     Qt::Key_4,    Qt::Key_Q, -    Qt::Key_W, Qt::Key_1, Qt::Key_2,    Qt::Key_N,  Qt::Key_M,     Qt::Key_F,    Qt::Key_T, -    Qt::Key_H, Qt::Key_G, Qt::Key_Left, Qt::Key_Up, Qt::Key_Right, Qt::Key_Down, Qt::Key_J, -    Qt::Key_I, Qt::Key_L, Qt::Key_K,    Qt::Key_D,  Qt::Key_C,     Qt::Key_B,    Qt::Key_V, +    Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_3, Qt::Key_4, Qt::Key_Q, +    Qt::Key_W, Qt::Key_1, Qt::Key_2, Qt::Key_N, Qt::Key_M, Qt::Key_F, Qt::Key_T, +    Qt::Key_H, Qt::Key_G, Qt::Key_D, Qt::Key_C, Qt::Key_B, Qt::Key_V, +}; + +const std::array<int, Settings::NativeMotion::NumMotions> Config::default_motions = { +    Qt::Key_7, +    Qt::Key_8,  }; -const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ +const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{      {          Qt::Key_Up,          Qt::Key_Down,          Qt::Key_Left,          Qt::Key_Right, -        Qt::Key_E,      },      {          Qt::Key_I,          Qt::Key_K,          Qt::Key_J,          Qt::Key_L, -        Qt::Key_R,      },  }}; +const std::array<int, 2> Config::default_stick_mod = { +    Qt::Key_E, +    Qt::Key_R, +}; +  const std::array<int, Settings::NativeMouseButton::NumMouseButtons> Config::default_mouse_buttons =      {          Qt::Key_BracketLeft, Qt::Key_BracketRight, Qt::Key_Apostrophe, Qt::Key_Minus, Qt::Key_Equal, @@ -212,95 +219,174 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default  // 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<UISettings::Shortcut, 15> default_hotkeys{{ -    {QStringLiteral("Capture Screenshot"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::ApplicationShortcut}}, +const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{ +    {QStringLiteral("Capture Screenshot"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}}, +    {QStringLiteral("Change Docked Mode"),       QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},      {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},      {QStringLiteral("Decrease Speed Limit"),     QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}}, -    {QStringLiteral("Exit yuzu"),                QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}},      {QStringLiteral("Exit Fullscreen"),          QStringLiteral("Main Window"), {QStringLiteral("Esc"), Qt::WindowShortcut}}, +    {QStringLiteral("Exit yuzu"),                QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}},      {QStringLiteral("Fullscreen"),               QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}},      {QStringLiteral("Increase Speed Limit"),     QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}}, -    {QStringLiteral("Load Amiibo"),              QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::ApplicationShortcut}}, -    {QStringLiteral("Load File"),                QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WindowShortcut}}, +    {QStringLiteral("Load Amiibo"),              QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}}, +    {QStringLiteral("Load File"),                QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}}, +    {QStringLiteral("Mute Audio"),               QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},      {QStringLiteral("Restart Emulation"),        QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},      {QStringLiteral("Stop Emulation"),           QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},      {QStringLiteral("Toggle Filter Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},      {QStringLiteral("Toggle Speed Limit"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}},      {QStringLiteral("Toggle Status Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}}, -    {QStringLiteral("Change Docked Mode"),       QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},  }};  // clang-format on -void Config::ReadPlayerValues() { -    for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { -        auto& player = Settings::values.players[p]; +void Config::Initialize(const std::string& config_name) { +    switch (type) { +    case ConfigType::GlobalConfig: +        qt_config_loc = fmt::format("{}" DIR_SEP "{}.ini", FS::GetUserPath(FS::UserPath::ConfigDir), +                                    config_name); +        FS::CreateFullPath(qt_config_loc); +        qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), +                                                QSettings::IniFormat); +        Reload(); +        break; +    case ConfigType::PerGameConfig: +        qt_config_loc = fmt::format("{}custom" DIR_SEP "{}.ini", +                                    FS::GetUserPath(FS::UserPath::ConfigDir), config_name); +        FS::CreateFullPath(qt_config_loc); +        qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), +                                                QSettings::IniFormat); +        Reload(); +        break; +    case ConfigType::InputProfile: +        qt_config_loc = fmt::format("{}input" DIR_SEP "{}.ini", +                                    FS::GetUserPath(FS::UserPath::ConfigDir), config_name); +        FS::CreateFullPath(qt_config_loc); +        qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), +                                                QSettings::IniFormat); +        break; +    } +} + +void Config::ReadPlayerValue(std::size_t player_index) { +    const QString player_prefix = [this, player_index] { +        if (type == ConfigType::InputProfile) { +            return QString{}; +        } else { +            return QStringLiteral("player_%1_").arg(player_index); +        } +    }(); + +    auto& player = Settings::values.players.GetValue()[player_index]; + +    if (player_prefix.isEmpty()) { +        const auto controller = static_cast<Settings::ControllerType>( +            qt_config +                ->value(QStringLiteral("%1type").arg(player_prefix), +                        static_cast<u8>(Settings::ControllerType::ProController)) +                .toUInt()); +        if (controller == Settings::ControllerType::LeftJoycon || +            controller == Settings::ControllerType::RightJoycon) { +            player.controller_type = controller; +        } +    } else {          player.connected = -            ReadSetting(QStringLiteral("player_%1_connected").arg(p), false).toBool(); +            ReadSetting(QStringLiteral("%1connected").arg(player_prefix), player_index == 0) +                .toBool(); -        player.type = static_cast<Settings::ControllerType>( +        player.controller_type = static_cast<Settings::ControllerType>(              qt_config -                ->value(QStringLiteral("player_%1_type").arg(p), -                        static_cast<u8>(Settings::ControllerType::DualJoycon)) +                ->value(QStringLiteral("%1type").arg(player_prefix), +                        static_cast<u8>(Settings::ControllerType::ProController))                  .toUInt()); +        player.vibration_enabled = +            qt_config->value(QStringLiteral("%1vibration_enabled").arg(player_prefix), true) +                .toBool(); + +        player.vibration_strength = +            qt_config->value(QStringLiteral("%1vibration_strength").arg(player_prefix), 100) +                .toInt(); +          player.body_color_left = qt_config -                                     ->value(QStringLiteral("player_%1_body_color_left").arg(p), +                                     ->value(QStringLiteral("%1body_color_left").arg(player_prefix),                                               Settings::JOYCON_BODY_NEON_BLUE)                                       .toUInt(); -        player.body_color_right = qt_config -                                      ->value(QStringLiteral("player_%1_body_color_right").arg(p), -                                              Settings::JOYCON_BODY_NEON_RED) -                                      .toUInt(); -        player.button_color_left = qt_config -                                       ->value(QStringLiteral("player_%1_button_color_left").arg(p), -                                               Settings::JOYCON_BUTTONS_NEON_BLUE) -                                       .toUInt(); +        player.body_color_right = +            qt_config +                ->value(QStringLiteral("%1body_color_right").arg(player_prefix), +                        Settings::JOYCON_BODY_NEON_RED) +                .toUInt(); +        player.button_color_left = +            qt_config +                ->value(QStringLiteral("%1button_color_left").arg(player_prefix), +                        Settings::JOYCON_BUTTONS_NEON_BLUE) +                .toUInt();          player.button_color_right =              qt_config -                ->value(QStringLiteral("player_%1_button_color_right").arg(p), +                ->value(QStringLiteral("%1button_color_right").arg(player_prefix),                          Settings::JOYCON_BUTTONS_NEON_RED)                  .toUInt(); +    } -        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 = qt_config -                                 ->value(QStringLiteral("player_%1_").arg(p) + -                                             QString::fromUtf8(Settings::NativeButton::mapping[i]), -                                         QString::fromStdString(default_param)) -                                 .toString() -                                 .toStdString(); -            if (player_buttons.empty()) { -                player_buttons = default_param; -            } +    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 = qt_config +                             ->value(QStringLiteral("%1").arg(player_prefix) + +                                         QString::fromUtf8(Settings::NativeButton::mapping[i]), +                                     QString::fromStdString(default_param)) +                             .toString() +                             .toStdString(); +        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_analogs[i][4], 0.5f); -            auto& player_analogs = player.analogs[i]; - -            player_analogs = qt_config -                                 ->value(QStringLiteral("player_%1_").arg(p) + -                                             QString::fromUtf8(Settings::NativeAnalog::mapping[i]), -                                         QString::fromStdString(default_param)) -                                 .toString() -                                 .toStdString(); -            if (player_analogs.empty()) { -                player_analogs = 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 = qt_config +                             ->value(QStringLiteral("%1").arg(player_prefix) + +                                         QString::fromUtf8(Settings::NativeAnalog::mapping[i]), +                                     QString::fromStdString(default_param)) +                             .toString() +                             .toStdString(); +        if (player_analogs.empty()) { +            player_analogs = default_param;          }      } -    std::stable_partition( -        Settings::values.players.begin(), -        Settings::values.players.begin() + -            Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD), -        [](const auto& player) { return player.connected; }); +    for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) { +        auto& player_vibrations = player.vibrations[i]; + +        player_vibrations = +            qt_config +                ->value(QStringLiteral("%1").arg(player_prefix) + +                            QString::fromUtf8(Settings::NativeVibration::mapping[i]), +                        QString{}) +                .toString() +                .toStdString(); +    } + +    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 = qt_config +                             ->value(QStringLiteral("%1").arg(player_prefix) + +                                         QString::fromUtf8(Settings::NativeMotion::mapping[i]), +                                     QString::fromStdString(default_param)) +                             .toString() +                             .toStdString(); +        if (player_motions.empty()) { +            player_motions = default_param; +        } +    }  }  void Config::ReadDebugValues() { @@ -325,7 +411,7 @@ void Config::ReadDebugValues() {      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_analogs[i][4], 0.5f); +            default_analogs[i][3], default_stick_mod[i], 0.5f);          auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i];          debug_pad_analogs = qt_config @@ -392,26 +478,22 @@ void Config::ReadTouchscreenValues() {          ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt();  } -void Config::ApplyDefaultProfileIfInputInvalid() { -    if (!std::any_of(Settings::values.players.begin(), Settings::values.players.end(), -                     [](const Settings::PlayerInput& in) { return in.connected; })) { -        ApplyInputProfileConfiguration(UISettings::values.profile_index); -    } -} -  void Config::ReadAudioValues() {      qt_config->beginGroup(QStringLiteral("Audio")); -    Settings::values.sink_id = ReadSetting(QStringLiteral("output_engine"), QStringLiteral("auto")) -                                   .toString() -                                   .toStdString(); -    Settings::values.enable_audio_stretching = -        ReadSetting(QStringLiteral("enable_audio_stretching"), true).toBool(); -    Settings::values.audio_device_id = -        ReadSetting(QStringLiteral("output_device"), QStringLiteral("auto")) -            .toString() -            .toStdString(); -    Settings::values.volume = ReadSetting(QStringLiteral("volume"), 1).toFloat(); +    if (global) { +        Settings::values.sink_id = +            ReadSetting(QStringLiteral("output_engine"), QStringLiteral("auto")) +                .toString() +                .toStdString(); +        Settings::values.audio_device_id = +            ReadSetting(QStringLiteral("output_device"), QStringLiteral("auto")) +                .toString() +                .toStdString(); +    } +    ReadSettingGlobal(Settings::values.enable_audio_stretching, +                      QStringLiteral("enable_audio_stretching"), true); +    ReadSettingGlobal(Settings::values.volume, QStringLiteral("volume"), 1);      qt_config->endGroup();  } @@ -419,17 +501,73 @@ void Config::ReadAudioValues() {  void Config::ReadControlValues() {      qt_config->beginGroup(QStringLiteral("Controls")); -    ReadPlayerValues(); +    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { +        ReadPlayerValue(p); +    }      ReadDebugValues();      ReadKeyboardValues();      ReadMouseValues();      ReadTouchscreenValues(); +    ReadMotionTouchValues(); + +    ReadSettingGlobal(Settings::values.use_docked_mode, QStringLiteral("use_docked_mode"), false); +    ReadSettingGlobal(Settings::values.vibration_enabled, QStringLiteral("vibration_enabled"), +                      true); +    ReadSettingGlobal(Settings::values.enable_accurate_vibrations, +                      QStringLiteral("enable_accurate_vibrations"), false); +    ReadSettingGlobal(Settings::values.motion_enabled, QStringLiteral("motion_enabled"), true); + +    qt_config->endGroup(); +} + +void Config::ReadMotionTouchValues() { +    int num_touch_from_button_maps = +        qt_config->beginReadArray(QStringLiteral("touch_from_button_maps")); + +    if (num_touch_from_button_maps > 0) { +        const auto append_touch_from_button_map = [this] { +            Settings::TouchFromButtonMap map; +            map.name = ReadSetting(QStringLiteral("name"), QStringLiteral("default")) +                           .toString() +                           .toStdString(); +            const int num_touch_maps = qt_config->beginReadArray(QStringLiteral("entries")); +            map.buttons.reserve(num_touch_maps); +            for (int i = 0; i < num_touch_maps; i++) { +                qt_config->setArrayIndex(i); +                std::string touch_mapping = +                    ReadSetting(QStringLiteral("bind")).toString().toStdString(); +                map.buttons.emplace_back(std::move(touch_mapping)); +            } +            qt_config->endArray(); // entries +            Settings::values.touch_from_button_maps.emplace_back(std::move(map)); +        }; + +        for (int i = 0; i < num_touch_from_button_maps; ++i) { +            qt_config->setArrayIndex(i); +            append_touch_from_button_map(); +        } +    } else { +        Settings::values.touch_from_button_maps.emplace_back( +            Settings::TouchFromButtonMap{"default", {}}); +        num_touch_from_button_maps = 1; +    } +    qt_config->endArray();      Settings::values.motion_device =          ReadSetting(QStringLiteral("motion_device"),                      QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))              .toString()              .toStdString(); +    Settings::values.touch_device = +        ReadSetting(QStringLiteral("touch_device"), QStringLiteral("engine:emu_window")) +            .toString() +            .toStdString(); +    Settings::values.use_touch_from_button = +        ReadSetting(QStringLiteral("use_touch_from_button"), false).toBool(); +    Settings::values.touch_from_button_map_index = +        ReadSetting(QStringLiteral("touch_from_button_map"), 0).toInt(); +    Settings::values.touch_from_button_map_index = +        std::clamp(Settings::values.touch_from_button_map_index, 0, num_touch_from_button_maps - 1);      Settings::values.udp_input_address =          ReadSetting(QStringLiteral("udp_input_address"),                      QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)) @@ -440,14 +578,12 @@ void Config::ReadControlValues() {              .toInt());      Settings::values.udp_pad_index =          static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt()); - -    qt_config->endGroup();  }  void Config::ReadCoreValues() {      qt_config->beginGroup(QStringLiteral("Core")); -    Settings::values.use_multi_core = ReadSetting(QStringLiteral("use_multi_core"), false).toBool(); +    ReadSettingGlobal(Settings::values.use_multi_core, QStringLiteral("use_multi_core"), true);      qt_config->endGroup();  } @@ -456,63 +592,42 @@ void Config::ReadDataStorageValues() {      qt_config->beginGroup(QStringLiteral("Data Storage"));      Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool(); -    FileUtil::GetUserPath( -        FileUtil::UserPath::NANDDir, -        qt_config -            ->value(QStringLiteral("nand_directory"), -                    QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))) -            .toString() -            .toStdString()); -    FileUtil::GetUserPath( -        FileUtil::UserPath::SDMCDir, -        qt_config -            ->value(QStringLiteral("sdmc_directory"), -                    QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))) -            .toString() -            .toStdString()); -    FileUtil::GetUserPath( -        FileUtil::UserPath::LoadDir, -        qt_config -            ->value(QStringLiteral("load_directory"), -                    QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir))) -            .toString() -            .toStdString()); -    FileUtil::GetUserPath( -        FileUtil::UserPath::DumpDir, -        qt_config -            ->value(QStringLiteral("dump_directory"), -                    QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir))) -            .toString() -            .toStdString()); -    FileUtil::GetUserPath( -        FileUtil::UserPath::CacheDir, -        qt_config -            ->value(QStringLiteral("cache_directory"), -                    QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir))) -            .toString() -            .toStdString()); +    FS::GetUserPath(FS::UserPath::NANDDir, +                    qt_config +                        ->value(QStringLiteral("nand_directory"), +                                QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir))) +                        .toString() +                        .toStdString()); +    FS::GetUserPath(FS::UserPath::SDMCDir, +                    qt_config +                        ->value(QStringLiteral("sdmc_directory"), +                                QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir))) +                        .toString() +                        .toStdString()); +    FS::GetUserPath(FS::UserPath::LoadDir, +                    qt_config +                        ->value(QStringLiteral("load_directory"), +                                QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir))) +                        .toString() +                        .toStdString()); +    FS::GetUserPath(FS::UserPath::DumpDir, +                    qt_config +                        ->value(QStringLiteral("dump_directory"), +                                QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir))) +                        .toString() +                        .toStdString()); +    FS::GetUserPath(FS::UserPath::CacheDir, +                    qt_config +                        ->value(QStringLiteral("cache_directory"), +                                QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir))) +                        .toString() +                        .toStdString());      Settings::values.gamecard_inserted =          ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool();      Settings::values.gamecard_current_game =          ReadSetting(QStringLiteral("gamecard_current_game"), false).toBool();      Settings::values.gamecard_path = -        ReadSetting(QStringLiteral("gamecard_path"), QStringLiteral("")).toString().toStdString(); -    Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>( -        ReadSetting(QStringLiteral("nand_total_size"), -                    QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDTotalSize::S29_1GB))) -            .toULongLong()); -    Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>( -        ReadSetting(QStringLiteral("nand_user_size"), -                    QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDUserSize::S26GB))) -            .toULongLong()); -    Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>( -        ReadSetting(QStringLiteral("nand_system_size"), -                    QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDSystemSize::S2_5GB))) -            .toULongLong()); -    Settings::values.sdmc_size = static_cast<Settings::SDMCSize>( -        ReadSetting(QStringLiteral("sdmc_size"), -                    QVariant::fromValue<u64>(static_cast<u64>(Settings::SDMCSize::S16GB))) -            .toULongLong()); +        ReadSetting(QStringLiteral("gamecard_path"), QString{}).toString().toStdString();      qt_config->endGroup();  } @@ -526,12 +641,16 @@ void Config::ReadDebuggingValues() {      Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool();      Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt();      Settings::values.program_args = -        ReadSetting(QStringLiteral("program_args"), QStringLiteral("")).toString().toStdString(); +        ReadSetting(QStringLiteral("program_args"), QString{}).toString().toStdString();      Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool();      Settings::values.dump_nso = ReadSetting(QStringLiteral("dump_nso"), false).toBool();      Settings::values.reporting_services =          ReadSetting(QStringLiteral("reporting_services"), false).toBool();      Settings::values.quest_flag = ReadSetting(QStringLiteral("quest_flag"), false).toBool(); +    Settings::values.disable_macro_jit = +        ReadSetting(QStringLiteral("disable_macro_jit"), false).toBool(); +    Settings::values.extended_logging = +        ReadSetting(QStringLiteral("extended_logging"), false).toBool();      qt_config->endGroup();  } @@ -557,8 +676,7 @@ void Config::ReadDisabledAddOnValues() {          const auto d_size = qt_config->beginReadArray(QStringLiteral("disabled"));          for (int j = 0; j < d_size; ++j) {              qt_config->setArrayIndex(j); -            out.push_back( -                ReadSetting(QStringLiteral("d"), QStringLiteral("")).toString().toStdString()); +            out.push_back(ReadSetting(QStringLiteral("d"), QString{}).toString().toStdString());          }          qt_config->endArray();          Settings::values.disabled_addons.insert_or_assign(title_id, out); @@ -584,7 +702,6 @@ void Config::ReadPathValues() {      UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString();      UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString(); -    UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString();      UISettings::values.game_dir_deprecated =          ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString();      UISettings::values.game_dir_deprecated_deepscan = @@ -617,6 +734,40 @@ void Config::ReadPathValues() {          }      }      UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); +    UISettings::values.language = ReadSetting(QStringLiteral("language"), QString{}).toString(); + +    qt_config->endGroup(); +} + +void Config::ReadCpuValues() { +    qt_config->beginGroup(QStringLiteral("Cpu")); + +    if (global) { +        Settings::values.cpu_accuracy = static_cast<Settings::CPUAccuracy>( +            ReadSetting(QStringLiteral("cpu_accuracy"), 0).toInt()); + +        Settings::values.cpuopt_page_tables = +            ReadSetting(QStringLiteral("cpuopt_page_tables"), true).toBool(); +        Settings::values.cpuopt_block_linking = +            ReadSetting(QStringLiteral("cpuopt_block_linking"), true).toBool(); +        Settings::values.cpuopt_return_stack_buffer = +            ReadSetting(QStringLiteral("cpuopt_return_stack_buffer"), true).toBool(); +        Settings::values.cpuopt_fast_dispatcher = +            ReadSetting(QStringLiteral("cpuopt_fast_dispatcher"), true).toBool(); +        Settings::values.cpuopt_context_elimination = +            ReadSetting(QStringLiteral("cpuopt_context_elimination"), true).toBool(); +        Settings::values.cpuopt_const_prop = +            ReadSetting(QStringLiteral("cpuopt_const_prop"), true).toBool(); +        Settings::values.cpuopt_misc_ir = +            ReadSetting(QStringLiteral("cpuopt_misc_ir"), true).toBool(); +        Settings::values.cpuopt_reduce_misalign_checks = +            ReadSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), true).toBool(); + +        Settings::values.cpuopt_unsafe_unfuse_fma = +            ReadSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"), true).toBool(); +        Settings::values.cpuopt_unsafe_reduce_fp_error = +            ReadSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true).toBool(); +    }      qt_config->endGroup();  } @@ -624,30 +775,46 @@ void Config::ReadPathValues() {  void Config::ReadRendererValues() {      qt_config->beginGroup(QStringLiteral("Renderer")); -    Settings::values.renderer_backend = -        static_cast<Settings::RendererBackend>(ReadSetting(QStringLiteral("backend"), 0).toInt()); -    Settings::values.renderer_debug = ReadSetting(QStringLiteral("debug"), false).toBool(); -    Settings::values.vulkan_device = ReadSetting(QStringLiteral("vulkan_device"), 0).toInt(); -    Settings::values.resolution_factor = -        ReadSetting(QStringLiteral("resolution_factor"), 1.0).toFloat(); -    Settings::values.aspect_ratio = ReadSetting(QStringLiteral("aspect_ratio"), 0).toInt(); -    Settings::values.max_anisotropy = ReadSetting(QStringLiteral("max_anisotropy"), 0).toInt(); -    Settings::values.use_frame_limit = -        ReadSetting(QStringLiteral("use_frame_limit"), true).toBool(); -    Settings::values.frame_limit = ReadSetting(QStringLiteral("frame_limit"), 100).toInt(); -    Settings::values.use_disk_shader_cache = -        ReadSetting(QStringLiteral("use_disk_shader_cache"), true).toBool(); -    Settings::values.use_accurate_gpu_emulation = -        ReadSetting(QStringLiteral("use_accurate_gpu_emulation"), false).toBool(); -    Settings::values.use_asynchronous_gpu_emulation = -        ReadSetting(QStringLiteral("use_asynchronous_gpu_emulation"), false).toBool(); -    Settings::values.use_vsync = ReadSetting(QStringLiteral("use_vsync"), true).toBool(); -    Settings::values.force_30fps_mode = -        ReadSetting(QStringLiteral("force_30fps_mode"), false).toBool(); - -    Settings::values.bg_red = ReadSetting(QStringLiteral("bg_red"), 0.0).toFloat(); -    Settings::values.bg_green = ReadSetting(QStringLiteral("bg_green"), 0.0).toFloat(); -    Settings::values.bg_blue = ReadSetting(QStringLiteral("bg_blue"), 0.0).toFloat(); +    ReadSettingGlobal(Settings::values.renderer_backend, QStringLiteral("backend"), 0); +    ReadSettingGlobal(Settings::values.renderer_debug, QStringLiteral("debug"), false); +    ReadSettingGlobal(Settings::values.vulkan_device, QStringLiteral("vulkan_device"), 0); +    ReadSettingGlobal(Settings::values.aspect_ratio, QStringLiteral("aspect_ratio"), 0); +    ReadSettingGlobal(Settings::values.max_anisotropy, QStringLiteral("max_anisotropy"), 0); +    ReadSettingGlobal(Settings::values.use_frame_limit, QStringLiteral("use_frame_limit"), true); +    ReadSettingGlobal(Settings::values.frame_limit, QStringLiteral("frame_limit"), 100); +    ReadSettingGlobal(Settings::values.use_disk_shader_cache, +                      QStringLiteral("use_disk_shader_cache"), true); +    ReadSettingGlobal(Settings::values.gpu_accuracy, QStringLiteral("gpu_accuracy"), 0); +    ReadSettingGlobal(Settings::values.use_asynchronous_gpu_emulation, +                      QStringLiteral("use_asynchronous_gpu_emulation"), true); +    ReadSettingGlobal(Settings::values.use_nvdec_emulation, QStringLiteral("use_nvdec_emulation"), +                      true); +    ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true); +    ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"), +                      true); +    ReadSettingGlobal(Settings::values.use_asynchronous_shaders, +                      QStringLiteral("use_asynchronous_shaders"), false); +    ReadSettingGlobal(Settings::values.use_fast_gpu_time, QStringLiteral("use_fast_gpu_time"), +                      true); +    ReadSettingGlobal(Settings::values.bg_red, QStringLiteral("bg_red"), 0.0); +    ReadSettingGlobal(Settings::values.bg_green, QStringLiteral("bg_green"), 0.0); +    ReadSettingGlobal(Settings::values.bg_blue, QStringLiteral("bg_blue"), 0.0); + +    qt_config->endGroup(); +} + +void Config::ReadScreenshotValues() { +    qt_config->beginGroup(QStringLiteral("Screenshots")); + +    UISettings::values.enable_screenshot_save_as = +        ReadSetting(QStringLiteral("enable_screenshot_save_as"), true).toBool(); +    FS::GetUserPath( +        FS::UserPath::ScreenshotsDir, +        qt_config +            ->value(QStringLiteral("screenshot_path"), +                    QString::fromStdString(FS::GetUserPath(FS::UserPath::ScreenshotsDir))) +            .toString() +            .toStdString());      qt_config->endGroup();  } @@ -659,11 +826,13 @@ void Config::ReadShortcutValues() {          const auto& [keyseq, context] = shortcut;          qt_config->beginGroup(group);          qt_config->beginGroup(name); +        // No longer using ReadSetting for shortcut.second as it innacurately 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, -             {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(), -              ReadSetting(QStringLiteral("Context"), context).toInt()}}); +             {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(), shortcut.second}});          qt_config->endGroup();          qt_config->endGroup();      } @@ -674,33 +843,45 @@ void Config::ReadShortcutValues() {  void Config::ReadSystemValues() {      qt_config->beginGroup(QStringLiteral("System")); -    Settings::values.use_docked_mode = -        ReadSetting(QStringLiteral("use_docked_mode"), false).toBool(); +    ReadSettingGlobal(Settings::values.current_user, QStringLiteral("current_user"), 0); +    Settings::values.current_user = +        std::clamp<int>(Settings::values.current_user, 0, Service::Account::MAX_USERS - 1); -    Settings::values.current_user = std::clamp<int>( -        ReadSetting(QStringLiteral("current_user"), 0).toInt(), 0, Service::Account::MAX_USERS - 1); +    ReadSettingGlobal(Settings::values.language_index, QStringLiteral("language_index"), 1); -    Settings::values.language_index = ReadSetting(QStringLiteral("language_index"), 1).toInt(); +    ReadSettingGlobal(Settings::values.region_index, QStringLiteral("region_index"), 1); -    Settings::values.region_index = ReadSetting(QStringLiteral("region_index"), 1).toInt(); +    ReadSettingGlobal(Settings::values.time_zone_index, QStringLiteral("time_zone_index"), 0); -    const auto rng_seed_enabled = ReadSetting(QStringLiteral("rng_seed_enabled"), false).toBool(); -    if (rng_seed_enabled) { -        Settings::values.rng_seed = ReadSetting(QStringLiteral("rng_seed"), 0).toULongLong(); -    } else { -        Settings::values.rng_seed = std::nullopt; +    bool rng_seed_enabled; +    ReadSettingGlobal(rng_seed_enabled, QStringLiteral("rng_seed_enabled"), false); +    bool rng_seed_global = +        global || qt_config->value(QStringLiteral("rng_seed/use_global"), true).toBool(); +    Settings::values.rng_seed.SetGlobal(rng_seed_global); +    if (global || !rng_seed_global) { +        if (rng_seed_enabled) { +            Settings::values.rng_seed.SetValue( +                ReadSetting(QStringLiteral("rng_seed"), 0).toULongLong()); +        } else { +            Settings::values.rng_seed.SetValue(std::nullopt); +        }      } -    const auto custom_rtc_enabled = -        ReadSetting(QStringLiteral("custom_rtc_enabled"), false).toBool(); -    if (custom_rtc_enabled) { -        Settings::values.custom_rtc = -            std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong()); -    } else { -        Settings::values.custom_rtc = std::nullopt; +    bool custom_rtc_enabled; +    ReadSettingGlobal(custom_rtc_enabled, QStringLiteral("custom_rtc_enabled"), false); +    bool custom_rtc_global = +        global || qt_config->value(QStringLiteral("custom_rtc/use_global"), true).toBool(); +    Settings::values.custom_rtc.SetGlobal(custom_rtc_global); +    if (global || !custom_rtc_global) { +        if (custom_rtc_enabled) { +            Settings::values.custom_rtc.SetValue( +                std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong())); +        } else { +            Settings::values.custom_rtc.SetValue(std::nullopt); +        }      } -    Settings::values.sound_index = ReadSetting(QStringLiteral("sound_index"), 1).toInt(); +    ReadSettingGlobal(Settings::values.sound_index, QStringLiteral("sound_index"), 1);      qt_config->endGroup();  } @@ -713,14 +894,13 @@ void Config::ReadUIValues() {              .toString();      UISettings::values.enable_discord_presence =          ReadSetting(QStringLiteral("enable_discord_presence"), true).toBool(); -    UISettings::values.screenshot_resolution_factor = -        static_cast<u16>(ReadSetting(QStringLiteral("screenshot_resolution_factor"), 0).toUInt());      UISettings::values.select_user_on_boot =          ReadSetting(QStringLiteral("select_user_on_boot"), false).toBool();      ReadUIGamelistValues();      ReadUILayoutValues();      ReadPathValues(); +    ReadScreenshotValues();      ReadShortcutValues();      UISettings::values.single_window_mode = @@ -737,11 +917,10 @@ void Config::ReadUIValues() {      UISettings::values.first_start = ReadSetting(QStringLiteral("firstStart"), true).toBool();      UISettings::values.callout_flags = ReadSetting(QStringLiteral("calloutFlags"), 0).toUInt();      UISettings::values.show_console = ReadSetting(QStringLiteral("showConsole"), false).toBool(); -    UISettings::values.profile_index = ReadSetting(QStringLiteral("profileIndex"), 0).toUInt();      UISettings::values.pause_when_in_background =          ReadSetting(QStringLiteral("pauseWhenInBackground"), false).toBool(); - -    ApplyDefaultProfileIfInputInvalid(); +    UISettings::values.hide_mouse = +        ReadSetting(QStringLiteral("hideInactiveMouse"), false).toBool();      qt_config->endGroup();  } @@ -794,54 +973,81 @@ void Config::ReadWebServiceValues() {  }  void Config::ReadValues() { -    ReadControlValues(); +    if (global) { +        ReadControlValues(); +        ReadDataStorageValues(); +        ReadDebuggingValues(); +        ReadDisabledAddOnValues(); +        ReadServiceValues(); +        ReadUIValues(); +        ReadWebServiceValues(); +        ReadMiscellaneousValues(); +    }      ReadCoreValues(); +    ReadCpuValues();      ReadRendererValues();      ReadAudioValues(); -    ReadDataStorageValues();      ReadSystemValues(); -    ReadMiscellaneousValues(); -    ReadDebuggingValues(); -    ReadWebServiceValues(); -    ReadServiceValues(); -    ReadDisabledAddOnValues(); -    ReadUIValues();  } -void Config::SavePlayerValues() { -    for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { -        const auto& player = Settings::values.players[p]; +void Config::SavePlayerValue(std::size_t player_index) { +    const QString player_prefix = [this, player_index] { +        if (type == ConfigType::InputProfile) { +            return QString{}; +        } else { +            return QStringLiteral("player_%1_").arg(player_index); +        } +    }(); + +    const auto& player = Settings::values.players.GetValue()[player_index]; -        WriteSetting(QStringLiteral("player_%1_connected").arg(p), player.connected, false); -        WriteSetting(QStringLiteral("player_%1_type").arg(p), static_cast<u8>(player.type), -                     static_cast<u8>(Settings::ControllerType::DualJoycon)); +    WriteSetting(QStringLiteral("%1type").arg(player_prefix), +                 static_cast<u8>(player.controller_type), +                 static_cast<u8>(Settings::ControllerType::ProController)); -        WriteSetting(QStringLiteral("player_%1_body_color_left").arg(p), player.body_color_left, +    if (!player_prefix.isEmpty()) { +        WriteSetting(QStringLiteral("%1connected").arg(player_prefix), player.connected, false); +        WriteSetting(QStringLiteral("%1vibration_enabled").arg(player_prefix), +                     player.vibration_enabled, true); +        WriteSetting(QStringLiteral("%1vibration_strength").arg(player_prefix), +                     player.vibration_strength, 100); +        WriteSetting(QStringLiteral("%1body_color_left").arg(player_prefix), player.body_color_left,                       Settings::JOYCON_BODY_NEON_BLUE); -        WriteSetting(QStringLiteral("player_%1_body_color_right").arg(p), player.body_color_right, -                     Settings::JOYCON_BODY_NEON_RED); -        WriteSetting(QStringLiteral("player_%1_button_color_left").arg(p), player.button_color_left, -                     Settings::JOYCON_BUTTONS_NEON_BLUE); -        WriteSetting(QStringLiteral("player_%1_button_color_right").arg(p), +        WriteSetting(QStringLiteral("%1body_color_right").arg(player_prefix), +                     player.body_color_right, Settings::JOYCON_BODY_NEON_RED); +        WriteSetting(QStringLiteral("%1button_color_left").arg(player_prefix), +                     player.button_color_left, Settings::JOYCON_BUTTONS_NEON_BLUE); +        WriteSetting(QStringLiteral("%1button_color_right").arg(player_prefix),                       player.button_color_right, Settings::JOYCON_BUTTONS_NEON_RED); +    } -        for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { -            const std::string default_param = -                InputCommon::GenerateKeyboardParam(default_buttons[i]); -            WriteSetting(QStringLiteral("player_%1_").arg(p) + -                             QString::fromStdString(Settings::NativeButton::mapping[i]), -                         QString::fromStdString(player.buttons[i]), -                         QString::fromStdString(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_analogs[i][4], 0.5f); -            WriteSetting(QStringLiteral("player_%1_").arg(p) + -                             QString::fromStdString(Settings::NativeAnalog::mapping[i]), -                         QString::fromStdString(player.analogs[i]), -                         QString::fromStdString(default_param)); -        } +    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { +        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); +        WriteSetting(QStringLiteral("%1").arg(player_prefix) + +                         QString::fromStdString(Settings::NativeButton::mapping[i]), +                     QString::fromStdString(player.buttons[i]), +                     QString::fromStdString(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); +        WriteSetting(QStringLiteral("%1").arg(player_prefix) + +                         QString::fromStdString(Settings::NativeAnalog::mapping[i]), +                     QString::fromStdString(player.analogs[i]), +                     QString::fromStdString(default_param)); +    } +    for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) { +        WriteSetting(QStringLiteral("%1").arg(player_prefix) + +                         QString::fromStdString(Settings::NativeVibration::mapping[i]), +                     QString::fromStdString(player.vibrations[i]), QString{}); +    } +    for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { +        const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); +        WriteSetting(QStringLiteral("%1").arg(player_prefix) + +                         QString::fromStdString(Settings::NativeMotion::mapping[i]), +                     QString::fromStdString(player.motions[i]), +                     QString::fromStdString(default_param));      }  } @@ -857,7 +1063,7 @@ void Config::SaveDebugValues() {      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_analogs[i][4], 0.5f); +            default_analogs[i][3], default_stick_mod[i], 0.5f);          WriteSetting(QStringLiteral("debug_pad_") +                           QString::fromStdString(Settings::NativeAnalog::mapping[i]),                       QString::fromStdString(Settings::values.debug_pad_analogs[i]), @@ -891,31 +1097,74 @@ void Config::SaveTouchscreenValues() {      WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15);  } +void Config::SaveMotionTouchValues() { +    WriteSetting(QStringLiteral("motion_device"), +                 QString::fromStdString(Settings::values.motion_device), +                 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); +    WriteSetting(QStringLiteral("touch_device"), +                 QString::fromStdString(Settings::values.touch_device), +                 QStringLiteral("engine:emu_window")); +    WriteSetting(QStringLiteral("use_touch_from_button"), Settings::values.use_touch_from_button, +                 false); +    WriteSetting(QStringLiteral("touch_from_button_map"), +                 Settings::values.touch_from_button_map_index, 0); +    WriteSetting(QStringLiteral("udp_input_address"), +                 QString::fromStdString(Settings::values.udp_input_address), +                 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)); +    WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port, +                 InputCommon::CemuhookUDP::DEFAULT_PORT); +    WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0); + +    qt_config->beginWriteArray(QStringLiteral("touch_from_button_maps")); +    for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) { +        qt_config->setArrayIndex(static_cast<int>(p)); +        WriteSetting(QStringLiteral("name"), +                     QString::fromStdString(Settings::values.touch_from_button_maps[p].name), +                     QStringLiteral("default")); +        qt_config->beginWriteArray(QStringLiteral("entries")); +        for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size(); +             ++q) { +            qt_config->setArrayIndex(static_cast<int>(q)); +            WriteSetting( +                QStringLiteral("bind"), +                QString::fromStdString(Settings::values.touch_from_button_maps[p].buttons[q])); +        } +        qt_config->endArray(); +    } +    qt_config->endArray(); +} +  void Config::SaveValues() { -    SaveControlValues(); +    if (global) { +        SaveControlValues(); +        SaveDataStorageValues(); +        SaveDebuggingValues(); +        SaveDisabledAddOnValues(); +        SaveServiceValues(); +        SaveUIValues(); +        SaveWebServiceValues(); +        SaveMiscellaneousValues(); +    }      SaveCoreValues(); +    SaveCpuValues();      SaveRendererValues();      SaveAudioValues(); -    SaveDataStorageValues();      SaveSystemValues(); -    SaveMiscellaneousValues(); -    SaveDebuggingValues(); -    SaveWebServiceValues(); -    SaveServiceValues(); -    SaveDisabledAddOnValues(); -    SaveUIValues();  }  void Config::SaveAudioValues() {      qt_config->beginGroup(QStringLiteral("Audio")); -    WriteSetting(QStringLiteral("output_engine"), QString::fromStdString(Settings::values.sink_id), -                 QStringLiteral("auto")); -    WriteSetting(QStringLiteral("enable_audio_stretching"), -                 Settings::values.enable_audio_stretching, true); -    WriteSetting(QStringLiteral("output_device"), -                 QString::fromStdString(Settings::values.audio_device_id), QStringLiteral("auto")); -    WriteSetting(QStringLiteral("volume"), Settings::values.volume, 1.0f); +    if (global) { +        WriteSetting(QStringLiteral("output_engine"), +                     QString::fromStdString(Settings::values.sink_id), QStringLiteral("auto")); +        WriteSetting(QStringLiteral("output_device"), +                     QString::fromStdString(Settings::values.audio_device_id), +                     QStringLiteral("auto")); +    } +    WriteSettingGlobal(QStringLiteral("enable_audio_stretching"), +                       Settings::values.enable_audio_stretching, true); +    WriteSettingGlobal(QStringLiteral("volume"), Settings::values.volume, 1.0f);      qt_config->endGroup();  } @@ -923,21 +1172,27 @@ void Config::SaveAudioValues() {  void Config::SaveControlValues() {      qt_config->beginGroup(QStringLiteral("Controls")); -    SavePlayerValues(); +    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { +        SavePlayerValue(p); +    }      SaveDebugValues();      SaveMouseValues();      SaveTouchscreenValues(); - +    SaveMotionTouchValues(); + +    WriteSettingGlobal(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false); +    WriteSettingGlobal(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, +                       true); +    WriteSettingGlobal(QStringLiteral("enable_accurate_vibrations"), +                       Settings::values.enable_accurate_vibrations, false); +    WriteSettingGlobal(QStringLiteral("motion_enabled"), Settings::values.motion_enabled, true);      WriteSetting(QStringLiteral("motion_device"),                   QString::fromStdString(Settings::values.motion_device),                   QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); +    WriteSetting(QStringLiteral("touch_device"), +                 QString::fromStdString(Settings::values.touch_device), +                 QStringLiteral("engine:emu_window"));      WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false); -    WriteSetting(QStringLiteral("udp_input_address"), -                 QString::fromStdString(Settings::values.udp_input_address), -                 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)); -    WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port, -                 InputCommon::CemuhookUDP::DEFAULT_PORT); -    WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);      qt_config->endGroup();  } @@ -945,7 +1200,7 @@ void Config::SaveControlValues() {  void Config::SaveCoreValues() {      qt_config->beginGroup(QStringLiteral("Core")); -    WriteSetting(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, false); +    WriteSettingGlobal(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, true);      qt_config->endGroup();  } @@ -955,37 +1210,26 @@ void Config::SaveDataStorageValues() {      WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true);      WriteSetting(QStringLiteral("nand_directory"), -                 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)), -                 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); +                 QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir)), +                 QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir)));      WriteSetting(QStringLiteral("sdmc_directory"), -                 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)), -                 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); +                 QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir)), +                 QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir)));      WriteSetting(QStringLiteral("load_directory"), -                 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)), -                 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir))); +                 QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir)), +                 QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir)));      WriteSetting(QStringLiteral("dump_directory"), -                 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)), -                 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir))); +                 QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)), +                 QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)));      WriteSetting(QStringLiteral("cache_directory"), -                 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)), -                 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir))); +                 QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir)), +                 QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir)));      WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false);      WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game,                   false);      WriteSetting(QStringLiteral("gamecard_path"), -                 QString::fromStdString(Settings::values.gamecard_path), QStringLiteral("")); -    WriteSetting(QStringLiteral("nand_total_size"), -                 QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_total_size)), -                 QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDTotalSize::S29_1GB))); -    WriteSetting(QStringLiteral("nand_user_size"), -                 QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_user_size)), -                 QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDUserSize::S26GB))); -    WriteSetting(QStringLiteral("nand_system_size"), -                 QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_system_size)), -                 QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDSystemSize::S2_5GB))); -    WriteSetting(QStringLiteral("sdmc_size"), -                 QVariant::fromValue<u64>(static_cast<u64>(Settings::values.sdmc_size)), -                 QVariant::fromValue<u64>(static_cast<u64>(Settings::SDMCSize::S16GB))); +                 QString::fromStdString(Settings::values.gamecard_path), QString{}); +      qt_config->endGroup();  } @@ -997,10 +1241,11 @@ void Config::SaveDebuggingValues() {      WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false);      WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689);      WriteSetting(QStringLiteral("program_args"), -                 QString::fromStdString(Settings::values.program_args), QStringLiteral("")); +                 QString::fromStdString(Settings::values.program_args), QString{});      WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false);      WriteSetting(QStringLiteral("dump_nso"), Settings::values.dump_nso, false);      WriteSetting(QStringLiteral("quest_flag"), Settings::values.quest_flag, false); +    WriteSetting(QStringLiteral("disable_macro_jit"), Settings::values.disable_macro_jit, false);      qt_config->endGroup();  } @@ -1023,8 +1268,7 @@ void Config::SaveDisabledAddOnValues() {          qt_config->beginWriteArray(QStringLiteral("disabled"));          for (std::size_t j = 0; j < elem.second.size(); ++j) {              qt_config->setArrayIndex(static_cast<int>(j)); -            WriteSetting(QStringLiteral("d"), QString::fromStdString(elem.second[j]), -                         QStringLiteral("")); +            WriteSetting(QStringLiteral("d"), QString::fromStdString(elem.second[j]), QString{});          }          qt_config->endArray();          ++i; @@ -1048,7 +1292,6 @@ void Config::SavePathValues() {      WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path);      WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path); -    WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path);      qt_config->beginWriteArray(QStringLiteral("gamedirs"));      for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {          qt_config->setArrayIndex(i); @@ -1059,6 +1302,38 @@ void Config::SavePathValues() {      }      qt_config->endArray();      WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); +    WriteSetting(QStringLiteral("language"), UISettings::values.language, QString{}); + +    qt_config->endGroup(); +} + +void Config::SaveCpuValues() { +    qt_config->beginGroup(QStringLiteral("Cpu")); + +    if (global) { +        WriteSetting(QStringLiteral("cpu_accuracy"), +                     static_cast<int>(Settings::values.cpu_accuracy), 0); + +        WriteSetting(QStringLiteral("cpuopt_page_tables"), Settings::values.cpuopt_page_tables, +                     true); +        WriteSetting(QStringLiteral("cpuopt_block_linking"), Settings::values.cpuopt_block_linking, +                     true); +        WriteSetting(QStringLiteral("cpuopt_return_stack_buffer"), +                     Settings::values.cpuopt_return_stack_buffer, true); +        WriteSetting(QStringLiteral("cpuopt_fast_dispatcher"), +                     Settings::values.cpuopt_fast_dispatcher, true); +        WriteSetting(QStringLiteral("cpuopt_context_elimination"), +                     Settings::values.cpuopt_context_elimination, true); +        WriteSetting(QStringLiteral("cpuopt_const_prop"), Settings::values.cpuopt_const_prop, true); +        WriteSetting(QStringLiteral("cpuopt_misc_ir"), Settings::values.cpuopt_misc_ir, true); +        WriteSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), +                     Settings::values.cpuopt_reduce_misalign_checks, true); + +        WriteSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"), +                     Settings::values.cpuopt_unsafe_unfuse_fma, true); +        WriteSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), +                     Settings::values.cpuopt_unsafe_reduce_fp_error, true); +    }      qt_config->endGroup();  } @@ -1066,28 +1341,46 @@ void Config::SavePathValues() {  void Config::SaveRendererValues() {      qt_config->beginGroup(QStringLiteral("Renderer")); -    WriteSetting(QStringLiteral("backend"), static_cast<int>(Settings::values.renderer_backend), 0); +    WriteSettingGlobal(QStringLiteral("backend"), +                       static_cast<int>(Settings::values.renderer_backend.GetValue(global)), +                       Settings::values.renderer_backend.UsingGlobal(), 0);      WriteSetting(QStringLiteral("debug"), Settings::values.renderer_debug, false); -    WriteSetting(QStringLiteral("vulkan_device"), Settings::values.vulkan_device, 0); -    WriteSetting(QStringLiteral("resolution_factor"), -                 static_cast<double>(Settings::values.resolution_factor), 1.0); -    WriteSetting(QStringLiteral("aspect_ratio"), Settings::values.aspect_ratio, 0); -    WriteSetting(QStringLiteral("max_anisotropy"), Settings::values.max_anisotropy, 0); -    WriteSetting(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true); -    WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100); -    WriteSetting(QStringLiteral("use_disk_shader_cache"), Settings::values.use_disk_shader_cache, -                 true); -    WriteSetting(QStringLiteral("use_accurate_gpu_emulation"), -                 Settings::values.use_accurate_gpu_emulation, false); -    WriteSetting(QStringLiteral("use_asynchronous_gpu_emulation"), -                 Settings::values.use_asynchronous_gpu_emulation, false); -    WriteSetting(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); -    WriteSetting(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode, false); - +    WriteSettingGlobal(QStringLiteral("vulkan_device"), Settings::values.vulkan_device, 0); +    WriteSettingGlobal(QStringLiteral("aspect_ratio"), Settings::values.aspect_ratio, 0); +    WriteSettingGlobal(QStringLiteral("max_anisotropy"), Settings::values.max_anisotropy, 0); +    WriteSettingGlobal(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true); +    WriteSettingGlobal(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100); +    WriteSettingGlobal(QStringLiteral("use_disk_shader_cache"), +                       Settings::values.use_disk_shader_cache, true); +    WriteSettingGlobal(QStringLiteral("gpu_accuracy"), +                       static_cast<int>(Settings::values.gpu_accuracy.GetValue(global)), +                       Settings::values.gpu_accuracy.UsingGlobal(), 0); +    WriteSettingGlobal(QStringLiteral("use_asynchronous_gpu_emulation"), +                       Settings::values.use_asynchronous_gpu_emulation, true); +    WriteSettingGlobal(QStringLiteral("use_nvdec_emulation"), Settings::values.use_nvdec_emulation, +                       true); +    WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); +    WriteSettingGlobal(QStringLiteral("use_assembly_shaders"), +                       Settings::values.use_assembly_shaders, true); +    WriteSettingGlobal(QStringLiteral("use_asynchronous_shaders"), +                       Settings::values.use_asynchronous_shaders, false); +    WriteSettingGlobal(QStringLiteral("use_fast_gpu_time"), Settings::values.use_fast_gpu_time, +                       true);      // Cast to double because Qt's written float values are not human-readable -    WriteSetting(QStringLiteral("bg_red"), static_cast<double>(Settings::values.bg_red), 0.0); -    WriteSetting(QStringLiteral("bg_green"), static_cast<double>(Settings::values.bg_green), 0.0); -    WriteSetting(QStringLiteral("bg_blue"), static_cast<double>(Settings::values.bg_blue), 0.0); +    WriteSettingGlobal(QStringLiteral("bg_red"), Settings::values.bg_red, 0.0); +    WriteSettingGlobal(QStringLiteral("bg_green"), Settings::values.bg_green, 0.0); +    WriteSettingGlobal(QStringLiteral("bg_blue"), Settings::values.bg_blue, 0.0); + +    qt_config->endGroup(); +} + +void Config::SaveScreenshotValues() { +    qt_config->beginGroup(QStringLiteral("Screenshots")); + +    WriteSetting(QStringLiteral("enable_screenshot_save_as"), +                 UISettings::values.enable_screenshot_save_as); +    WriteSetting(QStringLiteral("screenshot_path"), +                 QString::fromStdString(FS::GetUserPath(FS::UserPath::ScreenshotsDir)));      qt_config->endGroup();  } @@ -1115,22 +1408,28 @@ void Config::SaveShortcutValues() {  void Config::SaveSystemValues() {      qt_config->beginGroup(QStringLiteral("System")); -    WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);      WriteSetting(QStringLiteral("current_user"), Settings::values.current_user, 0); -    WriteSetting(QStringLiteral("language_index"), Settings::values.language_index, 1); -    WriteSetting(QStringLiteral("region_index"), Settings::values.region_index, 1); - -    WriteSetting(QStringLiteral("rng_seed_enabled"), Settings::values.rng_seed.has_value(), false); -    WriteSetting(QStringLiteral("rng_seed"), Settings::values.rng_seed.value_or(0), 0); - -    WriteSetting(QStringLiteral("custom_rtc_enabled"), Settings::values.custom_rtc.has_value(), -                 false); -    WriteSetting(QStringLiteral("custom_rtc"), -                 QVariant::fromValue<long long>( -                     Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()), -                 0); - -    WriteSetting(QStringLiteral("sound_index"), Settings::values.sound_index, 1); +    WriteSettingGlobal(QStringLiteral("language_index"), Settings::values.language_index, 1); +    WriteSettingGlobal(QStringLiteral("region_index"), Settings::values.region_index, 1); +    WriteSettingGlobal(QStringLiteral("time_zone_index"), Settings::values.time_zone_index, 0); + +    WriteSettingGlobal(QStringLiteral("rng_seed_enabled"), +                       Settings::values.rng_seed.GetValue(global).has_value(), +                       Settings::values.rng_seed.UsingGlobal(), false); +    WriteSettingGlobal(QStringLiteral("rng_seed"), +                       Settings::values.rng_seed.GetValue(global).value_or(0), +                       Settings::values.rng_seed.UsingGlobal(), 0); + +    WriteSettingGlobal(QStringLiteral("custom_rtc_enabled"), +                       Settings::values.custom_rtc.GetValue(global).has_value(), +                       Settings::values.custom_rtc.UsingGlobal(), false); +    WriteSettingGlobal( +        QStringLiteral("custom_rtc"), +        QVariant::fromValue<long long>( +            Settings::values.custom_rtc.GetValue(global).value_or(std::chrono::seconds{}).count()), +        Settings::values.custom_rtc.UsingGlobal(), 0); + +    WriteSettingGlobal(QStringLiteral("sound_index"), Settings::values.sound_index, 1);      qt_config->endGroup();  } @@ -1142,14 +1441,13 @@ void Config::SaveUIValues() {                   QString::fromUtf8(UISettings::themes[0].second));      WriteSetting(QStringLiteral("enable_discord_presence"),                   UISettings::values.enable_discord_presence, true); -    WriteSetting(QStringLiteral("screenshot_resolution_factor"), -                 UISettings::values.screenshot_resolution_factor, 0);      WriteSetting(QStringLiteral("select_user_on_boot"), UISettings::values.select_user_on_boot,                   false);      SaveUIGamelistValues();      SaveUILayoutValues();      SavePathValues(); +    SaveScreenshotValues();      SaveShortcutValues();      WriteSetting(QStringLiteral("singleWindowMode"), UISettings::values.single_window_mode, true); @@ -1161,9 +1459,9 @@ void Config::SaveUIValues() {      WriteSetting(QStringLiteral("firstStart"), UISettings::values.first_start, true);      WriteSetting(QStringLiteral("calloutFlags"), UISettings::values.callout_flags, 0);      WriteSetting(QStringLiteral("showConsole"), UISettings::values.show_console, false); -    WriteSetting(QStringLiteral("profileIndex"), UISettings::values.profile_index, 0);      WriteSetting(QStringLiteral("pauseWhenInBackground"),                   UISettings::values.pause_when_in_background, false); +    WriteSetting(QStringLiteral("hideInactiveMouse"), UISettings::values.hide_mouse, false);      qt_config->endGroup();  } @@ -1223,6 +1521,34 @@ QVariant Config::ReadSetting(const QString& name, const QVariant& default_value)      return result;  } +template <typename Type> +void Config::ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name) { +    const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool(); +    setting.SetGlobal(use_global); +    if (global || !use_global) { +        setting.SetValue(ReadSetting(name).value<Type>()); +    } +} + +template <typename Type> +void Config::ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name, +                               const QVariant& default_value) { +    const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool(); +    setting.SetGlobal(use_global); +    if (global || !use_global) { +        setting.SetValue(ReadSetting(name, default_value).value<Type>()); +    } +} + +template <typename Type> +void Config::ReadSettingGlobal(Type& setting, const QString& name, +                               const QVariant& default_value) const { +    const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool(); +    if (global || !use_global) { +        setting = ReadSetting(name, default_value).value<Type>(); +    } +} +  void Config::WriteSetting(const QString& name, const QVariant& value) {      qt_config->setValue(name, value);  } @@ -1233,13 +1559,65 @@ void Config::WriteSetting(const QString& name, const QVariant& value,      qt_config->setValue(name, value);  } +template <typename Type> +void Config::WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting) { +    if (!global) { +        qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal()); +    } +    if (global || !setting.UsingGlobal()) { +        qt_config->setValue(name, setting.GetValue(global)); +    } +} + +template <typename Type> +void Config::WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting, +                                const QVariant& default_value) { +    if (!global) { +        qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal()); +    } +    if (global || !setting.UsingGlobal()) { +        qt_config->setValue(name + QStringLiteral("/default"), +                            setting.GetValue(global) == default_value.value<Type>()); +        qt_config->setValue(name, setting.GetValue(global)); +    } +} + +void Config::WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global, +                                const QVariant& default_value) { +    if (!global) { +        qt_config->setValue(name + QStringLiteral("/use_global"), use_global); +    } +    if (global || !use_global) { +        qt_config->setValue(name + QStringLiteral("/default"), value == default_value); +        qt_config->setValue(name, value); +    } +} +  void Config::Reload() {      ReadValues(); +    Settings::Sanitize();      // To apply default value changes      SaveValues();      Settings::Apply();  }  void Config::Save() { +    Settings::Sanitize();      SaveValues();  } + +void Config::ReadControlPlayerValue(std::size_t player_index) { +    qt_config->beginGroup(QStringLiteral("Controls")); +    ReadPlayerValue(player_index); +    qt_config->endGroup(); +} + +void Config::SaveControlPlayerValue(std::size_t player_index) { +    qt_config->beginGroup(QStringLiteral("Controls")); +    SavePlayerValue(player_index); +    qt_config->endGroup(); +} + +const std::string& Config::GetConfigFilePath() const { +    return qt_config_loc; +} diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index ba6888004..8a600e19d 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -7,34 +7,53 @@  #include <array>  #include <memory>  #include <string> +#include <QMetaType>  #include <QVariant>  #include "core/settings.h" +#include "yuzu/uisettings.h"  class QSettings;  class Config {  public: -    Config(); +    enum class ConfigType { +        GlobalConfig, +        PerGameConfig, +        InputProfile, +    }; + +    explicit Config(const std::string& config_name = "qt-config", +                    ConfigType config_type = ConfigType::GlobalConfig);      ~Config();      void Reload();      void Save(); +    void ReadControlPlayerValue(std::size_t player_index); +    void SaveControlPlayerValue(std::size_t player_index); + +    const std::string& GetConfigFilePath() const; +      static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; -    static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs; +    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, Settings::NativeMouseButton::NumMouseButtons>          default_mouse_buttons;      static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;      static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods; +    static const std::array<UISettings::Shortcut, 16> default_hotkeys;  private: +    void Initialize(const std::string& config_name); +      void ReadValues(); -    void ReadPlayerValues(); +    void ReadPlayerValue(std::size_t player_index);      void ReadDebugValues();      void ReadKeyboardValues();      void ReadMouseValues();      void ReadTouchscreenValues(); -    void ApplyDefaultProfileIfInputInvalid(); +    void ReadMotionTouchValues();      // Read functions bases off the respective config section names.      void ReadAudioValues(); @@ -46,7 +65,9 @@ private:      void ReadDisabledAddOnValues();      void ReadMiscellaneousValues();      void ReadPathValues(); +    void ReadCpuValues();      void ReadRendererValues(); +    void ReadScreenshotValues();      void ReadShortcutValues();      void ReadSystemValues();      void ReadUIValues(); @@ -55,10 +76,11 @@ private:      void ReadWebServiceValues();      void SaveValues(); -    void SavePlayerValues(); +    void SavePlayerValue(std::size_t player_index);      void SaveDebugValues();      void SaveMouseValues();      void SaveTouchscreenValues(); +    void SaveMotionTouchValues();      // Save functions based off the respective config section names.      void SaveAudioValues(); @@ -70,7 +92,9 @@ private:      void SaveDisabledAddOnValues();      void SaveMiscellaneousValues();      void SavePathValues(); +    void SaveCpuValues();      void SaveRendererValues(); +    void SaveScreenshotValues();      void SaveShortcutValues();      void SaveSystemValues();      void SaveUIValues(); @@ -80,9 +104,33 @@ private:      QVariant ReadSetting(const QString& name) const;      QVariant ReadSetting(const QString& name, const QVariant& default_value) const; +    // Templated ReadSettingGlobal functions will also look for the use_global setting and set +    // both the value and the global state properly +    template <typename Type> +    void ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name); +    template <typename Type> +    void ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name, +                           const QVariant& default_value); +    template <typename Type> +    void ReadSettingGlobal(Type& setting, const QString& name, const QVariant& default_value) const; +    // Templated WriteSettingGlobal functions will also write the global state if needed and will +    // skip writing the actual setting if it defers to the global value      void WriteSetting(const QString& name, const QVariant& value);      void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value); +    template <typename Type> +    void WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting); +    template <typename Type> +    void WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting, +                            const QVariant& default_value); +    void WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global, +                            const QVariant& default_value); +    ConfigType type;      std::unique_ptr<QSettings> qt_config;      std::string qt_config_loc; +    bool global;  }; + +// These metatype declarations cannot be in core/settings.h because core is devoid of QT +Q_DECLARE_METATYPE(Settings::RendererBackend); +Q_DECLARE_METATYPE(Settings::GPUAccuracy); diff --git a/src/yuzu/configuration/configuration_shared.cpp b/src/yuzu/configuration/configuration_shared.cpp new file mode 100644 index 000000000..18482795c --- /dev/null +++ b/src/yuzu/configuration/configuration_shared.cpp @@ -0,0 +1,134 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QCheckBox> +#include <QComboBox> +#include <QObject> +#include <QString> +#include "core/settings.h" +#include "yuzu/configuration/configuration_shared.h" +#include "yuzu/configuration/configure_per_game.h" + +void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<bool>* setting, +                                              const QCheckBox* checkbox, +                                              const CheckState& tracker) { +    if (tracker == CheckState::Global) { +        setting->SetGlobal(true); +    } else { +        setting->SetGlobal(false); +        setting->SetValue(checkbox->checkState()); +    } +} + +void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<int>* setting, +                                              const QComboBox* combobox) { +    if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { +        setting->SetGlobal(true); +    } else { +        setting->SetGlobal(false); +        setting->SetValue(combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET); +    } +} + +void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting, +                                              const QComboBox* combobox) { +    if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { +        setting->SetGlobal(true); +    } else { +        setting->SetGlobal(false); +        setting->SetValue(static_cast<Settings::RendererBackend>( +            combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET)); +    } +} + +void ConfigurationShared::SetPerGameSetting(QCheckBox* checkbox, +                                            const Settings::Setting<bool>* setting) { +    if (setting->UsingGlobal()) { +        checkbox->setCheckState(Qt::PartiallyChecked); +    } else { +        checkbox->setCheckState(setting->GetValue() ? Qt::Checked : Qt::Unchecked); +    } +} + +void ConfigurationShared::SetPerGameSetting(QComboBox* combobox, +                                            const Settings::Setting<int>* setting) { +    combobox->setCurrentIndex(setting->UsingGlobal() +                                  ? ConfigurationShared::USE_GLOBAL_INDEX +                                  : setting->GetValue() + ConfigurationShared::USE_GLOBAL_OFFSET); +} + +void ConfigurationShared::SetPerGameSetting( +    QComboBox* combobox, const Settings::Setting<Settings::RendererBackend>* setting) { +    combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX +                                                     : static_cast<int>(setting->GetValue()) + +                                                           ConfigurationShared::USE_GLOBAL_OFFSET); +} + +void ConfigurationShared::SetPerGameSetting( +    QComboBox* combobox, const Settings::Setting<Settings::GPUAccuracy>* setting) { +    combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX +                                                     : static_cast<int>(setting->GetValue()) + +                                                           ConfigurationShared::USE_GLOBAL_OFFSET); +} + +void ConfigurationShared::SetHighlight(QWidget* widget, bool highlighted) { +    if (highlighted) { +        widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,203,255,0.5) }") +                                  .arg(widget->objectName())); +    } else { +        widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,0,0,0) }") +                                  .arg(widget->objectName())); +    } +    widget->show(); +} + +void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, +                                             const Settings::Setting<bool>& setting, +                                             CheckState& tracker) { +    if (setting.UsingGlobal()) { +        tracker = CheckState::Global; +    } else { +        tracker = (setting.GetValue() == setting.GetValue(true)) ? CheckState::On : CheckState::Off; +    } +    SetHighlight(checkbox, tracker != CheckState::Global); +    QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, setting, &tracker] { +        tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) % +                                          static_cast<int>(CheckState::Count)); +        if (tracker == CheckState::Global) { +            checkbox->setChecked(setting.GetValue(true)); +        } +        SetHighlight(checkbox, tracker != CheckState::Global); +    }); +} + +void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, bool global, bool state, +                                             bool global_state, CheckState& tracker) { +    if (global) { +        tracker = CheckState::Global; +    } else { +        tracker = (state == global_state) ? CheckState::On : CheckState::Off; +    } +    SetHighlight(checkbox, tracker != CheckState::Global); +    QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, global_state, &tracker] { +        tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) % +                                          static_cast<int>(CheckState::Count)); +        if (tracker == CheckState::Global) { +            checkbox->setChecked(global_state); +        } +        SetHighlight(checkbox, tracker != CheckState::Global); +    }); +} + +void ConfigurationShared::SetColoredComboBox(QComboBox* combobox, QWidget* target, int global) { +    InsertGlobalItem(combobox, global); +    QObject::connect(combobox, qOverload<int>(&QComboBox::activated), target, +                     [target](int index) { SetHighlight(target, index != 0); }); +} + +void ConfigurationShared::InsertGlobalItem(QComboBox* combobox, int global_index) { +    const QString use_global_text = +        ConfigurePerGame::tr("Use global configuration (%1)").arg(combobox->itemText(global_index)); +    combobox->insertItem(ConfigurationShared::USE_GLOBAL_INDEX, use_global_text); +    combobox->insertSeparator(ConfigurationShared::USE_GLOBAL_SEPARATOR_INDEX); +} diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h new file mode 100644 index 000000000..312b9e549 --- /dev/null +++ b/src/yuzu/configuration/configuration_shared.h @@ -0,0 +1,51 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QCheckBox> +#include <QComboBox> +#include <QString> +#include "core/settings.h" + +namespace ConfigurationShared { + +constexpr int USE_GLOBAL_INDEX = 0; +constexpr int USE_GLOBAL_SEPARATOR_INDEX = 1; +constexpr int USE_GLOBAL_OFFSET = 2; + +enum class CheckState { +    Off, +    On, +    Global, +    Count, +}; + +// Global-aware apply and set functions + +void ApplyPerGameSetting(Settings::Setting<bool>* setting, const QCheckBox* checkbox, +                         const CheckState& tracker); +void ApplyPerGameSetting(Settings::Setting<int>* setting, const QComboBox* combobox); +void ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting, +                         const QComboBox* combobox); +void ApplyPerGameSetting(Settings::Setting<Settings::GPUAccuracy>* setting, +                         const QComboBox* combobox); + +void SetPerGameSetting(QCheckBox* checkbox, const Settings::Setting<bool>* setting); +void SetPerGameSetting(QComboBox* combobox, const Settings::Setting<int>* setting); +void SetPerGameSetting(QComboBox* combobox, +                       const Settings::Setting<Settings::RendererBackend>* setting); +void SetPerGameSetting(QComboBox* combobox, +                       const Settings::Setting<Settings::GPUAccuracy>* setting); + +void SetHighlight(QWidget* widget, bool highlighted); +void SetColoredTristate(QCheckBox* checkbox, const Settings::Setting<bool>& setting, +                        CheckState& tracker); +void SetColoredTristate(QCheckBox* checkbox, bool global, bool state, bool global_state, +                        CheckState& tracker); +void SetColoredComboBox(QComboBox* combobox, QWidget* target, int global); + +void InsertGlobalItem(QComboBox* combobox, int global_index); + +} // namespace ConfigurationShared diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index 9aec1bd09..f92c3aff3 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -6,7 +6,7 @@     <rect>      <x>0</x>      <y>0</y> -    <width>382</width> +    <width>650</width>      <height>650</height>     </rect>    </property> @@ -26,13 +26,13 @@        <widget class="QListWidget" name="selectorList">         <property name="minimumSize">          <size> -         <width>150</width> +         <width>120</width>           <height>0</height>          </size>         </property>         <property name="maximumSize">          <size> -         <width>150</width> +         <width>120</width>           <height>16777215</height>          </size>         </property> @@ -44,66 +44,121 @@          <number>0</number>         </property>         <widget class="ConfigureGeneral" name="generalTab"> +        <property name="accessibleName"> +         <string>General</string> +        </property>          <attribute name="title">           <string>General</string>          </attribute>         </widget>         <widget class="ConfigureUi" name="uiTab"> +        <property name="accessibleName"> +         <string>UI</string> +        </property>          <attribute name="title">           <string>Game List</string>          </attribute>         </widget>         <widget class="ConfigureSystem" name="systemTab"> +        <property name="accessibleName"> +         <string>System</string> +        </property>          <attribute name="title">           <string>System</string>          </attribute>         </widget>         <widget class="ConfigureProfileManager" name="profileManagerTab"> +        <property name="accessibleName"> +         <string>Profiles</string> +        </property>          <attribute name="title">           <string>Profiles</string>          </attribute>         </widget>         <widget class="ConfigureFilesystem" name="filesystemTab"> +        <property name="accessibleName"> +         <string>Filesystem</string> +        </property>          <attribute name="title">           <string>Filesystem</string>          </attribute>         </widget> -       <widget class="ConfigureInputSimple" name="inputTab"> +       <widget class="ConfigureInput" name="inputTab"> +        <property name="accessibleName"> +         <string>Controls</string> +        </property>          <attribute name="title"> -         <string>Input</string> +         <string>Controls</string>          </attribute>         </widget>         <widget class="ConfigureHotkeys" name="hotkeysTab"> +        <property name="accessibleName"> +         <string>Hotkeys</string> +        </property>          <attribute name="title">           <string>Hotkeys</string>          </attribute>         </widget> +       <widget class="ConfigureCpu" name="cpuTab"> +        <property name="accessibleName"> +         <string>CPU</string> +        </property> +        <attribute name="title"> +         <string>CPU</string> +        </attribute> +       </widget> +       <widget class="ConfigureCpuDebug" name="cpuDebugTab"> +        <property name="accessibleName"> +         <string>Debug</string> +        </property> +        <attribute name="title"> +         <string>Debug</string> +        </attribute> +       </widget>         <widget class="ConfigureGraphics" name="graphicsTab"> +        <property name="accessibleName"> +         <string>Graphics</string> +        </property>          <attribute name="title">           <string>Graphics</string>          </attribute>         </widget>         <widget class="ConfigureGraphicsAdvanced" name="graphicsAdvancedTab"> +        <property name="accessibleName"> +         <string>Advanced</string> +        </property>          <attribute name="title">           <string>GraphicsAdvanced</string>          </attribute>         </widget>         <widget class="ConfigureAudio" name="audioTab"> +        <property name="accessibleName"> +         <string>Audio</string> +        </property>          <attribute name="title">           <string>Audio</string>          </attribute>         </widget>         <widget class="ConfigureDebug" name="debugTab"> +        <property name="accessibleName"> +         <string>Debug</string> +        </property>          <attribute name="title">           <string>Debug</string>          </attribute>         </widget>         <widget class="ConfigureWeb" name="webTab"> +        <property name="accessibleName"> +         <string>Web</string> +        </property>          <attribute name="title">           <string>Web</string>          </attribute>         </widget>         <widget class="ConfigureService" name="serviceTab"> +        <property name="accessibleName"> +         <string>Services</string> +        </property>          <attribute name="title">           <string>Services</string>          </attribute> @@ -159,6 +214,18 @@     <container>1</container>    </customwidget>    <customwidget> +   <class>ConfigureCpu</class> +   <extends>QWidget</extends> +   <header>configuration/configure_cpu.h</header> +   <container>1</container> +  </customwidget> +  <customwidget> +   <class>ConfigureCpuDebug</class> +   <extends>QWidget</extends> +   <header>configuration/configure_cpu_debug.h</header> +   <container>1</container> +  </customwidget> +  <customwidget>     <class>ConfigureGraphics</class>     <extends>QWidget</extends>     <header>configuration/configure_graphics.h</header> @@ -183,9 +250,9 @@     <container>1</container>    </customwidget>    <customwidget> -   <class>ConfigureInputSimple</class> +   <class>ConfigureInput</class>     <extends>QWidget</extends> -   <header>configuration/configure_input_simple.h</header> +   <header>configuration/configure_input.h</header>     <container>1</container>    </customwidget>    <customwidget> @@ -208,32 +275,12 @@     <signal>accepted()</signal>     <receiver>ConfigureDialog</receiver>     <slot>accept()</slot> -   <hints> -    <hint type="sourcelabel"> -     <x>220</x> -     <y>380</y> -    </hint> -    <hint type="destinationlabel"> -     <x>220</x> -     <y>200</y> -    </hint> -   </hints>    </connection>    <connection>     <sender>buttonBox</sender>     <signal>rejected()</signal>     <receiver>ConfigureDialog</receiver>     <slot>reject()</slot> -   <hints> -    <hint type="sourcelabel"> -     <x>220</x> -     <y>380</y> -    </hint> -    <hint type="destinationlabel"> -     <x>220</x> -     <y>200</y> -    </hint> -   </hints>    </connection>   </connections>  </ui> diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index f370c690f..db9518798 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -11,6 +11,7 @@  #include "core/core.h"  #include "core/settings.h"  #include "ui_configure_audio.h" +#include "yuzu/configuration/configuration_shared.h"  #include "yuzu/configuration/configure_audio.h"  ConfigureAudio::ConfigureAudio(QWidget* parent) @@ -24,6 +25,11 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)      connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,              &ConfigureAudio::UpdateAudioDevices); +    ui->volume_label->setVisible(Settings::IsConfiguringGlobal()); +    ui->volume_combo_box->setVisible(!Settings::IsConfiguringGlobal()); + +    SetupPerGameUI(); +      SetConfiguration();      const bool is_powered_on = Core::System::GetInstance().IsPoweredOn(); @@ -41,8 +47,21 @@ void ConfigureAudio::SetConfiguration() {      SetAudioDeviceFromDeviceID(); -    ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching); -    ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum()); +    ui->volume_slider->setValue(Settings::values.volume.GetValue() * ui->volume_slider->maximum()); + +    ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching.GetValue()); + +    if (!Settings::IsConfiguringGlobal()) { +        if (Settings::values.volume.UsingGlobal()) { +            ui->volume_combo_box->setCurrentIndex(0); +            ui->volume_slider->setEnabled(false); +        } else { +            ui->volume_combo_box->setCurrentIndex(1); +            ui->volume_slider->setEnabled(true); +        } +        ConfigurationShared::SetHighlight(ui->volume_layout, +                                          !Settings::values.volume.UsingGlobal()); +    }      SetVolumeIndicatorText(ui->volume_slider->sliderPosition());  } @@ -80,15 +99,37 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) {  }  void ConfigureAudio::ApplyConfiguration() { -    Settings::values.sink_id = -        ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) -            .toStdString(); -    Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked(); -    Settings::values.audio_device_id = -        ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) -            .toStdString(); -    Settings::values.volume = -        static_cast<float>(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum(); +    if (Settings::IsConfiguringGlobal()) { +        Settings::values.sink_id = +            ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) +                .toStdString(); +        Settings::values.audio_device_id = +            ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) +                .toStdString(); + +        // Guard if during game and set to game-specific value +        if (Settings::values.enable_audio_stretching.UsingGlobal()) { +            Settings::values.enable_audio_stretching.SetValue( +                ui->toggle_audio_stretching->isChecked()); +        } +        if (Settings::values.volume.UsingGlobal()) { +            Settings::values.volume.SetValue( +                static_cast<float>(ui->volume_slider->sliderPosition()) / +                ui->volume_slider->maximum()); +        } +    } else { +        ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_audio_stretching, +                                                 ui->toggle_audio_stretching, +                                                 enable_audio_stretching); +        if (ui->volume_combo_box->currentIndex() == 0) { +            Settings::values.volume.SetGlobal(true); +        } else { +            Settings::values.volume.SetGlobal(false); +            Settings::values.volume.SetValue( +                static_cast<float>(ui->volume_slider->sliderPosition()) / +                ui->volume_slider->maximum()); +        } +    }  }  void ConfigureAudio::changeEvent(QEvent* event) { @@ -122,3 +163,26 @@ void ConfigureAudio::RetranslateUI() {      ui->retranslateUi(this);      SetVolumeIndicatorText(ui->volume_slider->sliderPosition());  } + +void ConfigureAudio::SetupPerGameUI() { +    if (Settings::IsConfiguringGlobal()) { +        ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal()); +        ui->toggle_audio_stretching->setEnabled( +            Settings::values.enable_audio_stretching.UsingGlobal()); + +        return; +    } + +    ConfigurationShared::SetColoredTristate(ui->toggle_audio_stretching, +                                            Settings::values.enable_audio_stretching, +                                            enable_audio_stretching); +    connect(ui->volume_combo_box, qOverload<int>(&QComboBox::activated), this, [this](int index) { +        ui->volume_slider->setEnabled(index == 1); +        ConfigurationShared::SetHighlight(ui->volume_layout, index == 1); +    }); + +    ui->output_sink_combo_box->setVisible(false); +    ui->output_sink_label->setVisible(false); +    ui->audio_device_combo_box->setVisible(false); +    ui->audio_device_label->setVisible(false); +} diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h index ea83bd72d..9dbd3d93e 100644 --- a/src/yuzu/configuration/configure_audio.h +++ b/src/yuzu/configuration/configure_audio.h @@ -7,6 +7,10 @@  #include <memory>  #include <QWidget> +namespace ConfigurationShared { +enum class CheckState; +} +  namespace Ui {  class ConfigureAudio;  } @@ -34,5 +38,9 @@ private:      void SetAudioDeviceFromDeviceID();      void SetVolumeIndicatorText(int percentage); +    void SetupPerGameUI(); +      std::unique_ptr<Ui::ConfigureAudio> ui; + +    ConfigurationShared::CheckState enable_audio_stretching;  }; diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui index a098b9acc..9bd0cca96 100644 --- a/src/yuzu/configuration/configure_audio.ui +++ b/src/yuzu/configuration/configure_audio.ui @@ -6,8 +6,8 @@     <rect>      <x>0</x>      <y>0</y> -    <width>188</width> -    <height>246</height> +    <width>367</width> +    <height>368</height>     </rect>    </property>    <layout class="QVBoxLayout"> @@ -18,9 +18,9 @@       </property>       <layout class="QVBoxLayout">        <item> -       <layout class="QHBoxLayout"> +       <layout class="QHBoxLayout" name="_3">          <item> -         <widget class="QLabel" name="label_1"> +         <widget class="QLabel" name="output_sink_label">            <property name="text">             <string>Output Engine:</string>            </property> @@ -31,20 +31,20 @@          </item>         </layout>        </item> -       <item> -         <widget class="QCheckBox" name="toggle_audio_stretching"> -           <property name="toolTip"> -             <string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string> -           </property> -           <property name="text"> -             <string>Enable audio stretching</string> -           </property> -         </widget> -       </item>        <item> -       <layout class="QHBoxLayout"> +       <widget class="QCheckBox" name="toggle_audio_stretching"> +        <property name="toolTip"> +         <string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string> +        </property> +        <property name="text"> +         <string>Enable audio stretching</string> +        </property> +       </widget> +      </item> +      <item> +       <layout class="QHBoxLayout" name="_2">          <item> -         <widget class="QLabel" name="label_2"> +         <widget class="QLabel" name="audio_device_label">            <property name="text">             <string>Audio Device:</string>            </property> @@ -56,66 +56,91 @@         </layout>        </item>        <item> -       <layout class="QHBoxLayout" name="horizontalLayout_2"> -        <property name="topMargin"> -         <number>0</number> -        </property> -        <item> -         <widget class="QLabel" name="label_3"> -          <property name="text"> -           <string>Volume:</string> -          </property> -         </widget> -        </item> -        <item> -         <spacer name="horizontalSpacer"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>40</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -        <item> -         <widget class="QSlider" name="volume_slider"> -          <property name="sizePolicy"> -           <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> -            <horstretch>0</horstretch> -            <verstretch>0</verstretch> -           </sizepolicy> -          </property> -          <property name="maximum"> -           <number>100</number> -          </property> -          <property name="pageStep"> -           <number>10</number> -          </property> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -         </widget> -        </item> -        <item> -         <widget class="QLabel" name="volume_indicator"> -          <property name="minimumSize"> -           <size> -            <width>32</width> -            <height>0</height> -           </size> -          </property> -          <property name="text"> -           <string>0 %</string> -          </property> -          <property name="alignment"> -           <set>Qt::AlignCenter</set> -          </property> -         </widget> -        </item> -       </layout> +       <widget class="QWidget" name="volume_layout" native="true"> +        <layout class="QHBoxLayout" name="horizontalLayout_2"> +         <property name="leftMargin"> +          <number>0</number> +         </property> +         <property name="topMargin"> +          <number>0</number> +         </property> +         <property name="rightMargin"> +          <number>0</number> +         </property> +         <property name="bottomMargin"> +          <number>0</number> +         </property> +         <item> +          <widget class="QComboBox" name="volume_combo_box"> +           <item> +            <property name="text"> +             <string>Use global volume</string> +            </property> +           </item> +           <item> +            <property name="text"> +             <string>Set volume:</string> +            </property> +           </item> +          </widget> +         </item> +         <item> +          <widget class="QLabel" name="volume_label"> +           <property name="text"> +            <string>Volume:</string> +           </property> +          </widget> +         </item> +         <item> +          <spacer name="horizontalSpacer"> +           <property name="orientation"> +            <enum>Qt::Horizontal</enum> +           </property> +           <property name="sizeHint" stdset="0"> +            <size> +             <width>30</width> +             <height>20</height> +            </size> +           </property> +          </spacer> +         </item> +         <item> +          <widget class="QSlider" name="volume_slider"> +           <property name="sizePolicy"> +            <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> +             <horstretch>0</horstretch> +             <verstretch>0</verstretch> +            </sizepolicy> +           </property> +           <property name="maximum"> +            <number>100</number> +           </property> +           <property name="pageStep"> +            <number>10</number> +           </property> +           <property name="orientation"> +            <enum>Qt::Horizontal</enum> +           </property> +          </widget> +         </item> +         <item> +          <widget class="QLabel" name="volume_indicator"> +           <property name="minimumSize"> +            <size> +             <width>32</width> +             <height>0</height> +            </size> +           </property> +           <property name="text"> +            <string>0 %</string> +           </property> +           <property name="alignment"> +            <set>Qt::AlignCenter</set> +           </property> +          </widget> +         </item> +        </layout> +       </widget>        </item>       </layout>      </widget> diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp new file mode 100644 index 000000000..37fcd6adc --- /dev/null +++ b/src/yuzu/configuration/configure_cpu.cpp @@ -0,0 +1,76 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QComboBox> +#include <QMessageBox> + +#include "common/common_types.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/settings.h" +#include "ui_configure_cpu.h" +#include "yuzu/configuration/configure_cpu.h" + +ConfigureCpu::ConfigureCpu(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureCpu) { +    ui->setupUi(this); + +    SetConfiguration(); + +    connect(ui->accuracy, qOverload<int>(&QComboBox::activated), this, +            &ConfigureCpu::AccuracyUpdated); +    connect(ui->accuracy, qOverload<int>(&QComboBox::currentIndexChanged), this, +            &ConfigureCpu::UpdateGroup); +} + +ConfigureCpu::~ConfigureCpu() = default; + +void ConfigureCpu::SetConfiguration() { +    const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); + +    ui->accuracy->setEnabled(runtime_lock); +    ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy)); +    UpdateGroup(static_cast<int>(Settings::values.cpu_accuracy)); + +    ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock); +    ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma); +    ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock); +    ui->cpuopt_unsafe_reduce_fp_error->setChecked(Settings::values.cpuopt_unsafe_reduce_fp_error); +} + +void ConfigureCpu::AccuracyUpdated(int index) { +    if (static_cast<Settings::CPUAccuracy>(index) == Settings::CPUAccuracy::DebugMode) { +        const auto result = QMessageBox::warning(this, tr("Setting CPU to Debug Mode"), +                                                 tr("CPU Debug Mode is only intended for developer " +                                                    "use. Are you sure you want to enable this?"), +                                                 QMessageBox::Yes | QMessageBox::No); +        if (result == QMessageBox::No) { +            ui->accuracy->setCurrentIndex(static_cast<int>(Settings::CPUAccuracy::Accurate)); +            UpdateGroup(static_cast<int>(Settings::CPUAccuracy::Accurate)); +        } +    } +} + +void ConfigureCpu::UpdateGroup(int index) { +    ui->unsafe_group->setVisible(static_cast<Settings::CPUAccuracy>(index) == +                                 Settings::CPUAccuracy::Unsafe); +} + +void ConfigureCpu::ApplyConfiguration() { +    Settings::values.cpu_accuracy = +        static_cast<Settings::CPUAccuracy>(ui->accuracy->currentIndex()); +    Settings::values.cpuopt_unsafe_unfuse_fma = ui->cpuopt_unsafe_unfuse_fma->isChecked(); +    Settings::values.cpuopt_unsafe_reduce_fp_error = ui->cpuopt_unsafe_reduce_fp_error->isChecked(); +} + +void ConfigureCpu::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QWidget::changeEvent(event); +} + +void ConfigureCpu::RetranslateUI() { +    ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h new file mode 100644 index 000000000..3c5683d81 --- /dev/null +++ b/src/yuzu/configuration/configure_cpu.h @@ -0,0 +1,34 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QWidget> +#include "core/settings.h" + +namespace Ui { +class ConfigureCpu; +} + +class ConfigureCpu : public QWidget { +    Q_OBJECT + +public: +    explicit ConfigureCpu(QWidget* parent = nullptr); +    ~ConfigureCpu() override; + +    void ApplyConfiguration(); + +private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); + +    void AccuracyUpdated(int index); +    void UpdateGroup(int index); + +    void SetConfiguration(); + +    std::unique_ptr<Ui::ConfigureCpu> ui; +}; diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui new file mode 100644 index 000000000..ebdd2e6e9 --- /dev/null +++ b/src/yuzu/configuration/configure_cpu.ui @@ -0,0 +1,144 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureCpu</class> + <widget class="QWidget" name="ConfigureCpu"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>400</width> +    <height>321</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Form</string> +  </property> +  <layout class="QVBoxLayout"> +   <item> +    <layout class="QVBoxLayout"> +     <item> +      <widget class="QGroupBox"> +       <property name="title"> +        <string>General</string> +       </property> +       <layout class="QVBoxLayout"> +        <item> +         <layout class="QHBoxLayout"> +          <item> +           <widget class="QLabel"> +            <property name="text"> +             <string>Accuracy:</string> +            </property> +           </widget> +          </item> +          <item> +           <widget class="QComboBox" name="accuracy"> +            <item> +             <property name="text"> +              <string>Accurate</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Unsafe</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Enable Debug Mode</string> +             </property> +            </item> +           </widget> +          </item> +         </layout> +        </item> +        <item> +         <widget class="QLabel"> +          <property name="wordWrap"> +            <bool>1</bool> +          </property> +          <property name="text"> +           <string>We recommend setting accuracy to "Accurate".</string> +          </property> +         </widget> +        </item> +       </layout> +      </widget> +     </item> +    </layout> +   </item> +   <item> +    <layout class="QVBoxLayout"> +     <item> +      <widget class="QGroupBox" name="unsafe_group"> +       <property name="title"> +        <string>Unsafe CPU Optimization Settings</string> +       </property> +       <layout class="QVBoxLayout"> +        <item> +         <widget class="QLabel"> +          <property name="wordWrap"> +            <bool>1</bool> +          </property> +          <property name="text"> +           <string>These settings reduce accuracy for speed.</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QCheckBox" name="cpuopt_unsafe_unfuse_fma"> +          <property name="text"> +           <string>Unfuse FMA (improve performance on CPUs without FMA)</string> +          </property> +          <property name="toolTip"> +           <string> +            <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> +           </string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QCheckBox" name="cpuopt_unsafe_reduce_fp_error"> +          <property name="text"> +           <string>Faster FRSQRTE and FRECPE</string> +          </property> +          <property name="toolTip"> +           <string> +            <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> +           </string> +          </property> +         </widget> +        </item> +       </layout> +      </widget> +     </item> +    </layout> +   </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> +    <widget class="QLabel" name="label_disable_info"> +     <property name="text"> +      <string>CPU settings are available only when game is not running.</string> +     </property> +     <property name="wordWrap"> +      <bool>true</bool> +     </property> +    </widget> +   </item> +  </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_cpu_debug.cpp b/src/yuzu/configuration/configure_cpu_debug.cpp new file mode 100644 index 000000000..3385b2cf6 --- /dev/null +++ b/src/yuzu/configuration/configure_cpu_debug.cpp @@ -0,0 +1,65 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QComboBox> + +#include "common/common_types.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/settings.h" +#include "ui_configure_cpu_debug.h" +#include "yuzu/configuration/configure_cpu_debug.h" + +ConfigureCpuDebug::ConfigureCpuDebug(QWidget* parent) +    : QWidget(parent), ui(new Ui::ConfigureCpuDebug) { +    ui->setupUi(this); + +    SetConfiguration(); +} + +ConfigureCpuDebug::~ConfigureCpuDebug() = default; + +void ConfigureCpuDebug::SetConfiguration() { +    const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); + +    ui->cpuopt_page_tables->setEnabled(runtime_lock); +    ui->cpuopt_page_tables->setChecked(Settings::values.cpuopt_page_tables); +    ui->cpuopt_block_linking->setEnabled(runtime_lock); +    ui->cpuopt_block_linking->setChecked(Settings::values.cpuopt_block_linking); +    ui->cpuopt_return_stack_buffer->setEnabled(runtime_lock); +    ui->cpuopt_return_stack_buffer->setChecked(Settings::values.cpuopt_return_stack_buffer); +    ui->cpuopt_fast_dispatcher->setEnabled(runtime_lock); +    ui->cpuopt_fast_dispatcher->setChecked(Settings::values.cpuopt_fast_dispatcher); +    ui->cpuopt_context_elimination->setEnabled(runtime_lock); +    ui->cpuopt_context_elimination->setChecked(Settings::values.cpuopt_context_elimination); +    ui->cpuopt_const_prop->setEnabled(runtime_lock); +    ui->cpuopt_const_prop->setChecked(Settings::values.cpuopt_const_prop); +    ui->cpuopt_misc_ir->setEnabled(runtime_lock); +    ui->cpuopt_misc_ir->setChecked(Settings::values.cpuopt_misc_ir); +    ui->cpuopt_reduce_misalign_checks->setEnabled(runtime_lock); +    ui->cpuopt_reduce_misalign_checks->setChecked(Settings::values.cpuopt_reduce_misalign_checks); +} + +void ConfigureCpuDebug::ApplyConfiguration() { +    Settings::values.cpuopt_page_tables = ui->cpuopt_page_tables->isChecked(); +    Settings::values.cpuopt_block_linking = ui->cpuopt_block_linking->isChecked(); +    Settings::values.cpuopt_return_stack_buffer = ui->cpuopt_return_stack_buffer->isChecked(); +    Settings::values.cpuopt_fast_dispatcher = ui->cpuopt_fast_dispatcher->isChecked(); +    Settings::values.cpuopt_context_elimination = ui->cpuopt_context_elimination->isChecked(); +    Settings::values.cpuopt_const_prop = ui->cpuopt_const_prop->isChecked(); +    Settings::values.cpuopt_misc_ir = ui->cpuopt_misc_ir->isChecked(); +    Settings::values.cpuopt_reduce_misalign_checks = ui->cpuopt_reduce_misalign_checks->isChecked(); +} + +void ConfigureCpuDebug::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QWidget::changeEvent(event); +} + +void ConfigureCpuDebug::RetranslateUI() { +    ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_cpu_debug.h b/src/yuzu/configuration/configure_cpu_debug.h new file mode 100644 index 000000000..c9941ef3b --- /dev/null +++ b/src/yuzu/configuration/configure_cpu_debug.h @@ -0,0 +1,31 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QWidget> +#include "core/settings.h" + +namespace Ui { +class ConfigureCpuDebug; +} + +class ConfigureCpuDebug : public QWidget { +    Q_OBJECT + +public: +    explicit ConfigureCpuDebug(QWidget* parent = nullptr); +    ~ConfigureCpuDebug() override; + +    void ApplyConfiguration(); + +private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); + +    void SetConfiguration(); + +    std::unique_ptr<Ui::ConfigureCpuDebug> ui; +}; diff --git a/src/yuzu/configuration/configure_cpu_debug.ui b/src/yuzu/configuration/configure_cpu_debug.ui new file mode 100644 index 000000000..a90dc64fe --- /dev/null +++ b/src/yuzu/configuration/configure_cpu_debug.ui @@ -0,0 +1,174 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureCpuDebug</class> + <widget class="QWidget" name="ConfigureCpuDebug"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>400</width> +    <height>321</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Form</string> +  </property> +  <layout class="QVBoxLayout"> +   <item> +    <layout class="QVBoxLayout"> +     <item> +      <widget class="QGroupBox"> +       <property name="title"> +        <string>Toggle CPU Optimizations</string> +       </property> +       <layout class="QVBoxLayout"> +        <item> +         <widget class="QLabel"> +          <property name="wordWrap"> +            <bool>1</bool> +          </property> +          <property name="text"> +           <string> +            <div> +            <b>For debugging only.</b> +            <br> +            If you're not sure what these do, keep all of these enabled. +            <br> +            These settings only take effect when CPU Accuracy is "Debug Mode". +            </div> +           </string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QCheckBox" name="cpuopt_page_tables"> +          <property name="text"> +           <string>Enable inline page tables</string> +          </property> +          <property name="toolTip"> +           <string> +            <div style="white-space: nowrap">This optimization speeds up memory accesses by the guest program.</div> +            <div style="white-space: nowrap">Enabling it inlines accesses to PageTable::pointers into emitted code.</div> +            <div style="white-space: nowrap">Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.</div> +           </string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QCheckBox" name="cpuopt_block_linking"> +          <property name="text"> +           <string>Enable block linking</string> +          </property> +          <property name="toolTip"> +           <string> +            <div>This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.</div> +           </string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QCheckBox" name="cpuopt_return_stack_buffer"> +          <property name="text"> +           <string>Enable return stack buffer</string> +          </property> +          <property name="toolTip"> +           <string> +            <div>This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.</div> +           </string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QCheckBox" name="cpuopt_fast_dispatcher"> +          <property name="text"> +           <string>Enable fast dispatcher</string> +          </property> +          <property name="toolTip"> +           <string> +            <div>Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.</div> +           </string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QCheckBox" name="cpuopt_context_elimination"> +          <property name="text"> +           <string>Enable context elimination</string> +          </property> +          <property name="toolTip"> +           <string> +            <div>Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.</div> +           </string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QCheckBox" name="cpuopt_const_prop"> +          <property name="text"> +           <string>Enable constant propagation</string> +          </property> +          <property name="toolTip"> +           <string> +            <div>Enables IR optimizations that involve constant propagation.</div> +           </string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QCheckBox" name="cpuopt_misc_ir"> +          <property name="text"> +           <string>Enable miscellaneous optimizations</string> +          </property> +          <property name="toolTip"> +           <string> +            <div>Enables miscellaneous IR optimizations.</div> +           </string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QCheckBox" name="cpuopt_reduce_misalign_checks"> +          <property name="text"> +           <string>Enable misalignment check reduction</string> +          </property> +          <property name="toolTip"> +           <string> +            <div style="white-space: nowrap">When enabled, a misalignment is only triggered when an access crosses a page boundary.</div> +            <div style="white-space: nowrap">When disabled, a misalignment is triggered on all misaligned accesses.</div> +           </string> +          </property> +         </widget> +        </item> +       </layout> +      </widget> +     </item> +    </layout> +   </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> +    <widget class="QLabel" name="label_disable_info"> +     <property name="text"> +      <string>CPU settings are available only when game is not running.</string> +     </property> +     <property name="wordWrap"> +      <bool>true</bool> +     </property> +    </widget> +   </item> +  </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 9631059c7..027099ab7 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -19,7 +19,8 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co      SetConfiguration();      connect(ui->open_log_button, &QPushButton::clicked, []() { -        QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir)); +        const auto path = +            QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LogDir));          QDesktopServices::openUrl(QUrl::fromLocalFile(path));      });  } @@ -38,6 +39,9 @@ void ConfigureDebug::SetConfiguration() {      ui->quest_flag->setChecked(Settings::values.quest_flag);      ui->enable_graphics_debugging->setEnabled(!Core::System::GetInstance().IsPoweredOn());      ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug); +    ui->disable_macro_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); +    ui->disable_macro_jit->setChecked(Settings::values.disable_macro_jit); +    ui->extended_logging->setChecked(Settings::values.extended_logging);  }  void ConfigureDebug::ApplyConfiguration() { @@ -49,6 +53,8 @@ void ConfigureDebug::ApplyConfiguration() {      Settings::values.reporting_services = ui->reporting_services->isChecked();      Settings::values.quest_flag = ui->quest_flag->isChecked();      Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked(); +    Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked(); +    Settings::values.extended_logging = ui->extended_logging->isChecked();      Debugger::ToggleConsole();      Log::Filter filter;      filter.ParseFilterString(Settings::values.log_filter); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index e028c4c80..6f94fe304 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -90,7 +90,7 @@          <item>           <widget class="QCheckBox" name="toggle_console">            <property name="text"> -           <string>Show Log Console (Windows Only)</string> +           <string>Show Log in Console</string>            </property>           </widget>          </item> @@ -103,6 +103,34 @@          </item>         </layout>        </item> +      <item> +       <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> +      <item> +       <widget class="QLabel" name="label_3"> +        <property name="font"> +         <font> +          <italic>true</italic> +         </font> +        </property> +        <property name="text"> +         <string>This will be reset automatically when yuzu closes.</string> +        </property> +        <property name="indent"> +         <number>20</number> +        </property> +       </widget> +      </item>       </layout>      </widget>     </item> @@ -115,7 +143,7 @@        <item>         <layout class="QHBoxLayout" name="horizontalLayout_4">          <item> -         <widget class="QLabel" name="label_3"> +         <widget class="QLabel" name="label_4">            <property name="text">             <string>Arguments String</string>            </property> @@ -140,14 +168,27 @@          <property name="enabled">           <bool>true</bool>          </property> -        <property name="whatsThis"> -         <string>When checked, the graphics API enters in a slower debugging mode</string> +        <property name="toolTip"> +         <string>When checked, the graphics API enters a slower debugging mode</string>          </property>          <property name="text">           <string>Enable Graphics Debugging</string>          </property>         </widget>        </item> +      <item> +       <widget class="QCheckBox" name="disable_macro_jit"> +        <property name="enabled"> +         <bool>true</bool> +        </property> +        <property name="toolTip"> +         <string>When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower</string> +        </property> +        <property name="text"> +         <string>Disable Macro JIT</string> +        </property> +       </widget> +      </item>       </layout>      </widget>     </item> @@ -156,27 +197,7 @@       <property name="title">        <string>Dump</string>       </property> -     <layout class="QVBoxLayout" name="verticalLayout_6"> -      <item> -       <widget class="QCheckBox" name="dump_decompressed_nso"> -        <property name="whatsThis"> -         <string>When checked, any NSO yuzu tries to load or patch will be copied decompressed to the yuzu/dump directory.</string> -        </property> -        <property name="text"> -         <string>Dump Decompressed NSOs</string> -        </property> -       </widget> -      </item> -      <item> -       <widget class="QCheckBox" name="dump_exefs"> -        <property name="whatsThis"> -         <string>When checked, any game that yuzu loads will have its ExeFS dumped to the yuzu/dump directory.</string> -        </property> -        <property name="text"> -         <string>Dump ExeFS</string> -        </property> -       </widget> -      </item> +     <layout class="QVBoxLayout" name="verticalLayout_7">        <item>         <widget class="QCheckBox" name="reporting_services">          <property name="text"> @@ -185,7 +206,7 @@         </widget>        </item>        <item> -       <widget class="QLabel" name="label"> +       <widget class="QLabel" name="label_5">          <property name="font">           <font>            <italic>true</italic> @@ -207,7 +228,7 @@       <property name="title">        <string>Advanced</string>       </property> -     <layout class="QVBoxLayout" name="verticalLayout_7"> +     <layout class="QVBoxLayout" name="verticalLayout_8">        <item>         <widget class="QCheckBox" name="quest_flag">          <property name="text"> @@ -244,8 +265,6 @@    <tabstop>open_log_button</tabstop>    <tabstop>homebrew_args_edit</tabstop>    <tabstop>enable_graphics_debugging</tabstop> -  <tabstop>dump_decompressed_nso</tabstop> -  <tabstop>dump_exefs</tabstop>    <tabstop>reporting_services</tabstop>    <tabstop>quest_flag</tabstop>   </tabstops> diff --git a/src/yuzu/configuration/configure_debug_controller.cpp b/src/yuzu/configuration/configure_debug_controller.cpp new file mode 100644 index 000000000..a878ef9c6 --- /dev/null +++ b/src/yuzu/configuration/configure_debug_controller.cpp @@ -0,0 +1,43 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "ui_configure_debug_controller.h" +#include "yuzu/configuration/configure_debug_controller.h" +#include "yuzu/configuration/configure_input_player.h" + +ConfigureDebugController::ConfigureDebugController(QWidget* parent, +                                                   InputCommon::InputSubsystem* input_subsystem, +                                                   InputProfiles* profiles) +    : QDialog(parent), ui(std::make_unique<Ui::ConfigureDebugController>()), +      debug_controller( +          new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, true)) { +    ui->setupUi(this); + +    ui->controllerLayout->addWidget(debug_controller); + +    connect(ui->clear_all_button, &QPushButton::clicked, this, +            [this] { debug_controller->ClearAll(); }); +    connect(ui->restore_defaults_button, &QPushButton::clicked, this, +            [this] { debug_controller->RestoreDefaults(); }); + +    RetranslateUI(); +} + +ConfigureDebugController::~ConfigureDebugController() = default; + +void ConfigureDebugController::ApplyConfiguration() { +    debug_controller->ApplyConfiguration(); +} + +void ConfigureDebugController::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QDialog::changeEvent(event); +} + +void ConfigureDebugController::RetranslateUI() { +    ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_debug_controller.h b/src/yuzu/configuration/configure_debug_controller.h new file mode 100644 index 000000000..b4f53fad5 --- /dev/null +++ b/src/yuzu/configuration/configure_debug_controller.h @@ -0,0 +1,41 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QDialog> + +class QPushButton; + +class ConfigureInputPlayer; + +class InputProfiles; + +namespace InputCommon { +class InputSubsystem; +} + +namespace Ui { +class ConfigureDebugController; +} + +class ConfigureDebugController : public QDialog { +    Q_OBJECT + +public: +    explicit ConfigureDebugController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem, +                                      InputProfiles* profiles); +    ~ConfigureDebugController() override; + +    void ApplyConfiguration(); + +private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); + +    std::unique_ptr<Ui::ConfigureDebugController> ui; + +    ConfigureInputPlayer* debug_controller; +}; diff --git a/src/yuzu/configuration/configure_debug_controller.ui b/src/yuzu/configuration/configure_debug_controller.ui new file mode 100644 index 000000000..7b7e6582c --- /dev/null +++ b/src/yuzu/configuration/configure_debug_controller.ui @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureDebugController</class> + <widget class="QDialog" name="ConfigureDebugController"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>780</width> +    <height>500</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Configure Debug Controller</string> +  </property> +  <layout class="QVBoxLayout" name="verticalLayout"> +   <property name="spacing"> +    <number>2</number> +   </property> +   <property name="leftMargin"> +    <number>9</number> +   </property> +   <property name="topMargin"> +    <number>9</number> +   </property> +   <property name="rightMargin"> +    <number>9</number> +   </property> +   <property name="bottomMargin"> +    <number>9</number> +   </property> +   <item> +    <layout class="QHBoxLayout" name="controllerLayout"/> +   </item> +   <item> +    <layout class="QHBoxLayout" name="horizontalLayout"> +     <item> +      <widget class="QPushButton" name="clear_all_button"> +       <property name="text"> +        <string>Clear</string> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QPushButton" name="restore_defaults_button"> +       <property name="text"> +        <string>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>ConfigureDebugController</receiver> +   <slot>accept()</slot> +  </connection> +  <connection> +   <sender>buttonBox</sender> +   <signal>rejected()</signal> +   <receiver>ConfigureDebugController</receiver> +   <slot>reject()</slot> +  </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index df4473b46..5041e0bf8 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -12,15 +12,21 @@  #include "yuzu/configuration/configure_input_player.h"  #include "yuzu/hotkeys.h" -ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry) +ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, +                                 InputCommon::InputSubsystem* input_subsystem)      : QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) { +    Settings::SetConfiguringGlobal(true); +      ui->setupUi(this);      ui->hotkeysTab->Populate(registry);      setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); +    ui->inputTab->Initialize(input_subsystem); +      SetConfiguration();      PopulateSelectionList(); +    connect(ui->uiTab, &ConfigureUi::LanguageChanged, this, &ConfigureDialog::OnLanguageChanged);      connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,              &ConfigureDialog::UpdateVisibleTabs); @@ -40,6 +46,8 @@ void ConfigureDialog::ApplyConfiguration() {      ui->filesystemTab->applyConfiguration();      ui->inputTab->ApplyConfiguration();      ui->hotkeysTab->ApplyConfiguration(registry); +    ui->cpuTab->ApplyConfiguration(); +    ui->cpuDebugTab->ApplyConfiguration();      ui->graphicsTab->ApplyConfiguration();      ui->graphicsAdvancedTab->ApplyConfiguration();      ui->audioTab->ApplyConfiguration(); @@ -74,12 +82,13 @@ void ConfigureDialog::RetranslateUI() {  Q_DECLARE_METATYPE(QList<QWidget*>);  void ConfigureDialog::PopulateSelectionList() { -    const std::array<std::pair<QString, QList<QWidget*>>, 5> items{ -        {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->uiTab}}, +    const std::array<std::pair<QString, QList<QWidget*>>, 6> items{ +        {{tr("General"), {ui->generalTab, ui->hotkeysTab, ui->uiTab, ui->webTab, ui->debugTab}},           {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab}}, +         {tr("CPU"), {ui->cpuTab, ui->cpuDebugTab}},           {tr("Graphics"), {ui->graphicsTab, ui->graphicsAdvancedTab}},           {tr("Audio"), {ui->audioTab}}, -         {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}}, +         {tr("Controls"), ui->inputTab->GetSubTabs()}},      };      [[maybe_unused]] const QSignalBlocker blocker(ui->selectorList); @@ -93,6 +102,14 @@ void ConfigureDialog::PopulateSelectionList() {      }  } +void ConfigureDialog::OnLanguageChanged(const QString& locale) { +    emit LanguageChanged(locale); +    // first apply the configuration, and then restore the display +    ApplyConfiguration(); +    RetranslateUI(); +    SetConfiguration(); +} +  void ConfigureDialog::UpdateVisibleTabs() {      const auto items = ui->selectorList->selectedItems();      if (items.isEmpty()) { @@ -103,8 +120,10 @@ void ConfigureDialog::UpdateVisibleTabs() {          {ui->generalTab, tr("General")},          {ui->systemTab, tr("System")},          {ui->profileManagerTab, tr("Profiles")}, -        {ui->inputTab, tr("Input")}, +        {ui->inputTab, tr("Controls")},          {ui->hotkeysTab, tr("Hotkeys")}, +        {ui->cpuTab, tr("CPU")}, +        {ui->cpuDebugTab, tr("Debug")},          {ui->graphicsTab, tr("Graphics")},          {ui->graphicsAdvancedTab, tr("Advanced")},          {ui->audioTab, tr("Audio")}, @@ -122,6 +141,6 @@ void ConfigureDialog::UpdateVisibleTabs() {      const QList<QWidget*> tabs = qvariant_cast<QList<QWidget*>>(items[0]->data(Qt::UserRole));      for (const auto tab : tabs) { -        ui->tabWidget->addTab(tab, widgets.at(tab)); +        ui->tabWidget->addTab(tab, tab->accessibleName());      }  } diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h index 2d3bfc2da..570c3b941 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h @@ -9,6 +9,10 @@  class HotkeyRegistry; +namespace InputCommon { +class InputSubsystem; +} +  namespace Ui {  class ConfigureDialog;  } @@ -17,11 +21,18 @@ class ConfigureDialog : public QDialog {      Q_OBJECT  public: -    explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry); +    explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, +                             InputCommon::InputSubsystem* input_subsystem);      ~ConfigureDialog() override;      void ApplyConfiguration(); +private slots: +    void OnLanguageChanged(const QString& locale); + +signals: +    void LanguageChanged(const QString& locale); +  private:      void changeEvent(QEvent* event) override; diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp index 29f540eb7..7ab4a80f7 100644 --- a/src/yuzu/configuration/configure_filesystem.cpp +++ b/src/yuzu/configuration/configure_filesystem.cpp @@ -11,19 +11,6 @@  #include "yuzu/configuration/configure_filesystem.h"  #include "yuzu/uisettings.h" -namespace { - -template <typename T> -void SetComboBoxFromData(QComboBox* combo_box, T data) { -    const auto index = combo_box->findData(QVariant::fromValue(static_cast<u64>(data))); -    if (index >= combo_box->count() || index < 0) -        return; - -    combo_box->setCurrentIndex(index); -} - -} // Anonymous namespace -  ConfigureFilesystem::ConfigureFilesystem(QWidget* parent)      : QWidget(parent), ui(std::make_unique<Ui::ConfigureFilesystem>()) {      ui->setupUi(this); @@ -55,16 +42,16 @@ ConfigureFilesystem::~ConfigureFilesystem() = default;  void ConfigureFilesystem::setConfiguration() {      ui->nand_directory_edit->setText( -        QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); +        QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)));      ui->sdmc_directory_edit->setText( -        QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); +        QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)));      ui->gamecard_path_edit->setText(QString::fromStdString(Settings::values.gamecard_path));      ui->dump_path_edit->setText( -        QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir))); +        QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir)));      ui->load_path_edit->setText( -        QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir))); +        QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir)));      ui->cache_directory_edit->setText( -        QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir))); +        QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)));      ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted);      ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game); @@ -73,23 +60,20 @@ void ConfigureFilesystem::setConfiguration() {      ui->cache_game_list->setChecked(UISettings::values.cache_game_list); -    SetComboBoxFromData(ui->nand_size, Settings::values.nand_total_size); -    SetComboBoxFromData(ui->usrnand_size, Settings::values.nand_user_size); -    SetComboBoxFromData(ui->sysnand_size, Settings::values.nand_system_size); -    SetComboBoxFromData(ui->sdmc_size, Settings::values.sdmc_size); -      UpdateEnabledControls();  }  void ConfigureFilesystem::applyConfiguration() { -    FileUtil::GetUserPath(FileUtil::UserPath::NANDDir, -                          ui->nand_directory_edit->text().toStdString()); -    FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir, -                          ui->sdmc_directory_edit->text().toStdString()); -    FileUtil::GetUserPath(FileUtil::UserPath::DumpDir, ui->dump_path_edit->text().toStdString()); -    FileUtil::GetUserPath(FileUtil::UserPath::LoadDir, ui->load_path_edit->text().toStdString()); -    FileUtil::GetUserPath(FileUtil::UserPath::CacheDir, -                          ui->cache_directory_edit->text().toStdString()); +    Common::FS::GetUserPath(Common::FS::UserPath::NANDDir, +                            ui->nand_directory_edit->text().toStdString()); +    Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir, +                            ui->sdmc_directory_edit->text().toStdString()); +    Common::FS::GetUserPath(Common::FS::UserPath::DumpDir, +                            ui->dump_path_edit->text().toStdString()); +    Common::FS::GetUserPath(Common::FS::UserPath::LoadDir, +                            ui->load_path_edit->text().toStdString()); +    Common::FS::GetUserPath(Common::FS::UserPath::CacheDir, +                            ui->cache_directory_edit->text().toStdString());      Settings::values.gamecard_path = ui->gamecard_path_edit->text().toStdString();      Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked(); @@ -98,15 +82,6 @@ void ConfigureFilesystem::applyConfiguration() {      Settings::values.dump_nso = ui->dump_nso->isChecked();      UISettings::values.cache_game_list = ui->cache_game_list->isChecked(); - -    Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>( -        ui->nand_size->itemData(ui->nand_size->currentIndex()).toULongLong()); -    Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>( -        ui->nand_size->itemData(ui->sysnand_size->currentIndex()).toULongLong()); -    Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>( -        ui->nand_size->itemData(ui->usrnand_size->currentIndex()).toULongLong()); -    Settings::values.sdmc_size = static_cast<Settings::SDMCSize>( -        ui->nand_size->itemData(ui->sdmc_size->currentIndex()).toULongLong());  }  void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit) { @@ -138,7 +113,7 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit)          str = QFileDialog::getOpenFileName(this, caption, QFileInfo(edit->text()).dir().path(),                                             QStringLiteral("NX Gamecard;*.xci"));      } else { -        str = QFileDialog::getExistingDirectory(this, caption, edit->text()); +        str = QFileDialog::getExistingDirectory(this, caption, edit->text()) + QDir::separator();      }      if (str.isEmpty()) @@ -148,12 +123,13 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit)  }  void ConfigureFilesystem::ResetMetadata() { -    if (!FileUtil::Exists(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + -                          "game_list")) { +    if (!Common::FS::Exists(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + +                            "game_list")) {          QMessageBox::information(this, tr("Reset Metadata Cache"),                                   tr("The metadata cache is already empty.")); -    } else if (FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + -                                              DIR_SEP + "game_list")) { +    } else if (Common::FS::DeleteDirRecursively( +                   Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + +                   "game_list")) {          QMessageBox::information(this, tr("Reset Metadata Cache"),                                   tr("The operation completed successfully."));          UISettings::values.is_game_list_reload_pending.exchange(true); diff --git a/src/yuzu/configuration/configure_filesystem.ui b/src/yuzu/configuration/configure_filesystem.ui index 58cd07f52..84bea0600 100644 --- a/src/yuzu/configuration/configure_filesystem.ui +++ b/src/yuzu/configuration/configure_filesystem.ui @@ -116,127 +116,6 @@        </widget>       </item>       <item> -      <widget class="QGroupBox" name="groupBox_3"> -       <property name="title"> -        <string>Storage Sizes</string> -       </property> -       <layout class="QGridLayout" name="gridLayout_3"> -        <item row="3" column="0"> -         <widget class="QLabel" name="label_5"> -          <property name="text"> -           <string>SD Card</string> -          </property> -         </widget> -        </item> -        <item row="1" column="0"> -         <widget class="QLabel" name="label_4"> -          <property name="text"> -           <string>System NAND</string> -          </property> -         </widget> -        </item> -        <item row="1" column="1"> -         <widget class="QComboBox" name="sysnand_size"> -          <item> -           <property name="text"> -            <string>2.5 GB</string> -           </property> -          </item> -         </widget> -        </item> -        <item row="3" column="1"> -         <widget class="QComboBox" name="sdmc_size"> -          <property name="currentText"> -           <string>32 GB</string> -          </property> -          <item> -           <property name="text"> -            <string>1 GB</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>2 GB</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>4 GB</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>8 GB</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>16 GB</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>32 GB</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>64 GB</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>128 GB</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>256 GB</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>1 TB</string> -           </property> -          </item> -         </widget> -        </item> -        <item row="2" column="1"> -         <widget class="QComboBox" name="usrnand_size"> -          <item> -           <property name="text"> -            <string>26 GB</string> -           </property> -          </item> -         </widget> -        </item> -        <item row="2" column="0"> -         <widget class="QLabel" name="label_6"> -          <property name="text"> -           <string>User NAND</string> -          </property> -         </widget> -        </item> -        <item row="0" column="0"> -         <widget class="QLabel" name="label_7"> -          <property name="text"> -           <string>NAND</string> -          </property> -         </widget> -        </item> -        <item row="0" column="1"> -         <widget class="QComboBox" name="nand_size"> -          <item> -           <property name="text"> -            <string>29.1 GB</string> -           </property> -          </item> -         </widget> -        </item> -       </layout> -      </widget> -     </item> -     <item>        <widget class="QGroupBox" name="groupBox_4">         <property name="title">          <string>Patch Manager</string> diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 5ef927114..d4d29d422 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -7,38 +7,77 @@  #include "core/core.h"  #include "core/settings.h"  #include "ui_configure_general.h" +#include "yuzu/configuration/configuration_shared.h"  #include "yuzu/configuration/configure_general.h"  #include "yuzu/uisettings.h"  ConfigureGeneral::ConfigureGeneral(QWidget* parent)      : QWidget(parent), ui(new Ui::ConfigureGeneral) { -      ui->setupUi(this); +    SetupPerGameUI(); +      SetConfiguration(); -    connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled); +    if (Settings::IsConfiguringGlobal()) { +        connect(ui->toggle_frame_limit, &QCheckBox::clicked, ui->frame_limit, +                [this]() { ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); }); +    }  }  ConfigureGeneral::~ConfigureGeneral() = default;  void ConfigureGeneral::SetConfiguration() { +    const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); + +    ui->use_multi_core->setEnabled(runtime_lock); +    ui->use_multi_core->setChecked(Settings::values.use_multi_core.GetValue()); +      ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);      ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);      ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background); +    ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse); + +    ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit.GetValue()); +    ui->frame_limit->setValue(Settings::values.frame_limit.GetValue()); -    ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); -    ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); -    ui->frame_limit->setValue(Settings::values.frame_limit); +    if (Settings::IsConfiguringGlobal()) { +        ui->frame_limit->setEnabled(Settings::values.use_frame_limit.GetValue()); +    } else { +        ui->frame_limit->setEnabled(Settings::values.use_frame_limit.GetValue() && +                                    use_frame_limit != ConfigurationShared::CheckState::Global); +    }  }  void ConfigureGeneral::ApplyConfiguration() { -    UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); -    UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); -    UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked(); +    if (Settings::IsConfiguringGlobal()) { +        UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); +        UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); +        UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked(); +        UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked(); + +        // Guard if during game and set to game-specific value +        if (Settings::values.use_frame_limit.UsingGlobal()) { +            Settings::values.use_frame_limit.SetValue(ui->toggle_frame_limit->checkState() == +                                                      Qt::Checked); +            Settings::values.frame_limit.SetValue(ui->frame_limit->value()); +        } +        if (Settings::values.use_multi_core.UsingGlobal()) { +            Settings::values.use_multi_core.SetValue(ui->use_multi_core->isChecked()); +        } +    } else { +        ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, +                                                 ui->use_multi_core, use_multi_core); -    Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); -    Settings::values.frame_limit = ui->frame_limit->value(); +        bool global_frame_limit = use_frame_limit == ConfigurationShared::CheckState::Global; +        Settings::values.use_frame_limit.SetGlobal(global_frame_limit); +        Settings::values.frame_limit.SetGlobal(global_frame_limit); +        if (!global_frame_limit) { +            Settings::values.use_frame_limit.SetValue(ui->toggle_frame_limit->checkState() == +                                                      Qt::Checked); +            Settings::values.frame_limit.SetValue(ui->frame_limit->value()); +        } +    }  }  void ConfigureGeneral::changeEvent(QEvent* event) { @@ -52,3 +91,27 @@ void ConfigureGeneral::changeEvent(QEvent* event) {  void ConfigureGeneral::RetranslateUI() {      ui->retranslateUi(this);  } + +void ConfigureGeneral::SetupPerGameUI() { +    if (Settings::IsConfiguringGlobal()) { +        ui->toggle_frame_limit->setEnabled(Settings::values.use_frame_limit.UsingGlobal()); +        ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal()); + +        return; +    } + +    ui->toggle_check_exit->setVisible(false); +    ui->toggle_user_on_boot->setVisible(false); +    ui->toggle_background_pause->setVisible(false); +    ui->toggle_hide_mouse->setVisible(false); + +    ConfigurationShared::SetColoredTristate(ui->toggle_frame_limit, +                                            Settings::values.use_frame_limit, use_frame_limit); +    ConfigurationShared::SetColoredTristate(ui->use_multi_core, Settings::values.use_multi_core, +                                            use_multi_core); + +    connect(ui->toggle_frame_limit, &QCheckBox::clicked, ui->frame_limit, [this]() { +        ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked() && +                                    (use_frame_limit != ConfigurationShared::CheckState::Global)); +    }); +} diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h index ef05ce065..323ffbd8f 100644 --- a/src/yuzu/configuration/configure_general.h +++ b/src/yuzu/configuration/configure_general.h @@ -7,6 +7,10 @@  #include <memory>  #include <QWidget> +namespace ConfigurationShared { +enum class CheckState; +} +  class HotkeyRegistry;  namespace Ui { @@ -28,5 +32,10 @@ private:      void SetConfiguration(); +    void SetupPerGameUI(); +      std::unique_ptr<Ui::ConfigureGeneral> ui; + +    ConfigurationShared::CheckState use_frame_limit; +    ConfigurationShared::CheckState use_multi_core;  }; diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index 857119bb3..2711116a2 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -52,6 +52,13 @@             </layout>            </item>            <item> +           <widget class="QCheckBox" name="use_multi_core"> +            <property name="text"> +             <string>Multicore CPU Emulation</string> +            </property> +           </widget> +          </item> +          <item>             <widget class="QCheckBox" name="toggle_check_exit">              <property name="text">               <string>Confirm exit while emulation is running</string> @@ -72,6 +79,13 @@              </property>             </widget>            </item> +          <item> +           <widget class="QCheckBox" name="toggle_hide_mouse"> +            <property name="text"> +             <string>Hide mouse on inactivity</string> +            </property> +           </widget> +          </item>           </layout>          </item>         </layout> diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index ea667caef..6fda0ce35 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -13,65 +13,32 @@  #include "core/core.h"  #include "core/settings.h"  #include "ui_configure_graphics.h" +#include "yuzu/configuration/configuration_shared.h"  #include "yuzu/configuration/configure_graphics.h"  #ifdef HAS_VULKAN  #include "video_core/renderer_vulkan/renderer_vulkan.h"  #endif -namespace { -enum class Resolution : int { -    Auto, -    Scale1x, -    Scale2x, -    Scale3x, -    Scale4x, -}; - -float ToResolutionFactor(Resolution option) { -    switch (option) { -    case Resolution::Auto: -        return 0.f; -    case Resolution::Scale1x: -        return 1.f; -    case Resolution::Scale2x: -        return 2.f; -    case Resolution::Scale3x: -        return 3.f; -    case Resolution::Scale4x: -        return 4.f; -    } -    return 0.f; -} - -Resolution FromResolutionFactor(float factor) { -    if (factor == 0.f) { -        return Resolution::Auto; -    } else if (factor == 1.f) { -        return Resolution::Scale1x; -    } else if (factor == 2.f) { -        return Resolution::Scale2x; -    } else if (factor == 3.f) { -        return Resolution::Scale3x; -    } else if (factor == 4.f) { -        return Resolution::Scale4x; -    } -    return Resolution::Auto; -} -} // Anonymous namespace -  ConfigureGraphics::ConfigureGraphics(QWidget* parent)      : QWidget(parent), ui(new Ui::ConfigureGraphics) { -    vulkan_device = Settings::values.vulkan_device; +    vulkan_device = Settings::values.vulkan_device.GetValue();      RetrieveVulkanDevices();      ui->setupUi(this); +    SetupPerGameUI(); +      SetConfiguration(); -    connect(ui->api, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, -            [this] { UpdateDeviceComboBox(); }); -    connect(ui->device, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, +    connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] { +        UpdateDeviceComboBox(); +        if (!Settings::IsConfiguringGlobal()) { +            ConfigurationShared::SetHighlight( +                ui->api_layout, ui->api->currentIndex() != ConfigurationShared::USE_GLOBAL_INDEX); +        } +    }); +    connect(ui->device, qOverload<int>(&QComboBox::activated), this,              [this](int device) { UpdateDeviceSelection(device); });      connect(ui->bg_button, &QPushButton::clicked, this, [this] { @@ -81,6 +48,9 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)          }          UpdateBackgroundColorButton(new_bg_color);      }); + +    ui->bg_label->setVisible(Settings::IsConfiguringGlobal()); +    ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal());  }  void ConfigureGraphics::UpdateDeviceSelection(int device) { @@ -98,31 +68,103 @@ void ConfigureGraphics::SetConfiguration() {      const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();      ui->api->setEnabled(runtime_lock); -    ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend)); -    ui->resolution_factor_combobox->setCurrentIndex( -        static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor))); -    ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio); -    ui->use_disk_shader_cache->setEnabled(runtime_lock); -    ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);      ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock); -    ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation); -    UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green, -                                                 Settings::values.bg_blue)); +    ui->use_disk_shader_cache->setEnabled(runtime_lock); +    ui->use_nvdec_emulation->setEnabled(runtime_lock); +    ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue()); +    ui->use_asynchronous_gpu_emulation->setChecked( +        Settings::values.use_asynchronous_gpu_emulation.GetValue()); +    ui->use_nvdec_emulation->setChecked(Settings::values.use_nvdec_emulation.GetValue()); + +    if (Settings::IsConfiguringGlobal()) { +        ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue())); +        ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue()); +    } else { +        ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend); +        ConfigurationShared::SetHighlight(ui->api_layout, +                                          !Settings::values.renderer_backend.UsingGlobal()); +        ConfigurationShared::SetPerGameSetting(ui->aspect_ratio_combobox, +                                               &Settings::values.aspect_ratio); + +        ui->bg_combobox->setCurrentIndex(Settings::values.bg_red.UsingGlobal() ? 0 : 1); +        ui->bg_button->setEnabled(!Settings::values.bg_red.UsingGlobal()); +        ConfigurationShared::SetHighlight(ui->ar_label, +                                          !Settings::values.aspect_ratio.UsingGlobal()); +        ConfigurationShared::SetHighlight(ui->bg_layout, !Settings::values.bg_red.UsingGlobal()); +    } + +    UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red.GetValue(), +                                                 Settings::values.bg_green.GetValue(), +                                                 Settings::values.bg_blue.GetValue()));      UpdateDeviceComboBox();  }  void ConfigureGraphics::ApplyConfiguration() { -    Settings::values.renderer_backend = GetCurrentGraphicsBackend(); -    Settings::values.vulkan_device = vulkan_device; -    Settings::values.resolution_factor = -        ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex())); -    Settings::values.aspect_ratio = ui->aspect_ratio_combobox->currentIndex(); -    Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked(); -    Settings::values.use_asynchronous_gpu_emulation = -        ui->use_asynchronous_gpu_emulation->isChecked(); -    Settings::values.bg_red = static_cast<float>(bg_color.redF()); -    Settings::values.bg_green = static_cast<float>(bg_color.greenF()); -    Settings::values.bg_blue = static_cast<float>(bg_color.blueF()); +    if (Settings::IsConfiguringGlobal()) { +        // Guard if during game and set to game-specific value +        if (Settings::values.renderer_backend.UsingGlobal()) { +            Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend()); +        } +        if (Settings::values.vulkan_device.UsingGlobal()) { +            Settings::values.vulkan_device.SetValue(vulkan_device); +        } +        if (Settings::values.aspect_ratio.UsingGlobal()) { +            Settings::values.aspect_ratio.SetValue(ui->aspect_ratio_combobox->currentIndex()); +        } +        if (Settings::values.use_disk_shader_cache.UsingGlobal()) { +            Settings::values.use_disk_shader_cache.SetValue(ui->use_disk_shader_cache->isChecked()); +        } +        if (Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()) { +            Settings::values.use_asynchronous_gpu_emulation.SetValue( +                ui->use_asynchronous_gpu_emulation->isChecked()); +        } +        if (Settings::values.use_nvdec_emulation.UsingGlobal()) { +            Settings::values.use_nvdec_emulation.SetValue(ui->use_nvdec_emulation->isChecked()); +        } +        if (Settings::values.bg_red.UsingGlobal()) { +            Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF())); +            Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF())); +            Settings::values.bg_blue.SetValue(static_cast<float>(bg_color.blueF())); +        } +    } else { +        if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { +            Settings::values.renderer_backend.SetGlobal(true); +            Settings::values.vulkan_device.SetGlobal(true); +        } else { +            Settings::values.renderer_backend.SetGlobal(false); +            Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend()); +            if (GetCurrentGraphicsBackend() == Settings::RendererBackend::Vulkan) { +                Settings::values.vulkan_device.SetGlobal(false); +                Settings::values.vulkan_device.SetValue(vulkan_device); +            } else { +                Settings::values.vulkan_device.SetGlobal(true); +            } +        } + +        ConfigurationShared::ApplyPerGameSetting(&Settings::values.aspect_ratio, +                                                 ui->aspect_ratio_combobox); + +        ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_disk_shader_cache, +                                                 ui->use_disk_shader_cache, use_disk_shader_cache); +        ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation, +                                                 ui->use_asynchronous_gpu_emulation, +                                                 use_asynchronous_gpu_emulation); +        ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_nvdec_emulation, +                                                 ui->use_nvdec_emulation, use_nvdec_emulation); + +        if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { +            Settings::values.bg_red.SetGlobal(true); +            Settings::values.bg_green.SetGlobal(true); +            Settings::values.bg_blue.SetGlobal(true); +        } else { +            Settings::values.bg_red.SetGlobal(false); +            Settings::values.bg_green.SetGlobal(false); +            Settings::values.bg_blue.SetGlobal(false); +            Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF())); +            Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF())); +            Settings::values.bg_blue.SetValue(static_cast<float>(bg_color.blueF())); +        } +    }  }  void ConfigureGraphics::changeEvent(QEvent* event) { @@ -151,19 +193,27 @@ void ConfigureGraphics::UpdateDeviceComboBox() {      ui->device->clear();      bool enabled = false; + +    if (!Settings::IsConfiguringGlobal() && +        ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { +        vulkan_device = Settings::values.vulkan_device.GetValue(); +    }      switch (GetCurrentGraphicsBackend()) {      case Settings::RendererBackend::OpenGL:          ui->device->addItem(tr("OpenGL Graphics Device"));          enabled = false;          break;      case Settings::RendererBackend::Vulkan: -        for (const auto device : vulkan_devices) { +        for (const auto& device : vulkan_devices) {              ui->device->addItem(device);          }          ui->device->setCurrentIndex(vulkan_device);          enabled = !vulkan_devices.empty();          break;      } +    // If in per-game config and use global is selected, don't enable. +    enabled &= !(!Settings::IsConfiguringGlobal() && +                 ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX);      ui->device->setEnabled(enabled && !Core::System::GetInstance().IsPoweredOn());  } @@ -177,5 +227,48 @@ void ConfigureGraphics::RetrieveVulkanDevices() {  }  Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { -    return static_cast<Settings::RendererBackend>(ui->api->currentIndex()); +    if (Settings::IsConfiguringGlobal()) { +        return static_cast<Settings::RendererBackend>(ui->api->currentIndex()); +    } + +    if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { +        Settings::values.renderer_backend.SetGlobal(true); +        return Settings::values.renderer_backend.GetValue(); +    } +    Settings::values.renderer_backend.SetGlobal(false); +    return static_cast<Settings::RendererBackend>(ui->api->currentIndex() - +                                                  ConfigurationShared::USE_GLOBAL_OFFSET); +} + +void ConfigureGraphics::SetupPerGameUI() { +    if (Settings::IsConfiguringGlobal()) { +        ui->api->setEnabled(Settings::values.renderer_backend.UsingGlobal()); +        ui->device->setEnabled(Settings::values.renderer_backend.UsingGlobal()); +        ui->aspect_ratio_combobox->setEnabled(Settings::values.aspect_ratio.UsingGlobal()); +        ui->use_asynchronous_gpu_emulation->setEnabled( +            Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()); +        ui->use_nvdec_emulation->setEnabled(Settings::values.use_nvdec_emulation.UsingGlobal()); +        ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal()); +        ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal()); + +        return; +    } + +    connect(ui->bg_combobox, qOverload<int>(&QComboBox::activated), this, [this](int index) { +        ui->bg_button->setEnabled(index == 1); +        ConfigurationShared::SetHighlight(ui->bg_layout, index == 1); +    }); + +    ConfigurationShared::SetColoredTristate( +        ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache); +    ConfigurationShared::SetColoredTristate( +        ui->use_nvdec_emulation, Settings::values.use_nvdec_emulation, use_nvdec_emulation); +    ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation, +                                            Settings::values.use_asynchronous_gpu_emulation, +                                            use_asynchronous_gpu_emulation); + +    ConfigurationShared::SetColoredComboBox(ui->aspect_ratio_combobox, ui->ar_label, +                                            Settings::values.aspect_ratio.GetValue(true)); +    ConfigurationShared::InsertGlobalItem( +        ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true)));  } diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index 7e0596d9c..1fefc88eb 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -10,6 +10,10 @@  #include <QWidget>  #include "core/settings.h" +namespace ConfigurationShared { +enum class CheckState; +} +  namespace Ui {  class ConfigureGraphics;  } @@ -35,11 +39,17 @@ private:      void RetrieveVulkanDevices(); +    void SetupPerGameUI(); +      Settings::RendererBackend GetCurrentGraphicsBackend() const;      std::unique_ptr<Ui::ConfigureGraphics> ui;      QColor bg_color; +    ConfigurationShared::CheckState use_nvdec_emulation; +    ConfigurationShared::CheckState use_disk_shader_cache; +    ConfigurationShared::CheckState use_asynchronous_gpu_emulation; +      std::vector<QString> vulkan_devices;      u32 vulkan_device{};  }; diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index c816d6108..58486eb1e 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -6,7 +6,7 @@     <rect>      <x>0</x>      <y>0</y> -    <width>400</width> +    <width>437</width>      <height>321</height>     </rect>    </property> @@ -23,43 +23,56 @@         </property>         <layout class="QVBoxLayout" name="verticalLayout_3">          <item> -         <layout class="QHBoxLayout" name="horizontalLayout_4"> -          <item> -           <widget class="QLabel" name="label_2"> -            <property name="text"> -             <string>API:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QComboBox" name="api"> -            <item> +         <widget class="QWidget" name="api_layout" native="true"> +          <layout class="QGridLayout" name="gridLayout"> +           <property name="leftMargin"> +            <number>0</number> +           </property> +           <property name="topMargin"> +            <number>0</number> +           </property> +           <property name="rightMargin"> +            <number>0</number> +           </property> +           <property name="bottomMargin"> +            <number>0</number> +           </property> +           <property name="horizontalSpacing"> +            <number>6</number> +           </property> +           <item row="0" column="0"> +            <widget class="QLabel" name="api_label">               <property name="text"> -              <string notr="true">OpenGL</string> +              <string>API:</string>               </property> -            </item> -            <item> +            </widget> +           </item> +           <item row="0" column="1"> +            <widget class="QComboBox" name="api"> +             <item> +              <property name="text"> +               <string notr="true">OpenGL</string> +              </property> +             </item> +             <item> +              <property name="text"> +               <string notr="true">Vulkan</string> +              </property> +             </item> +            </widget> +           </item> +           <item row="1" column="0"> +            <widget class="QLabel" name="device_label">               <property name="text"> -              <string notr="true">Vulkan</string> +              <string>Device:</string>               </property> -            </item> -           </widget> -          </item> -         </layout> -        </item> -        <item> -         <layout class="QHBoxLayout" name="horizontalLayout_5"> -          <item> -           <widget class="QLabel" name="label_3"> -            <property name="text"> -             <string>Device:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QComboBox" name="device"/> -          </item> -         </layout> +            </widget> +           </item> +           <item row="1" column="1"> +            <widget class="QComboBox" name="device"/> +           </item> +          </layout> +         </widget>          </item>         </layout>        </widget> @@ -85,100 +98,140 @@           </widget>          </item>          <item> -         <layout class="QHBoxLayout" name="horizontalLayout_2"> -          <item> -           <widget class="QLabel" name="label"> -            <property name="text"> -             <string>Internal Resolution:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QComboBox" name="resolution_factor_combobox"> -            <item> -             <property name="text"> -              <string>Auto (Window Size)</string> -             </property> -            </item> -            <item> +         <widget class="QCheckBox" name="use_nvdec_emulation"> +          <property name="text"> +           <string>Use NVDEC emulation</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QWidget" name="aspect_ratio_layout" native="true"> +          <layout class="QHBoxLayout" name="horizontalLayout_6"> +           <property name="leftMargin"> +            <number>0</number> +           </property> +           <property name="topMargin"> +            <number>0</number> +           </property> +           <property name="rightMargin"> +            <number>0</number> +           </property> +           <property name="bottomMargin"> +            <number>0</number> +           </property> +           <item> +            <widget class="QLabel" name="ar_label">               <property name="text"> -              <string>Native (1280x720)</string> +              <string>Aspect Ratio:</string>               </property> -            </item> -            <item> -             <property name="text"> -              <string>2x Native (2560x1440)</string> +            </widget> +           </item> +           <item> +            <widget class="QComboBox" name="aspect_ratio_combobox"> +             <item> +              <property name="text"> +               <string>Default (16:9)</string> +              </property> +             </item> +             <item> +              <property name="text"> +               <string>Force 4:3</string> +              </property> +             </item> +             <item> +              <property name="text"> +               <string>Force 21:9</string> +              </property> +             </item> +             <item> +              <property name="text"> +               <string>Stretch to Window</string> +              </property> +             </item> +            </widget> +           </item> +          </layout> +         </widget> +        </item> +        <item> +         <widget class="QWidget" name="bg_layout" native="true"> +          <property name="sizePolicy"> +           <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> +            <horstretch>0</horstretch> +            <verstretch>0</verstretch> +           </sizepolicy> +          </property> +          <layout class="QHBoxLayout" name="horizontalLayout_3"> +           <property name="spacing"> +            <number>6</number> +           </property> +           <property name="leftMargin"> +            <number>0</number> +           </property> +           <property name="topMargin"> +            <number>0</number> +           </property> +           <property name="rightMargin"> +            <number>0</number> +           </property> +           <property name="bottomMargin"> +            <number>0</number> +           </property> +           <item> +            <widget class="QComboBox" name="bg_combobox"> +             <property name="currentText"> +              <string>Use global background color</string>               </property> -            </item> -            <item> -             <property name="text"> -              <string>3x Native (3840x2160)</string> +             <property name="currentIndex"> +              <number>0</number>               </property> -            </item> -            <item> -             <property name="text"> -              <string>4x Native (5120x2880)</string> +             <property name="maxVisibleItems"> +              <number>10</number>               </property> -            </item> -           </widget> -          </item> -         </layout> -        </item> -        <item> -         <layout class="QHBoxLayout" name="horizontalLayout_6"> -          <item> -           <widget class="QLabel" name="ar_label"> -            <property name="text"> -             <string>Aspect Ratio:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QComboBox" name="aspect_ratio_combobox"> -            <item> +             <item> +              <property name="text"> +               <string>Use global background color</string> +              </property> +             </item> +             <item> +              <property name="text"> +               <string>Set background color:</string> +              </property> +             </item> +            </widget> +           </item> +           <item> +            <widget class="QLabel" name="bg_label">               <property name="text"> -              <string>Default (16:9)</string> +              <string>Background Color:</string>               </property> -            </item> -            <item> -             <property name="text"> -              <string>Force 4:3</string> +            </widget> +           </item> +           <item> +            <spacer name="horizontalSpacer"> +             <property name="orientation"> +              <enum>Qt::Horizontal</enum>               </property> -            </item> -            <item> -             <property name="text"> -              <string>Force 21:9</string> +             <property name="sizeHint" stdset="0"> +              <size> +               <width>40</width> +               <height>20</height> +              </size>               </property> -            </item> -            <item> -             <property name="text"> -              <string>Stretch to Window</string> +            </spacer> +           </item> +           <item> +            <widget class="QPushButton" name="bg_button"> +             <property name="maximumSize"> +              <size> +               <width>40</width> +               <height>16777215</height> +              </size>               </property> -            </item> -           </widget> -          </item> -         </layout> -        </item> -        <item> -         <layout class="QHBoxLayout" name="horizontalLayout_3"> -          <item> -           <widget class="QLabel" name="bg_label"> -            <property name="text"> -             <string>Background Color:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="bg_button"> -            <property name="maximumSize"> -             <size> -              <width>40</width> -              <height>16777215</height> -             </size> -            </property> -           </widget> -          </item> -         </layout> +            </widget> +           </item> +          </layout> +         </widget>          </item>         </layout>        </widget> diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index b9f429f84..383c7bac8 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -5,6 +5,7 @@  #include "core/core.h"  #include "core/settings.h"  #include "ui_configure_graphics_advanced.h" +#include "yuzu/configuration/configuration_shared.h"  #include "yuzu/configuration/configure_graphics_advanced.h"  ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(QWidget* parent) @@ -12,6 +13,8 @@ ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(QWidget* parent)      ui->setupUi(this); +    SetupPerGameUI(); +      SetConfiguration();  } @@ -19,20 +22,86 @@ ConfigureGraphicsAdvanced::~ConfigureGraphicsAdvanced() = default;  void ConfigureGraphicsAdvanced::SetConfiguration() {      const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); -    ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);      ui->use_vsync->setEnabled(runtime_lock); -    ui->use_vsync->setChecked(Settings::values.use_vsync); -    ui->force_30fps_mode->setEnabled(runtime_lock); -    ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode); +    ui->use_assembly_shaders->setEnabled(runtime_lock); +    ui->use_asynchronous_shaders->setEnabled(runtime_lock);      ui->anisotropic_filtering_combobox->setEnabled(runtime_lock); -    ui->anisotropic_filtering_combobox->setCurrentIndex(Settings::values.max_anisotropy); + +    ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue()); +    ui->use_assembly_shaders->setChecked(Settings::values.use_assembly_shaders.GetValue()); +    ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue()); +    ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); + +    if (Settings::IsConfiguringGlobal()) { +        ui->gpu_accuracy->setCurrentIndex( +            static_cast<int>(Settings::values.gpu_accuracy.GetValue())); +        ui->anisotropic_filtering_combobox->setCurrentIndex( +            Settings::values.max_anisotropy.GetValue()); +    } else { +        ConfigurationShared::SetPerGameSetting(ui->gpu_accuracy, &Settings::values.gpu_accuracy); +        ConfigurationShared::SetPerGameSetting(ui->anisotropic_filtering_combobox, +                                               &Settings::values.max_anisotropy); +        ConfigurationShared::SetHighlight(ui->label_gpu_accuracy, +                                          !Settings::values.gpu_accuracy.UsingGlobal()); +        ConfigurationShared::SetHighlight(ui->af_label, +                                          !Settings::values.max_anisotropy.UsingGlobal()); +    }  }  void ConfigureGraphicsAdvanced::ApplyConfiguration() { -    Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked(); -    Settings::values.use_vsync = ui->use_vsync->isChecked(); -    Settings::values.force_30fps_mode = ui->force_30fps_mode->isChecked(); -    Settings::values.max_anisotropy = ui->anisotropic_filtering_combobox->currentIndex(); +    // Subtract 2 if configuring per-game (separator and "use global configuration" take 2 slots) +    const auto gpu_accuracy = static_cast<Settings::GPUAccuracy>( +        ui->gpu_accuracy->currentIndex() - +        ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET)); + +    if (Settings::IsConfiguringGlobal()) { +        // Must guard in case of a during-game configuration when set to be game-specific. +        if (Settings::values.gpu_accuracy.UsingGlobal()) { +            Settings::values.gpu_accuracy.SetValue(gpu_accuracy); +        } +        if (Settings::values.use_vsync.UsingGlobal()) { +            Settings::values.use_vsync.SetValue(ui->use_vsync->isChecked()); +        } +        if (Settings::values.use_assembly_shaders.UsingGlobal()) { +            Settings::values.use_assembly_shaders.SetValue(ui->use_assembly_shaders->isChecked()); +        } +        if (Settings::values.use_asynchronous_shaders.UsingGlobal()) { +            Settings::values.use_asynchronous_shaders.SetValue( +                ui->use_asynchronous_shaders->isChecked()); +        } +        if (Settings::values.use_asynchronous_shaders.UsingGlobal()) { +            Settings::values.use_asynchronous_shaders.SetValue( +                ui->use_asynchronous_shaders->isChecked()); +        } +        if (Settings::values.use_fast_gpu_time.UsingGlobal()) { +            Settings::values.use_fast_gpu_time.SetValue(ui->use_fast_gpu_time->isChecked()); +        } +        if (Settings::values.max_anisotropy.UsingGlobal()) { +            Settings::values.max_anisotropy.SetValue( +                ui->anisotropic_filtering_combobox->currentIndex()); +        } +    } else { +        ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, +                                                 ui->anisotropic_filtering_combobox); +        ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync, +                                                 use_vsync); +        ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_assembly_shaders, +                                                 ui->use_assembly_shaders, use_assembly_shaders); +        ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders, +                                                 ui->use_asynchronous_shaders, +                                                 use_asynchronous_shaders); +        ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, +                                                 ui->use_fast_gpu_time, use_fast_gpu_time); +        ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, +                                                 ui->anisotropic_filtering_combobox); + +        if (ui->gpu_accuracy->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { +            Settings::values.gpu_accuracy.SetGlobal(true); +        } else { +            Settings::values.gpu_accuracy.SetGlobal(false); +            Settings::values.gpu_accuracy.SetValue(gpu_accuracy); +        } +    }  }  void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) { @@ -46,3 +115,34 @@ void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) {  void ConfigureGraphicsAdvanced::RetranslateUI() {      ui->retranslateUi(this);  } + +void ConfigureGraphicsAdvanced::SetupPerGameUI() { +    // Disable if not global (only happens during game) +    if (Settings::IsConfiguringGlobal()) { +        ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal()); +        ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal()); +        ui->use_assembly_shaders->setEnabled(Settings::values.use_assembly_shaders.UsingGlobal()); +        ui->use_asynchronous_shaders->setEnabled( +            Settings::values.use_asynchronous_shaders.UsingGlobal()); +        ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal()); +        ui->anisotropic_filtering_combobox->setEnabled( +            Settings::values.max_anisotropy.UsingGlobal()); + +        return; +    } + +    ConfigurationShared::SetColoredTristate(ui->use_vsync, Settings::values.use_vsync, use_vsync); +    ConfigurationShared::SetColoredTristate( +        ui->use_assembly_shaders, Settings::values.use_assembly_shaders, use_assembly_shaders); +    ConfigurationShared::SetColoredTristate(ui->use_asynchronous_shaders, +                                            Settings::values.use_asynchronous_shaders, +                                            use_asynchronous_shaders); +    ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time, +                                            Settings::values.use_fast_gpu_time, use_fast_gpu_time); +    ConfigurationShared::SetColoredComboBox( +        ui->gpu_accuracy, ui->label_gpu_accuracy, +        static_cast<int>(Settings::values.gpu_accuracy.GetValue(true))); +    ConfigurationShared::SetColoredComboBox( +        ui->anisotropic_filtering_combobox, ui->af_label, +        static_cast<int>(Settings::values.max_anisotropy.GetValue(true))); +} diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h index bbc9d4355..e61b571c7 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.h +++ b/src/yuzu/configuration/configure_graphics_advanced.h @@ -7,6 +7,10 @@  #include <memory>  #include <QWidget> +namespace ConfigurationShared { +enum class CheckState; +} +  namespace Ui {  class ConfigureGraphicsAdvanced;  } @@ -26,5 +30,12 @@ private:      void SetConfiguration(); +    void SetupPerGameUI(); +      std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui; + +    ConfigurationShared::CheckState use_vsync; +    ConfigurationShared::CheckState use_assembly_shaders; +    ConfigurationShared::CheckState use_asynchronous_shaders; +    ConfigurationShared::CheckState use_fast_gpu_time;  }; diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index 42eec278e..846a30586 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.ui +++ b/src/yuzu/configuration/configure_graphics_advanced.ui @@ -6,7 +6,7 @@     <rect>      <x>0</x>      <y>0</y> -    <width>400</width> +    <width>404</width>      <height>321</height>     </rect>    </property> @@ -23,10 +23,47 @@         </property>         <layout class="QVBoxLayout" name="verticalLayout_3">          <item> -         <widget class="QCheckBox" name="use_accurate_gpu_emulation"> -          <property name="text"> -           <string>Use accurate GPU emulation (slow)</string> -          </property> +         <widget class="QWidget" name="gpu_accuracy_layout" native="true"> +          <layout class="QHBoxLayout" name="horizontalLayout_2"> +           <property name="leftMargin"> +            <number>0</number> +           </property> +           <property name="topMargin"> +            <number>0</number> +           </property> +           <property name="rightMargin"> +            <number>0</number> +           </property> +           <property name="bottomMargin"> +            <number>0</number> +           </property> +           <item> +            <widget class="QLabel" name="label_gpu_accuracy"> +             <property name="text"> +              <string>Accuracy Level:</string> +             </property> +            </widget> +           </item> +           <item> +            <widget class="QComboBox" name="gpu_accuracy"> +             <item> +              <property name="text"> +               <string notr="true">Normal</string> +              </property> +             </item> +             <item> +              <property name="text"> +               <string notr="true">High</string> +              </property> +             </item> +             <item> +              <property name="text"> +               <string notr="true">Extreme(very slow)</string> +              </property> +             </item> +            </widget> +           </item> +          </layout>           </widget>          </item>          <item> @@ -40,51 +77,85 @@           </widget>          </item>          <item> -         <widget class="QCheckBox" name="force_30fps_mode"> +         <widget class="QCheckBox" name="use_assembly_shaders"> +          <property name="toolTip"> +           <string>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</string> +          </property>            <property name="text"> -           <string>Force 30 FPS mode</string> +           <string>Use assembly shaders (experimental, Nvidia OpenGL only)</string>            </property>           </widget>          </item>          <item> -         <layout class="QHBoxLayout" name="horizontalLayout_1"> -          <item> -           <widget class="QLabel" name="af_label"> -            <property name="text"> -             <string>Anisotropic Filtering:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QComboBox" name="anisotropic_filtering_combobox"> -            <item> -             <property name="text"> -              <string>Default</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>2x</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>4x</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>8x</string> -             </property> -            </item> -            <item> +         <widget class="QCheckBox" name="use_asynchronous_shaders"> +          <property name="toolTip"> +           <string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string> +          </property> +          <property name="text"> +           <string>Use asynchronous shader building (experimental)</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QCheckBox" name="use_fast_gpu_time"> +          <property name="text"> +           <string>Use Fast GPU Time</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QWidget" name="af_layout" native="true"> +          <layout class="QHBoxLayout" name="horizontalLayout_1"> +           <property name="leftMargin"> +            <number>0</number> +           </property> +           <property name="topMargin"> +            <number>0</number> +           </property> +           <property name="rightMargin"> +            <number>0</number> +           </property> +           <property name="bottomMargin"> +            <number>0</number> +           </property> +           <item> +            <widget class="QLabel" name="af_label">               <property name="text"> -              <string>16x</string> +              <string>Anisotropic Filtering:</string>               </property> -            </item> -           </widget> -          </item> -         </layout> +            </widget> +           </item> +           <item> +            <widget class="QComboBox" name="anisotropic_filtering_combobox"> +             <item> +              <property name="text"> +               <string>Default</string> +              </property> +             </item> +             <item> +              <property name="text"> +               <string>2x</string> +              </property> +             </item> +             <item> +              <property name="text"> +               <string>4x</string> +              </property> +             </item> +             <item> +              <property name="text"> +               <string>8x</string> +              </property> +             </item> +             <item> +              <property name="text"> +               <string>16x</string> +              </property> +             </item> +            </widget> +           </item> +          </layout> +         </widget>          </item>         </layout>        </widget> diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp index fa9052136..cbee51a5e 100644 --- a/src/yuzu/configuration/configure_hotkeys.cpp +++ b/src/yuzu/configuration/configure_hotkeys.cpp @@ -2,10 +2,12 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <QMenu>  #include <QMessageBox>  #include <QStandardItemModel>  #include "core/settings.h"  #include "ui_configure_hotkeys.h" +#include "yuzu/configuration/config.h"  #include "yuzu/configuration/configure_hotkeys.h"  #include "yuzu/hotkeys.h"  #include "yuzu/util/sequence_dialog/sequence_dialog.h" @@ -19,6 +21,9 @@ ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)      model->setColumnCount(3);      connect(ui->hotkey_list, &QTreeView::doubleClicked, this, &ConfigureHotkeys::Configure); +    connect(ui->hotkey_list, &QTreeView::customContextMenuRequested, this, +            &ConfigureHotkeys::PopupContextMenu); +    ui->hotkey_list->setContextMenuPolicy(Qt::CustomContextMenu);      ui->hotkey_list->setModel(model);      // TODO(Kloen): Make context configurable as well (hiding the column for now) @@ -27,6 +32,10 @@ ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)      ui->hotkey_list->setColumnWidth(0, 200);      ui->hotkey_list->resizeColumnToContents(1); +    connect(ui->button_restore_defaults, &QPushButton::clicked, this, +            &ConfigureHotkeys::RestoreDefaults); +    connect(ui->button_clear_all, &QPushButton::clicked, this, &ConfigureHotkeys::ClearAll); +      RetranslateUI();  } @@ -71,7 +80,6 @@ void ConfigureHotkeys::Configure(QModelIndex index) {      }      index = index.sibling(index.row(), 1); -    auto* const model = ui->hotkey_list->model();      const auto previous_key = model->data(index);      SequenceDialog hotkey_dialog{this}; @@ -81,31 +89,33 @@ void ConfigureHotkeys::Configure(QModelIndex index) {      if (return_code == QDialog::Rejected || key_sequence.isEmpty()) {          return;      } +    const auto [key_sequence_used, used_action] = IsUsedKey(key_sequence); -    if (IsUsedKey(key_sequence) && key_sequence != QKeySequence(previous_key.toString())) { -        QMessageBox::warning(this, tr("Conflicting Key Sequence"), -                             tr("The entered key sequence is already assigned to another hotkey.")); +    if (key_sequence_used && key_sequence != QKeySequence(previous_key.toString())) { +        QMessageBox::warning( +            this, tr("Conflicting Key Sequence"), +            tr("The entered key sequence is already assigned to: %1").arg(used_action));      } else {          model->setData(index, key_sequence.toString(QKeySequence::NativeText));      }  } -bool ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const { -    for (int r = 0; r < model->rowCount(); r++) { +std::pair<bool, QString> ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const { +    for (int r = 0; r < model->rowCount(); ++r) {          const QStandardItem* const parent = model->item(r, 0); -        for (int r2 = 0; r2 < parent->rowCount(); r2++) { +        for (int r2 = 0; r2 < parent->rowCount(); ++r2) {              const QStandardItem* const key_seq_item = parent->child(r2, 1);              const auto key_seq_str = key_seq_item->text();              const auto key_seq = QKeySequence::fromString(key_seq_str, QKeySequence::NativeText);              if (key_sequence == key_seq) { -                return true; +                return std::make_pair(true, parent->child(r2, 0)->text());              }          }      } -    return false; +    return std::make_pair(false, QString());  }  void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) { @@ -128,3 +138,55 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {      registry.SaveHotkeys();  } + +void ConfigureHotkeys::RestoreDefaults() { +    for (int r = 0; r < model->rowCount(); ++r) { +        const QStandardItem* parent = model->item(r, 0); + +        for (int r2 = 0; r2 < parent->rowCount(); ++r2) { +            model->item(r, 0)->child(r2, 1)->setText(Config::default_hotkeys[r2].shortcut.first); +        } +    } +} + +void ConfigureHotkeys::ClearAll() { +    for (int r = 0; r < model->rowCount(); ++r) { +        const QStandardItem* parent = model->item(r, 0); + +        for (int r2 = 0; r2 < parent->rowCount(); ++r2) { +            model->item(r, 0)->child(r2, 1)->setText(QString{}); +        } +    } +} + +void ConfigureHotkeys::PopupContextMenu(const QPoint& menu_location) { +    QModelIndex index = ui->hotkey_list->indexAt(menu_location); +    if (!index.parent().isValid()) { +        return; +    } + +    const auto selected = index.sibling(index.row(), 1); +    QMenu context_menu; + +    QAction* restore_default = context_menu.addAction(tr("Restore Default")); +    QAction* clear = context_menu.addAction(tr("Clear")); + +    connect(restore_default, &QAction::triggered, [this, selected] { +        const QKeySequence& default_key_sequence = QKeySequence::fromString( +            Config::default_hotkeys[selected.row()].shortcut.first, QKeySequence::NativeText); +        const auto [key_sequence_used, used_action] = IsUsedKey(default_key_sequence); + +        if (key_sequence_used && +            default_key_sequence != QKeySequence(model->data(selected).toString())) { + +            QMessageBox::warning( +                this, tr("Conflicting Key Sequence"), +                tr("The default key sequence is already assigned to: %1").arg(used_action)); +        } else { +            model->setData(selected, default_key_sequence.toString(QKeySequence::NativeText)); +        } +    }); +    connect(clear, &QAction::triggered, [this, selected] { model->setData(selected, QString{}); }); + +    context_menu.exec(ui->hotkey_list->viewport()->mapToGlobal(menu_location)); +} diff --git a/src/yuzu/configuration/configure_hotkeys.h b/src/yuzu/configuration/configure_hotkeys.h index 8f8c6173b..a2ec3323e 100644 --- a/src/yuzu/configuration/configure_hotkeys.h +++ b/src/yuzu/configuration/configure_hotkeys.h @@ -35,7 +35,11 @@ private:      void RetranslateUI();      void Configure(QModelIndex index); -    bool IsUsedKey(QKeySequence key_sequence) const; +    std::pair<bool, QString> IsUsedKey(QKeySequence key_sequence) const; + +    void RestoreDefaults(); +    void ClearAll(); +    void PopupContextMenu(const QPoint& menu_location);      std::unique_ptr<Ui::ConfigureHotkeys> ui; diff --git a/src/yuzu/configuration/configure_hotkeys.ui b/src/yuzu/configuration/configure_hotkeys.ui index 0d0b70f38..6d9f861e3 100644 --- a/src/yuzu/configuration/configure_hotkeys.ui +++ b/src/yuzu/configuration/configure_hotkeys.ui @@ -6,8 +6,8 @@     <rect>      <x>0</x>      <y>0</y> -    <width>363</width> -    <height>388</height> +    <width>439</width> +    <height>510</height>     </rect>    </property>    <property name="windowTitle"> @@ -15,7 +15,7 @@    </property>    <layout class="QVBoxLayout" name="verticalLayout">     <item> -    <layout class="QVBoxLayout" name="verticalLayout_2"> +    <layout class="QHBoxLayout" name="horizontalLayout">       <item>        <widget class="QLabel" name="label_2">         <property name="text"> @@ -24,6 +24,37 @@        </widget>       </item>       <item> +      <spacer name="horizontalSpacer"> +       <property name="orientation"> +        <enum>Qt::Horizontal</enum> +       </property> +       <property name="sizeHint" stdset="0"> +        <size> +         <width>40</width> +         <height>20</height> +        </size> +       </property> +      </spacer> +     </item> +     <item> +      <widget class="QPushButton" name="button_clear_all"> +       <property name="text"> +        <string>Clear All</string> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QPushButton" name="button_restore_defaults"> +       <property name="text"> +        <string>Restore Defaults</string> +       </property> +      </widget> +     </item> +    </layout> +   </item> +   <item> +    <layout class="QVBoxLayout" name="verticalLayout_2"> +     <item>        <widget class="QTreeView" name="hotkey_list">         <property name="editTriggers">          <set>QAbstractItemView::NoEditTriggers</set> @@ -39,4 +70,4 @@   </widget>   <resources/>   <connections/> -</ui>
\ No newline at end of file +</ui> diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index f2977719c..d9009091b 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -8,18 +8,35 @@  #include <QSignalBlocker>  #include <QTimer> -#include "configuration/configure_touchscreen_advanced.h"  #include "core/core.h"  #include "core/hle/service/am/am.h"  #include "core/hle/service/am/applet_ae.h"  #include "core/hle/service/am/applet_oe.h" -#include "core/hle/service/hid/controllers/npad.h"  #include "core/hle/service/sm/sm.h"  #include "ui_configure_input.h" +#include "ui_configure_input_advanced.h"  #include "ui_configure_input_player.h" +#include "yuzu/configuration/configure_debug_controller.h"  #include "yuzu/configuration/configure_input.h" +#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_mouse_advanced.h" +#include "yuzu/configuration/configure_touchscreen_advanced.h" +#include "yuzu/configuration/configure_vibration.h" +#include "yuzu/configuration/input_profiles.h" + +namespace { +template <typename Dialog, typename... Args> +void CallConfigureDialog(ConfigureInput& parent, Args&&... args) { +    Dialog dialog(&parent, std::forward<Args>(args)...); + +    const auto res = dialog.exec(); +    if (res == QDialog::Accepted) { +        dialog.ApplyConfiguration(); +    } +} +} // Anonymous namespace  void OnDockedModeChanged(bool last_state, bool new_state) {      if (last_state == new_state) { @@ -48,97 +65,134 @@ void OnDockedModeChanged(bool last_state, bool new_state) {      }  } -namespace { -template <typename Dialog, typename... Args> -void CallConfigureDialog(ConfigureInput& parent, Args&&... args) { -    parent.ApplyConfiguration(); -    Dialog dialog(&parent, std::forward<Args>(args)...); - -    const auto res = dialog.exec(); -    if (res == QDialog::Accepted) { -        dialog.ApplyConfiguration(); -    } -} -} // Anonymous namespace -  ConfigureInput::ConfigureInput(QWidget* parent) -    : QDialog(parent), ui(std::make_unique<Ui::ConfigureInput>()) { +    : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), +      profiles(std::make_unique<InputProfiles>()) {      ui->setupUi(this); +} + +ConfigureInput::~ConfigureInput() = default; -    players_controller = { -        ui->player1_combobox, ui->player2_combobox, ui->player3_combobox, ui->player4_combobox, -        ui->player5_combobox, ui->player6_combobox, ui->player7_combobox, ui->player8_combobox, +void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, +                                std::size_t max_players) { +    player_controllers = { +        new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem, +                                 profiles.get()), +        new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem, +                                 profiles.get()), +        new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem, +                                 profiles.get()), +        new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem, +                                 profiles.get()), +        new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem, +                                 profiles.get()), +        new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem, +                                 profiles.get()), +        new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem, +                                 profiles.get()), +        new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem, +                                 profiles.get()),      }; -    players_configure = { -        ui->player1_configure, ui->player2_configure, ui->player3_configure, ui->player4_configure, -        ui->player5_configure, ui->player6_configure, ui->player7_configure, ui->player8_configure, +    player_tabs = { +        ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, +        ui->tabPlayer5, ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8,      }; -    RetranslateUI(); -    LoadConfiguration(); -    UpdateUIEnabled(); +    player_connected = { +        ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected, +        ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected, +        ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, +    }; -    connect(ui->restore_defaults_button, &QPushButton::clicked, this, -            &ConfigureInput::RestoreDefaults); +    std::array<QLabel*, 8> player_connected_labels = { +        ui->label,   ui->label_3, ui->label_4, ui->label_5, +        ui->label_6, ui->label_7, ui->label_8, ui->label_9, +    }; -    for (auto* enabled : players_controller) { -        connect(enabled, QOverload<int>::of(&QComboBox::currentIndexChanged), this, -                &ConfigureInput::UpdateUIEnabled); -    } -    connect(ui->use_docked_mode, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled); -    connect(ui->handheld_connected, &QCheckBox::stateChanged, this, -            &ConfigureInput::UpdateUIEnabled); -    connect(ui->mouse_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled); -    connect(ui->keyboard_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled); -    connect(ui->debug_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled); -    connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this, -            &ConfigureInput::UpdateUIEnabled); - -    for (std::size_t i = 0; i < players_configure.size(); ++i) { -        connect(players_configure[i], &QPushButton::clicked, this, -                [this, i] { CallConfigureDialog<ConfigureInputPlayer>(*this, i, false); }); +    for (std::size_t i = 0; i < player_tabs.size(); ++i) { +        player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i])); +        player_tabs[i]->layout()->addWidget(player_controllers[i]); +        connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) { +            if (is_connected) { +                for (std::size_t index = 0; index <= i; ++index) { +                    player_connected[index]->setChecked(is_connected); +                } +            } else { +                for (std::size_t index = i; index < player_tabs.size(); ++index) { +                    player_connected[index]->setChecked(is_connected); +                } +            } +        }); +        connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this, +                &ConfigureInput::UpdateAllInputDevices); +        connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this, +                &ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection); +        connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) { +            player_controllers[i]->ConnectPlayer(state == Qt::Checked); +        }); + +        // Remove/hide all the elements that exceed max_players, if applicable. +        if (i >= max_players) { +            ui->tabWidget->removeTab(static_cast<int>(max_players)); +            player_connected[i]->hide(); +            player_connected_labels[i]->hide(); +        }      } +    // Only the first player can choose handheld mode so connect the signal just to player 1 +    connect(player_controllers[0], &ConfigureInputPlayer::HandheldStateChanged, +            [this](bool is_handheld) { UpdateDockedState(is_handheld); }); + +    advanced = new ConfigureInputAdvanced(this); +    ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced)); +    ui->tabAdvanced->layout()->addWidget(advanced); +    connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] { +        CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem, profiles.get()); +    }); +    connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, [this, input_subsystem] { +        CallConfigureDialog<ConfigureMouseAdvanced>(*this, input_subsystem); +    }); +    connect(advanced, &ConfigureInputAdvanced::CallTouchscreenConfigDialog, +            [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); }); +    connect(advanced, &ConfigureInputAdvanced::CallMotionTouchConfigDialog, +            [this, input_subsystem] { +                CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem); +            }); -    connect(ui->handheld_configure, &QPushButton::clicked, this, -            [this] { CallConfigureDialog<ConfigureInputPlayer>(*this, 8, false); }); +    connect(ui->vibrationButton, &QPushButton::clicked, +            [this] { CallConfigureDialog<ConfigureVibration>(*this); }); -    connect(ui->debug_configure, &QPushButton::clicked, this, -            [this] { CallConfigureDialog<ConfigureInputPlayer>(*this, 9, true); }); +    connect(ui->motionButton, &QPushButton::clicked, [this, input_subsystem] { +        CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem); +    }); -    connect(ui->mouse_advanced, &QPushButton::clicked, this, -            [this] { CallConfigureDialog<ConfigureMouseAdvanced>(*this); }); +    connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); +    connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); -    connect(ui->touchscreen_advanced, &QPushButton::clicked, this, -            [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); }); +    RetranslateUI(); +    LoadConfiguration();  } -ConfigureInput::~ConfigureInput() = default; +QList<QWidget*> ConfigureInput::GetSubTabs() const { +    return { +        ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4,  ui->tabPlayer5, +        ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8, ui->tabAdvanced, +    }; +}  void ConfigureInput::ApplyConfiguration() { -    for (std::size_t i = 0; i < players_controller.size(); ++i) { -        const auto controller_type_index = players_controller[i]->currentIndex(); +    for (auto controller : player_controllers) { +        controller->ApplyConfiguration(); +    } -        Settings::values.players[i].connected = controller_type_index != 0; +    advanced->ApplyConfiguration(); -        if (controller_type_index > 0) { -            Settings::values.players[i].type = -                static_cast<Settings::ControllerType>(controller_type_index - 1); -        } else { -            Settings::values.players[i].type = Settings::ControllerType::DualJoycon; -        } -    } +    const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue(); +    Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked()); +    OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue()); -    const bool pre_docked_mode = Settings::values.use_docked_mode; -    Settings::values.use_docked_mode = ui->use_docked_mode->isChecked(); -    OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); -    Settings::values -        .players[Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD)] -        .connected = ui->handheld_connected->isChecked(); -    Settings::values.debug_pad_enabled = ui->debug_enabled->isChecked(); -    Settings::values.mouse_enabled = ui->mouse_enabled->isChecked(); -    Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked(); -    Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked(); +    Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked()); +    Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());  }  void ConfigureInput::changeEvent(QEvent* event) { @@ -146,94 +200,74 @@ void ConfigureInput::changeEvent(QEvent* event) {          RetranslateUI();      } -    QDialog::changeEvent(event); +    QWidget::changeEvent(event);  }  void ConfigureInput::RetranslateUI() {      ui->retranslateUi(this); -    RetranslateControllerComboBoxes();  } -void ConfigureInput::RetranslateControllerComboBoxes() { -    for (auto* controller_box : players_controller) { -        [[maybe_unused]] const QSignalBlocker blocker(controller_box); - -        controller_box->clear(); -        controller_box->addItems({tr("None"), tr("Pro Controller"), tr("Dual Joycons"), -                                  tr("Single Right Joycon"), tr("Single Left Joycon")}); -    } - +void ConfigureInput::LoadConfiguration() {      LoadPlayerControllerIndices(); +    UpdateDockedState(Settings::values.players.GetValue()[8].connected); + +    ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue()); +    ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());  } -void ConfigureInput::UpdateUIEnabled() { -    bool hit_disabled = false; -    for (auto* player : players_controller) { -        player->setDisabled(hit_disabled); -        if (hit_disabled) { -            player->setCurrentIndex(0); -        } -        if (!hit_disabled && player->currentIndex() == 0) { -            hit_disabled = true; -        } +void ConfigureInput::LoadPlayerControllerIndices() { +    for (std::size_t i = 0; i < player_connected.size(); ++i) { +        const auto connected = Settings::values.players.GetValue()[i].connected || +                               (i == 0 && Settings::values.players.GetValue()[8].connected); +        player_connected[i]->setChecked(connected);      } +} -    for (std::size_t i = 0; i < players_controller.size(); ++i) { -        players_configure[i]->setEnabled(players_controller[i]->currentIndex() != 0); -    } +void ConfigureInput::ClearAll() { +    // We don't have a good way to know what tab is active, but we can find out by getting the +    // parent of the consoleInputSettings +    auto* player_tab = static_cast<ConfigureInputPlayer*>(ui->consoleInputSettings->parent()); +    player_tab->ClearAll(); +} -    ui->handheld_connected->setChecked(ui->handheld_connected->isChecked() && -                                       !ui->use_docked_mode->isChecked()); -    ui->handheld_connected->setEnabled(!ui->use_docked_mode->isChecked()); -    ui->handheld_configure->setEnabled(ui->handheld_connected->isChecked() && -                                       !ui->use_docked_mode->isChecked()); -    ui->mouse_advanced->setEnabled(ui->mouse_enabled->isChecked()); -    ui->debug_configure->setEnabled(ui->debug_enabled->isChecked()); -    ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked()); +void ConfigureInput::RestoreDefaults() { +    // We don't have a good way to know what tab is active, but we can find out by getting the +    // parent of the consoleInputSettings +    auto* player_tab = static_cast<ConfigureInputPlayer*>(ui->consoleInputSettings->parent()); +    player_tab->RestoreDefaults(); + +    ui->radioDocked->setChecked(true); +    ui->radioUndocked->setChecked(false); +    ui->vibrationGroup->setChecked(true); +    ui->motionGroup->setChecked(true);  } -void ConfigureInput::LoadConfiguration() { -    std::stable_partition( -        Settings::values.players.begin(), -        Settings::values.players.begin() + -            Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD), -        [](const auto& player) { return player.connected; }); +void ConfigureInput::UpdateDockedState(bool is_handheld) { +    // Disallow changing the console mode if the controller type is handheld. +    ui->radioDocked->setEnabled(!is_handheld); +    ui->radioUndocked->setEnabled(!is_handheld); -    LoadPlayerControllerIndices(); +    ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue()); +    ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue()); -    ui->use_docked_mode->setChecked(Settings::values.use_docked_mode); -    ui->handheld_connected->setChecked( -        Settings::values -            .players[Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD)] -            .connected); -    ui->debug_enabled->setChecked(Settings::values.debug_pad_enabled); -    ui->mouse_enabled->setChecked(Settings::values.mouse_enabled); -    ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled); -    ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); - -    UpdateUIEnabled(); +    // Also force into undocked mode if the controller type is handheld. +    if (is_handheld) { +        ui->radioUndocked->setChecked(true); +    }  } -void ConfigureInput::LoadPlayerControllerIndices() { -    for (std::size_t i = 0; i < players_controller.size(); ++i) { -        const auto connected = Settings::values.players[i].connected; -        players_controller[i]->setCurrentIndex( -            connected ? static_cast<u8>(Settings::values.players[i].type) + 1 : 0); +void ConfigureInput::UpdateAllInputDevices() { +    for (const auto& player : player_controllers) { +        player->UpdateInputDeviceCombobox();      }  } -void ConfigureInput::RestoreDefaults() { -    players_controller[0]->setCurrentIndex(2); +void ConfigureInput::UpdateAllInputProfiles(std::size_t player_index) { +    for (std::size_t i = 0; i < player_controllers.size(); ++i) { +        if (i == player_index) { +            continue; +        } -    for (std::size_t i = 1; i < players_controller.size(); ++i) { -        players_controller[i]->setCurrentIndex(0); +        player_controllers[i]->UpdateInputProfiles();      } - -    ui->use_docked_mode->setCheckState(Qt::Unchecked); -    ui->handheld_connected->setCheckState(Qt::Unchecked); -    ui->mouse_enabled->setCheckState(Qt::Unchecked); -    ui->keyboard_enabled->setCheckState(Qt::Unchecked); -    ui->debug_enabled->setCheckState(Qt::Unchecked); -    ui->touchscreen_enabled->setCheckState(Qt::Checked); -    UpdateUIEnabled();  } diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index 2f70cb3ca..f4eb0d78b 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -7,37 +7,52 @@  #include <array>  #include <memory> -#include <QDialog>  #include <QKeyEvent> +#include <QList> +#include <QWidget> -#include "ui_configure_input.h" - -class QPushButton; +class QCheckBox;  class QString;  class QTimer; +class ConfigureInputAdvanced; +class ConfigureInputPlayer; + +class InputProfiles; + +namespace InputCommon { +class InputSubsystem; +} +  namespace Ui {  class ConfigureInput;  }  void OnDockedModeChanged(bool last_state, bool new_state); -class ConfigureInput : public QDialog { +class ConfigureInput : public QWidget {      Q_OBJECT  public:      explicit ConfigureInput(QWidget* parent = nullptr);      ~ConfigureInput() override; -    /// Save all button configurations to settings file +    /// Initializes the input dialog with the given input subsystem. +    void Initialize(InputCommon::InputSubsystem* input_subsystem_, std::size_t max_players = 8); + +    /// Save all button configurations to settings file.      void ApplyConfiguration(); +    QList<QWidget*> GetSubTabs() const; +  private:      void changeEvent(QEvent* event) override;      void RetranslateUI(); -    void RetranslateControllerComboBoxes(); +    void ClearAll(); -    void UpdateUIEnabled(); +    void UpdateDockedState(bool is_handheld); +    void UpdateAllInputDevices(); +    void UpdateAllInputProfiles(std::size_t player_index);      /// Load configuration settings.      void LoadConfiguration(); @@ -48,6 +63,10 @@ private:      std::unique_ptr<Ui::ConfigureInput> ui; -    std::array<QComboBox*, 8> players_controller; -    std::array<QPushButton*, 8> players_configure; +    std::unique_ptr<InputProfiles> profiles; + +    std::array<ConfigureInputPlayer*, 8> player_controllers; +    std::array<QWidget*, 8> player_tabs; +    std::array<QCheckBox*, 8> player_connected; +    ConfigureInputAdvanced* advanced;  }; diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index efffd8487..2707025e7 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -1,529 +1,548 @@  <?xml version="1.0" encoding="UTF-8"?>  <ui version="4.0">   <class>ConfigureInput</class> - <widget class="QDialog" name="ConfigureInput"> + <widget class="QWidget" name="ConfigureInput">    <property name="geometry">     <rect>      <x>0</x>      <y>0</y> -    <width>384</width> -    <height>576</height> +    <width>680</width> +    <height>540</height>     </rect>    </property>    <property name="windowTitle"> -   <string>Custom Input Settings</string> +   <string>ConfigureInput</string>    </property>    <layout class="QVBoxLayout" name="verticalLayout_5"> +   <property name="spacing"> +    <number>2</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> -    <layout class="QVBoxLayout" name="verticalLayout"> -     <item> -      <widget class="QGroupBox" name="gridGroupBox_1"> -       <property name="title"> -        <string>Players</string> -       </property> -       <layout class="QGridLayout" name="gridLayout"> -        <item row="1" column="2"> -         <widget class="QComboBox" name="player1_combobox"> -          <property name="minimumSize"> -           <size> -            <width>110</width> -            <height>0</height> -           </size> -          </property> -         </widget> -        </item> -        <item row="1" column="3"> -         <widget class="QPushButton" name="player1_configure"> -          <property name="text"> -           <string>Configure</string> -          </property> -         </widget> -        </item> -        <item row="0" column="2"> -         <widget class="QLabel" name="label"> -          <property name="text"> -           <string>Controller Type</string> -          </property> -          <property name="alignment"> -           <set>Qt::AlignCenter</set> -          </property> -         </widget> -        </item> -        <item row="2" column="2"> -         <widget class="QComboBox" name="player2_combobox"> -          <property name="minimumSize"> -           <size> -            <width>110</width> -            <height>0</height> -           </size> -          </property> -         </widget> -        </item> -        <item row="3" column="2"> -         <widget class="QComboBox" name="player3_combobox"> -          <property name="minimumSize"> -           <size> -            <width>110</width> -            <height>0</height> -           </size> -          </property> -         </widget> -        </item> -        <item row="4" column="2"> -         <widget class="QComboBox" name="player4_combobox"> -          <property name="minimumSize"> -           <size> -            <width>110</width> -            <height>0</height> -           </size> -          </property> -         </widget> -        </item> -        <item row="5" column="2"> -         <widget class="QComboBox" name="player5_combobox"> -          <property name="minimumSize"> -           <size> -            <width>110</width> -            <height>0</height> -           </size> -          </property> -         </widget> -        </item> -        <item row="6" column="2"> -         <widget class="QComboBox" name="player6_combobox"> -          <property name="minimumSize"> -           <size> -            <width>110</width> -            <height>0</height> -           </size> -          </property> -         </widget> -        </item> -        <item row="7" column="2"> -         <widget class="QComboBox" name="player7_combobox"> -          <property name="minimumSize"> -           <size> -            <width>110</width> -            <height>0</height> -           </size> -          </property> -         </widget> -        </item> -        <item row="8" column="2"> -         <widget class="QComboBox" name="player8_combobox"> -          <property name="minimumSize"> -           <size> -            <width>110</width> -            <height>0</height> -           </size> -          </property> -         </widget> -        </item> -        <item row="2" column="3"> -         <widget class="QPushButton" name="player2_configure"> -          <property name="text"> -           <string>Configure</string> -          </property> -         </widget> -        </item> -        <item row="3" column="3"> -         <widget class="QPushButton" name="player3_configure"> -          <property name="text"> -           <string>Configure</string> -          </property> -         </widget> -        </item> -        <item row="4" column="3"> -         <widget class="QPushButton" name="player4_configure"> -          <property name="text"> -           <string>Configure</string> -          </property> -         </widget> -        </item> -        <item row="5" column="3"> -         <widget class="QPushButton" name="player5_configure"> -          <property name="text"> -           <string>Configure</string> -          </property> -         </widget> -        </item> -        <item row="6" column="3"> -         <widget class="QPushButton" name="player6_configure"> -          <property name="text"> -           <string>Configure</string> -          </property> -         </widget> -        </item> -        <item row="7" column="3"> -         <widget class="QPushButton" name="player7_configure"> -          <property name="text"> -           <string>Configure</string> -          </property> -         </widget> -        </item> -        <item row="8" column="3"> -         <widget class="QPushButton" name="player8_configure"> -          <property name="text"> -           <string>Configure</string> -          </property> -         </widget> -        </item> -        <item row="0" column="0"> -         <spacer name="horizontalSpacer"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>40</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -        <item row="0" column="4"> -         <spacer name="horizontalSpacer_2"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>40</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -        <item row="1" column="1"> -         <widget class="QLabel" name="label_3"> -          <property name="minimumSize"> -           <size> -            <width>55</width> -            <height>0</height> -           </size> -          </property> -          <property name="text"> -           <string>Player 1</string> -          </property> -         </widget> -        </item> -        <item row="2" column="1"> -         <widget class="QLabel" name="label_4"> -          <property name="text"> -           <string>Player 2</string> -          </property> -         </widget> -        </item> -        <item row="3" column="1"> -         <widget class="QLabel" name="label_5"> -          <property name="text"> -           <string>Player 3</string> -          </property> -         </widget> -        </item> -        <item row="4" column="1"> -         <widget class="QLabel" name="label_6"> -          <property name="text"> -           <string>Player 4</string> -          </property> -         </widget> -        </item> -        <item row="5" column="1"> -         <widget class="QLabel" name="label_7"> -          <property name="text"> -           <string>Player 5</string> -          </property> -         </widget> -        </item> -        <item row="6" column="1"> -         <widget class="QLabel" name="label_8"> -          <property name="text"> -           <string>Player 6</string> -          </property> -         </widget> -        </item> -        <item row="7" column="1"> -         <widget class="QLabel" name="label_9"> -          <property name="text"> -           <string>Player 7</string> -          </property> -         </widget> -        </item> -        <item row="8" column="1"> -         <widget class="QLabel" name="label_10"> -          <property name="text"> -           <string>Player 8</string> -          </property> -         </widget> -        </item> -       </layout> -      </widget> -     </item> -     <item> -      <widget class="QGroupBox" name="gridGroupBox_2"> -       <property name="title"> -        <string>Handheld</string> -       </property> -       <layout class="QGridLayout" name="gridLayout_2"> -        <item row="1" column="2"> -         <spacer name="horizontalSpacer_5"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeType"> -           <enum>QSizePolicy::Fixed</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>72</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -        <item row="1" column="4"> -         <spacer name="horizontalSpacer_4"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>40</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -        <item row="1" column="3"> -         <widget class="QPushButton" name="handheld_configure"> -          <property name="text"> -           <string>Configure</string> -          </property> -         </widget> -        </item> -        <item row="1" column="0"> -         <spacer name="horizontalSpacer_3"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>40</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -        <item row="1" column="1"> -         <widget class="QCheckBox" name="handheld_connected"> -          <property name="text"> -           <string>Joycons Docked</string> -          </property> -         </widget> -        </item> -        <item row="0" column="1"> -         <widget class="QCheckBox" name="use_docked_mode"> -          <property name="text"> -           <string>Use Docked Mode</string> -          </property> -         </widget> -        </item> -       </layout> -      </widget> -     </item> -     <item> -      <widget class="QGroupBox" name="gridGroupBox_3"> -       <property name="title"> -        <string>Other</string> -       </property> -       <layout class="QGridLayout" name="gridLayout_3"> -        <item row="1" column="1"> -         <widget class="QCheckBox" name="keyboard_enabled"> -          <property name="minimumSize"> -           <size> -            <width>0</width> -            <height>23</height> -           </size> -          </property> -          <property name="text"> -           <string>Keyboard</string> -          </property> -         </widget> -        </item> -        <item row="2" column="1"> -         <widget class="QCheckBox" name="debug_enabled"> -          <property name="text"> -           <string>Debug Controller</string> -          </property> -         </widget> -        </item> -        <item row="3" column="1"> -         <widget class="QCheckBox" name="touchscreen_enabled"> -          <property name="text"> -           <string>Touchscreen</string> -          </property> -         </widget> -        </item> -        <item row="0" column="1"> -         <widget class="QCheckBox" name="mouse_enabled"> -          <property name="minimumSize"> -           <size> -            <width>0</width> -            <height>23</height> -           </size> -          </property> -          <property name="text"> -           <string>Mouse</string> -          </property> -         </widget> -        </item> -        <item row="0" column="4"> -         <spacer name="horizontalSpacer_7"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>40</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -        <item row="0" column="2"> -         <spacer name="horizontalSpacer_8"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeType"> -           <enum>QSizePolicy::Fixed</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>76</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -        <item row="0" column="0"> -         <spacer name="horizontalSpacer_6"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>40</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -        <item row="3" column="3"> -         <widget class="QPushButton" name="touchscreen_advanced"> -          <property name="text"> -           <string>Advanced</string> -          </property> -         </widget> -        </item> -        <item row="2" column="3"> -         <widget class="QPushButton" name="debug_configure"> -          <property name="text"> -           <string>Configure</string> -          </property> -         </widget> -        </item> -        <item row="0" column="3"> -         <widget class="QPushButton" name="mouse_advanced"> -          <property name="text"> -           <string>Advanced</string> -          </property> -         </widget> -        </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> +    <widget class="QTabWidget" name="tabWidget"> +     <property name="currentIndex"> +      <number>0</number> +     </property> +     <widget class="QWidget" name="tabPlayer1"> +      <property name="accessibleName"> +       <string>Player 1</string> +      </property> +      <attribute name="title"> +       <string>Player 1</string> +      </attribute> +     </widget> +     <widget class="QWidget" name="tabPlayer2"> +      <property name="accessibleName"> +       <string>Player 2</string> +      </property> +      <attribute name="title"> +       <string>Player 2</string> +      </attribute> +     </widget> +     <widget class="QWidget" name="tabPlayer3"> +      <property name="accessibleName"> +       <string>Player 3</string> +      </property> +      <attribute name="title"> +       <string>Player 3</string> +      </attribute> +     </widget> +     <widget class="QWidget" name="tabPlayer4"> +      <property name="accessibleName"> +       <string>Player 4</string> +      </property> +      <attribute name="title"> +       <string>Player 4</string> +      </attribute> +     </widget> +     <widget class="QWidget" name="tabPlayer5"> +      <property name="accessibleName"> +       <string>Player 5</string> +      </property> +      <attribute name="title"> +       <string>Player 5</string> +      </attribute> +     </widget> +     <widget class="QWidget" name="tabPlayer6"> +      <property name="accessibleName"> +       <string>Player 6</string> +      </property> +      <attribute name="title"> +       <string>Player 6</string> +      </attribute> +     </widget> +     <widget class="QWidget" name="tabPlayer7"> +      <property name="accessibleName"> +       <string>Player 7</string> +      </property> +      <attribute name="title"> +       <string>Player 7</string> +      </attribute> +     </widget> +     <widget class="QWidget" name="tabPlayer8"> +      <property name="accessibleName"> +       <string>Player 8</string> +      </property> +      <attribute name="title"> +       <string>Player 8</string> +      </attribute> +     </widget> +     <widget class="QWidget" name="tabAdvanced"> +      <property name="accessibleName"> +       <string>Advanced</string> +      </property> +      <attribute name="title"> +       <string>Advanced</string> +      </attribute> +     </widget> +    </widget> +   </item> +   <item alignment="Qt::AlignVCenter"> +    <widget class="QWidget" name="consoleInputSettings" native="true"> +     <layout class="QHBoxLayout" name="buttonsBottomRightHorizontalLayout"> +      <property name="spacing"> +       <number>3</number> +      </property> +      <property name="leftMargin"> +       <number>0</number> +      </property> +      <property name="topMargin"> +       <number>3</number> +      </property> +      <property name="rightMargin"> +       <number>0</number> +      </property> +      <property name="bottomMargin"> +       <number>0</number> +      </property> +      <item alignment="Qt::AlignVCenter"> +       <widget class="QGroupBox" name="handheldGroup"> +        <property name="maximumSize"> +         <size> +          <width>16777215</width> +          <height>16777215</height> +         </size> +        </property> +        <property name="title"> +         <string>Console Mode</string> +        </property> +        <layout class="QHBoxLayout" name="horizontalLayout"> +         <property name="spacing"> +          <number>6</number> +         </property> +         <property name="leftMargin"> +          <number>8</number> +         </property> +         <property name="topMargin"> +          <number>6</number> +         </property> +         <property name="rightMargin"> +          <number>3</number> +         </property> +         <property name="bottomMargin"> +          <number>6</number> +         </property> +         <item> +          <widget class="QRadioButton" name="radioDocked"> +           <property name="text"> +            <string>Docked</string> +           </property> +           <property name="checked"> +            <bool>true</bool> +           </property> +          </widget> +         </item> +         <item> +          <widget class="QRadioButton" name="radioUndocked"> +           <property name="text"> +            <string>Undocked</string> +           </property> +          </widget> +         </item> +        </layout> +       </widget> +      </item> +      <item> +       <widget class="QGroupBox" name="vibrationGroup"> +        <property name="title"> +         <string>Vibration</string> +        </property> +        <property name="checkable"> +         <bool>true</bool> +        </property> +        <layout class="QHBoxLayout" name="horizontalLayout_2"> +         <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="vibrationButton"> +           <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>Configure</string> +           </property> +          </widget> +         </item> +        </layout> +       </widget> +      </item> +      <item> +       <widget class="QGroupBox" name="motionGroup"> +        <property name="title"> +         <string>Motion</string> +        </property> +        <property name="checkable"> +         <bool>true</bool> +        </property> +        <layout class="QHBoxLayout" name="horizontalLayout_3"> +         <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="motionButton"> +           <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>Configure</string> +           </property> +          </widget> +         </item> +        </layout> +       </widget> +      </item> +      <item alignment="Qt::AlignVCenter"> +       <widget class="QWidget" name="connectedControllers" native="true"> +        <layout class="QGridLayout" name="gridLayout_2"> +         <property name="leftMargin"> +          <number>5</number> +         </property> +         <property name="topMargin"> +          <number>0</number>           </property> -        </widget> -       </item> -       <item> -        <spacer name="horizontalSpacer_9"> -         <property name="orientation"> -          <enum>Qt::Horizontal</enum> +         <property name="rightMargin"> +          <number>0</number>           </property> -         <property name="sizeHint" stdset="0"> -          <size> -           <width>40</width> -           <height>20</height> -          </size> +         <property name="bottomMargin"> +          <number>0</number>           </property> -        </spacer> -       </item> -       <item> -        <widget class="QDialogButtonBox" name="buttonBox"> -         <property name="standardButtons"> -          <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +         <property name="spacing"> +          <number>3</number>           </property> -        </widget> -       </item> -      </layout> -     </item> -    </layout> +         <item row="1" column="2"> +          <widget class="QCheckBox" name="checkboxPlayer2Connected"> +           <property name="text"> +            <string/> +           </property> +          </widget> +         </item> +         <item row="1" column="0"> +          <widget class="QLabel" name="label_2"> +           <property name="text"> +            <string>Controllers</string> +           </property> +          </widget> +         </item> +         <item row="1" column="4"> +          <widget class="QCheckBox" name="checkboxPlayer4Connected"> +           <property name="text"> +            <string/> +           </property> +          </widget> +         </item> +         <item row="1" column="3"> +          <widget class="QCheckBox" name="checkboxPlayer3Connected"> +           <property name="text"> +            <string/> +           </property> +          </widget> +         </item> +         <item row="1" column="5"> +          <widget class="QCheckBox" name="checkboxPlayer5Connected"> +           <property name="text"> +            <string/> +           </property> +          </widget> +         </item> +         <item row="0" column="1"> +          <widget class="QLabel" name="label"> +           <property name="text"> +            <string>1</string> +           </property> +           <property name="alignment"> +            <set>Qt::AlignCenter</set> +           </property> +          </widget> +         </item> +         <item row="1" column="7"> +          <widget class="QCheckBox" name="checkboxPlayer7Connected"> +           <property name="text"> +            <string/> +           </property> +          </widget> +         </item> +         <item row="1" column="6"> +          <widget class="QCheckBox" name="checkboxPlayer6Connected"> +           <property name="text"> +            <string/> +           </property> +          </widget> +         </item> +         <item row="1" column="1"> +          <widget class="QCheckBox" name="checkboxPlayer1Connected"> +           <property name="layoutDirection"> +            <enum>Qt::LeftToRight</enum> +           </property> +           <property name="checked"> +            <bool>true</bool> +           </property> +          </widget> +         </item> +         <item row="1" column="8"> +          <widget class="QCheckBox" name="checkboxPlayer8Connected"> +           <property name="text"> +            <string/> +           </property> +          </widget> +         </item> +         <item row="0" column="2"> +          <widget class="QLabel" name="label_3"> +           <property name="text"> +            <string>2</string> +           </property> +           <property name="alignment"> +            <set>Qt::AlignCenter</set> +           </property> +          </widget> +         </item> +         <item row="0" column="3"> +          <widget class="QLabel" name="label_4"> +           <property name="text"> +            <string>3</string> +           </property> +           <property name="alignment"> +            <set>Qt::AlignCenter</set> +           </property> +          </widget> +         </item> +         <item row="0" column="4"> +          <widget class="QLabel" name="label_5"> +           <property name="text"> +            <string>4</string> +           </property> +           <property name="alignment"> +            <set>Qt::AlignCenter</set> +           </property> +          </widget> +         </item> +         <item row="0" column="5"> +          <widget class="QLabel" name="label_6"> +           <property name="text"> +            <string>5</string> +           </property> +           <property name="alignment"> +            <set>Qt::AlignCenter</set> +           </property> +          </widget> +         </item> +         <item row="0" column="6"> +          <widget class="QLabel" name="label_7"> +           <property name="text"> +            <string>6</string> +           </property> +           <property name="alignment"> +            <set>Qt::AlignCenter</set> +           </property> +          </widget> +         </item> +         <item row="0" column="7"> +          <widget class="QLabel" name="label_8"> +           <property name="text"> +            <string>7</string> +           </property> +           <property name="alignment"> +            <set>Qt::AlignCenter</set> +           </property> +          </widget> +         </item> +         <item row="0" column="8"> +          <widget class="QLabel" name="label_9"> +           <property name="text"> +            <string>8</string> +           </property> +           <property name="alignment"> +            <set>Qt::AlignCenter</set> +           </property> +          </widget> +         </item> +         <item row="0" column="0"> +          <widget class="QLabel" name="label_10"> +           <property name="text"> +            <string>Connected</string> +           </property> +          </widget> +         </item> +        </layout> +       </widget> +      </item> +      <item> +       <spacer name="horizontalSpacer"> +        <property name="orientation"> +         <enum>Qt::Horizontal</enum> +        </property> +        <property name="sizeHint" stdset="0"> +         <size> +          <width>40</width> +          <height>20</height> +         </size> +        </property> +       </spacer> +      </item> +      <item alignment="Qt::AlignBottom"> +       <widget class="QPushButton" name="buttonRestoreDefaults"> +        <property name="sizePolicy"> +         <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> +          <horstretch>0</horstretch> +          <verstretch>0</verstretch> +         </sizepolicy> +        </property> +        <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="sizeIncrement"> +         <size> +          <width>0</width> +          <height>0</height> +         </size> +        </property> +        <property name="baseSize"> +         <size> +          <width>0</width> +          <height>0</height> +         </size> +        </property> +        <property name="layoutDirection"> +         <enum>Qt::LeftToRight</enum> +        </property> +        <property name="styleSheet"> +         <string notr="true">min-width: 68px;</string> +        </property> +        <property name="text"> +         <string>Defaults</string> +        </property> +       </widget> +      </item> +      <item alignment="Qt::AlignBottom"> +       <widget class="QPushButton" name="buttonClearAll"> +        <property name="sizePolicy"> +         <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> +          <horstretch>0</horstretch> +          <verstretch>0</verstretch> +         </sizepolicy> +        </property> +        <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="sizeIncrement"> +         <size> +          <width>0</width> +          <height>0</height> +         </size> +        </property> +        <property name="baseSize"> +         <size> +          <width>0</width> +          <height>0</height> +         </size> +        </property> +        <property name="layoutDirection"> +         <enum>Qt::LeftToRight</enum> +        </property> +        <property name="styleSheet"> +         <string notr="true">min-width: 68px;</string> +        </property> +        <property name="text"> +         <string>Clear</string> +        </property> +       </widget> +      </item> +     </layout> +    </widget>     </item>    </layout>   </widget>   <resources/> - <connections> -  <connection> -   <sender>buttonBox</sender> -   <signal>accepted()</signal> -   <receiver>ConfigureInput</receiver> -   <slot>accept()</slot> -   <hints> -    <hint type="sourcelabel"> -     <x>294</x> -     <y>553</y> -    </hint> -    <hint type="destinationlabel"> -     <x>191</x> -     <y>287</y> -    </hint> -   </hints> -  </connection> -  <connection> -   <sender>buttonBox</sender> -   <signal>rejected()</signal> -   <receiver>ConfigureInput</receiver> -   <slot>reject()</slot> -   <hints> -    <hint type="sourcelabel"> -     <x>294</x> -     <y>553</y> -    </hint> -    <hint type="destinationlabel"> -     <x>191</x> -     <y>287</y> -    </hint> -   </hints> -  </connection> - </connections> + <connections/>  </ui> diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp new file mode 100644 index 000000000..abaf03630 --- /dev/null +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -0,0 +1,171 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QColorDialog> +#include "core/core.h" +#include "core/settings.h" +#include "ui_configure_input_advanced.h" +#include "yuzu/configuration/configure_input_advanced.h" + +ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent) +    : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputAdvanced>()) { +    ui->setupUi(this); + +    controllers_color_buttons = {{ +        { +            ui->player1_left_body_button, +            ui->player1_left_buttons_button, +            ui->player1_right_body_button, +            ui->player1_right_buttons_button, +        }, +        { +            ui->player2_left_body_button, +            ui->player2_left_buttons_button, +            ui->player2_right_body_button, +            ui->player2_right_buttons_button, +        }, +        { +            ui->player3_left_body_button, +            ui->player3_left_buttons_button, +            ui->player3_right_body_button, +            ui->player3_right_buttons_button, +        }, +        { +            ui->player4_left_body_button, +            ui->player4_left_buttons_button, +            ui->player4_right_body_button, +            ui->player4_right_buttons_button, +        }, +        { +            ui->player5_left_body_button, +            ui->player5_left_buttons_button, +            ui->player5_right_body_button, +            ui->player5_right_buttons_button, +        }, +        { +            ui->player6_left_body_button, +            ui->player6_left_buttons_button, +            ui->player6_right_body_button, +            ui->player6_right_buttons_button, +        }, +        { +            ui->player7_left_body_button, +            ui->player7_left_buttons_button, +            ui->player7_right_body_button, +            ui->player7_right_buttons_button, +        }, +        { +            ui->player8_left_body_button, +            ui->player8_left_buttons_button, +            ui->player8_right_body_button, +            ui->player8_right_buttons_button, +        }, +    }}; + +    for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) { +        auto& color_buttons = controllers_color_buttons[player_idx]; +        for (std::size_t button_idx = 0; button_idx < color_buttons.size(); ++button_idx) { +            connect(color_buttons[button_idx], &QPushButton::clicked, this, +                    [this, player_idx, button_idx] { +                        OnControllerButtonClick(player_idx, button_idx); +                    }); +        } +    } + +    connect(ui->mouse_enabled, &QCheckBox::stateChanged, this, +            &ConfigureInputAdvanced::UpdateUIEnabled); +    connect(ui->debug_enabled, &QCheckBox::stateChanged, this, +            &ConfigureInputAdvanced::UpdateUIEnabled); +    connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this, +            &ConfigureInputAdvanced::UpdateUIEnabled); + +    connect(ui->debug_configure, &QPushButton::clicked, this, +            [this] { CallDebugControllerDialog(); }); +    connect(ui->mouse_advanced, &QPushButton::clicked, this, [this] { CallMouseConfigDialog(); }); +    connect(ui->touchscreen_advanced, &QPushButton::clicked, this, +            [this] { CallTouchscreenConfigDialog(); }); +    connect(ui->buttonMotionTouch, &QPushButton::clicked, this, +            &ConfigureInputAdvanced::CallMotionTouchConfigDialog); + +    LoadConfiguration(); +} + +ConfigureInputAdvanced::~ConfigureInputAdvanced() = default; + +void ConfigureInputAdvanced::OnControllerButtonClick(std::size_t player_idx, +                                                     std::size_t button_idx) { +    const QColor new_bg_color = QColorDialog::getColor(controllers_colors[player_idx][button_idx]); +    if (!new_bg_color.isValid()) { +        return; +    } +    controllers_colors[player_idx][button_idx] = new_bg_color; +    controllers_color_buttons[player_idx][button_idx]->setStyleSheet( +        QStringLiteral("background-color: %1; min-width: 60px;") +            .arg(controllers_colors[player_idx][button_idx].name())); +} + +void ConfigureInputAdvanced::ApplyConfiguration() { +    for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) { +        auto& player = Settings::values.players.GetValue()[player_idx]; +        std::array<u32, 4> colors{}; +        std::transform(controllers_colors[player_idx].begin(), controllers_colors[player_idx].end(), +                       colors.begin(), [](QColor color) { return color.rgb(); }); + +        player.body_color_left = colors[0]; +        player.button_color_left = colors[1]; +        player.body_color_right = colors[2]; +        player.button_color_right = colors[3]; +    } + +    Settings::values.debug_pad_enabled = ui->debug_enabled->isChecked(); +    Settings::values.mouse_enabled = ui->mouse_enabled->isChecked(); +    Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked(); +    Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked(); +} + +void ConfigureInputAdvanced::LoadConfiguration() { +    for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) { +        auto& player = Settings::values.players.GetValue()[player_idx]; +        std::array<u32, 4> colors = { +            player.body_color_left, +            player.button_color_left, +            player.body_color_right, +            player.button_color_right, +        }; + +        std::transform(colors.begin(), colors.end(), controllers_colors[player_idx].begin(), +                       [](u32 rgb) { return QColor::fromRgb(rgb); }); + +        for (std::size_t button_idx = 0; button_idx < colors.size(); ++button_idx) { +            controllers_color_buttons[player_idx][button_idx]->setStyleSheet( +                QStringLiteral("background-color: %1; min-width: 60px;") +                    .arg(controllers_colors[player_idx][button_idx].name())); +        } +    } + +    ui->debug_enabled->setChecked(Settings::values.debug_pad_enabled); +    ui->mouse_enabled->setChecked(Settings::values.mouse_enabled); +    ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled); +    ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); + +    UpdateUIEnabled(); +} + +void ConfigureInputAdvanced::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QWidget::changeEvent(event); +} + +void ConfigureInputAdvanced::RetranslateUI() { +    ui->retranslateUi(this); +} + +void ConfigureInputAdvanced::UpdateUIEnabled() { +    ui->mouse_advanced->setEnabled(ui->mouse_enabled->isChecked()); +    ui->debug_configure->setEnabled(ui->debug_enabled->isChecked()); +    ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked()); +} diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h new file mode 100644 index 000000000..3083d55c1 --- /dev/null +++ b/src/yuzu/configuration/configure_input_advanced.h @@ -0,0 +1,46 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <memory> +#include <QWidget> + +class QColor; +class QPushButton; + +namespace Ui { +class ConfigureInputAdvanced; +} + +class ConfigureInputAdvanced : public QWidget { +    Q_OBJECT + +public: +    explicit ConfigureInputAdvanced(QWidget* parent = nullptr); +    ~ConfigureInputAdvanced() override; + +    void ApplyConfiguration(); + +signals: +    void CallDebugControllerDialog(); +    void CallMouseConfigDialog(); +    void CallTouchscreenConfigDialog(); +    void CallMotionTouchConfigDialog(); + +private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); +    void UpdateUIEnabled(); + +    void OnControllerButtonClick(std::size_t player_idx, std::size_t button_idx); + +    void LoadConfiguration(); + +    std::unique_ptr<Ui::ConfigureInputAdvanced> ui; + +    std::array<std::array<QColor, 4>, 8> controllers_colors; +    std::array<std::array<QPushButton*, 4>, 8> controllers_color_buttons; +}; diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui new file mode 100644 index 000000000..a880a7c68 --- /dev/null +++ b/src/yuzu/configuration/configure_input_advanced.ui @@ -0,0 +1,2688 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureInputAdvanced</class> + <widget class="QWidget" name="ConfigureInputAdvanced"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>710</width> +    <height>580</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Configure Input</string> +  </property> +  <property name="styleSheet"> +   <string notr="true"/> +  </property> +  <layout class="QHBoxLayout" name="horizontalLayout"> +   <property name="spacing"> +    <number>0</number> +   </property> +   <property name="leftMargin"> +    <number>0</number> +   </property> +   <property name="topMargin"> +    <number>0</number> +   </property> +   <property name="rightMargin"> +    <number>0</number> +   </property> +   <property name="bottomMargin"> +    <number>0</number> +   </property> +   <item> +    <widget class="QWidget" name="mainInputAdvanced" native="true"> +     <layout class="QHBoxLayout" name="main" stretch="1,1"> +      <property name="spacing"> +       <number>9</number> +      </property> +      <property name="leftMargin"> +       <number>0</number> +      </property> +      <property name="topMargin"> +       <number>0</number> +      </property> +      <property name="rightMargin"> +       <number>0</number> +      </property> +      <property name="bottomMargin"> +       <number>0</number> +      </property> +      <item> +       <widget class="QWidget" name="leftInputAdvanced" native="true"> +        <layout class="QVBoxLayout" name="leftLayout" stretch="0"> +         <property name="spacing"> +          <number>3</number> +         </property> +         <property name="leftMargin"> +          <number>0</number> +         </property> +         <property name="topMargin"> +          <number>0</number> +         </property> +         <property name="rightMargin"> +          <number>0</number> +         </property> +         <property name="bottomMargin"> +          <number>0</number> +         </property> +         <item> +          <widget class="QGroupBox" name="joyconColorsGroup"> +           <property name="title"> +            <string>Joycon Colors</string> +           </property> +           <layout class="QVBoxLayout" name="verticalLayout_3" stretch="1,1"> +            <property name="leftMargin"> +             <number>9</number> +            </property> +            <property name="topMargin"> +             <number>9</number> +            </property> +            <property name="rightMargin"> +             <number>9</number> +            </property> +            <property name="bottomMargin"> +             <number>9</number> +            </property> +            <item> +             <widget class="QWidget" name="topLeftInputAdvanced" native="true"> +              <layout class="QVBoxLayout" name="verticalLayout_4"> +               <property name="spacing"> +                <number>6</number> +               </property> +               <property name="leftMargin"> +                <number>0</number> +               </property> +               <property name="topMargin"> +                <number>0</number> +               </property> +               <property name="rightMargin"> +                <number>0</number> +               </property> +               <property name="bottomMargin"> +                <number>0</number> +               </property> +               <item> +                <widget class="QWidget" name="player12Widget" native="true"> +                 <layout class="QHBoxLayout" name="horizontalLayout_4"> +                  <property name="leftMargin"> +                   <number>0</number> +                  </property> +                  <property name="topMargin"> +                   <number>0</number> +                  </property> +                  <property name="rightMargin"> +                   <number>0</number> +                  </property> +                  <property name="bottomMargin"> +                   <number>0</number> +                  </property> +                  <item> +                   <widget class="QGroupBox" name="player1Group"> +                    <property name="title"> +                     <string>Player 1</string> +                    </property> +                    <layout class="QHBoxLayout" name="horizontalLayout_2"> +                     <property name="spacing"> +                      <number>6</number> +                     </property> +                     <property name="leftMargin"> +                      <number>6</number> +                     </property> +                     <property name="topMargin"> +                      <number>0</number> +                     </property> +                     <property name="rightMargin"> +                      <number>6</number> +                     </property> +                     <property name="bottomMargin"> +                      <number>6</number> +                     </property> +                     <item> +                      <widget class="QWidget" name="player1LeftJoycon" native="true"> +                       <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_14"> +                        <property name="spacing"> +                         <number>0</number> +                        </property> +                        <property name="leftMargin"> +                         <number>0</number> +                        </property> +                        <property name="topMargin"> +                         <number>0</number> +                        </property> +                        <property name="rightMargin"> +                         <number>0</number> +                        </property> +                        <property name="bottomMargin"> +                         <number>0</number> +                        </property> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player1LeftBodyGroup"> +                          <property name="title"> +                           <string>L Body</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_66"> +                           <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="player1_left_body_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player1LeftButtonsGroup"> +                          <property name="title"> +                           <string>L Button</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_67"> +                           <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="player1_left_buttons_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                       </layout> +                      </widget> +                     </item> +                     <item> +                      <widget class="QWidget" name="player1RightJoycon" native="true"> +                       <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_14"> +                        <property name="spacing"> +                         <number>0</number> +                        </property> +                        <property name="leftMargin"> +                         <number>0</number> +                        </property> +                        <property name="topMargin"> +                         <number>0</number> +                        </property> +                        <property name="rightMargin"> +                         <number>0</number> +                        </property> +                        <property name="bottomMargin"> +                         <number>0</number> +                        </property> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player1RightBodyGroup"> +                          <property name="title"> +                           <string>R Body</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_64"> +                           <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="player1_right_body_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player1RightButtonsGroup"> +                          <property name="title"> +                           <string>R Button</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_65"> +                           <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="player1_right_buttons_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                       </layout> +                      </widget> +                     </item> +                    </layout> +                   </widget> +                  </item> +                  <item> +                   <widget class="QGroupBox" name="player2Group"> +                    <property name="title"> +                     <string>Player 2</string> +                    </property> +                    <layout class="QHBoxLayout" name="horizontalLayout_14"> +                     <property name="spacing"> +                      <number>6</number> +                     </property> +                     <property name="leftMargin"> +                      <number>6</number> +                     </property> +                     <property name="topMargin"> +                      <number>0</number> +                     </property> +                     <property name="rightMargin"> +                      <number>6</number> +                     </property> +                     <property name="bottomMargin"> +                      <number>6</number> +                     </property> +                     <item> +                      <widget class="QWidget" name="player2LeftJoycon" native="true"> +                       <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_15"> +                        <property name="spacing"> +                         <number>0</number> +                        </property> +                        <property name="leftMargin"> +                         <number>0</number> +                        </property> +                        <property name="topMargin"> +                         <number>0</number> +                        </property> +                        <property name="rightMargin"> +                         <number>0</number> +                        </property> +                        <property name="bottomMargin"> +                         <number>0</number> +                        </property> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player2LeftBodyGroup"> +                          <property name="title"> +                           <string>L Body</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_70"> +                           <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="player2_left_body_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player2LeftButtonsGroup"> +                          <property name="title"> +                           <string>L Button</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_71"> +                           <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="player2_left_buttons_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                       </layout> +                      </widget> +                     </item> +                     <item> +                      <widget class="QWidget" name="player2RightJoycon" native="true"> +                       <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_15"> +                        <property name="spacing"> +                         <number>0</number> +                        </property> +                        <property name="leftMargin"> +                         <number>0</number> +                        </property> +                        <property name="topMargin"> +                         <number>0</number> +                        </property> +                        <property name="rightMargin"> +                         <number>0</number> +                        </property> +                        <property name="bottomMargin"> +                         <number>0</number> +                        </property> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player2RightBodyGroup"> +                          <property name="title"> +                           <string>R Body</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_68"> +                           <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="player2_right_body_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player2RightButtonsGroup"> +                          <property name="title"> +                           <string>R Button</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_69"> +                           <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="player2_right_buttons_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                       </layout> +                      </widget> +                     </item> +                    </layout> +                   </widget> +                  </item> +                 </layout> +                </widget> +               </item> +               <item> +                <widget class="QWidget" name="player34Widget" native="true"> +                 <layout class="QHBoxLayout" name="horizontalLayout_5"> +                  <property name="leftMargin"> +                   <number>0</number> +                  </property> +                  <property name="topMargin"> +                   <number>0</number> +                  </property> +                  <property name="rightMargin"> +                   <number>0</number> +                  </property> +                  <property name="bottomMargin"> +                   <number>0</number> +                  </property> +                  <item> +                   <widget class="QGroupBox" name="player3Group"> +                    <property name="title"> +                     <string>Player 3</string> +                    </property> +                    <layout class="QHBoxLayout" name="horizontalLayout_15"> +                     <property name="spacing"> +                      <number>6</number> +                     </property> +                     <property name="leftMargin"> +                      <number>6</number> +                     </property> +                     <property name="topMargin"> +                      <number>0</number> +                     </property> +                     <property name="rightMargin"> +                      <number>6</number> +                     </property> +                     <property name="bottomMargin"> +                      <number>6</number> +                     </property> +                     <item> +                      <widget class="QWidget" name="player3LeftJoycon" native="true"> +                       <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_16"> +                        <property name="spacing"> +                         <number>0</number> +                        </property> +                        <property name="leftMargin"> +                         <number>0</number> +                        </property> +                        <property name="topMargin"> +                         <number>0</number> +                        </property> +                        <property name="rightMargin"> +                         <number>0</number> +                        </property> +                        <property name="bottomMargin"> +                         <number>0</number> +                        </property> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player3LeftBodyGroup"> +                          <property name="title"> +                           <string>L Body</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_74"> +                           <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="player3_left_body_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player3LeftButtonsGroup"> +                          <property name="title"> +                           <string>L Button</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_75"> +                           <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="player3_left_buttons_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                       </layout> +                      </widget> +                     </item> +                     <item> +                      <widget class="QWidget" name="player3RightJoycon" native="true"> +                       <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_16"> +                        <property name="spacing"> +                         <number>0</number> +                        </property> +                        <property name="leftMargin"> +                         <number>0</number> +                        </property> +                        <property name="topMargin"> +                         <number>0</number> +                        </property> +                        <property name="rightMargin"> +                         <number>0</number> +                        </property> +                        <property name="bottomMargin"> +                         <number>0</number> +                        </property> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player3RightBodyGroup"> +                          <property name="title"> +                           <string>R Body</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_72"> +                           <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="player3_right_body_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player3RightButtonsGroup"> +                          <property name="title"> +                           <string>R Button</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_73"> +                           <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="player3_right_buttons_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                       </layout> +                      </widget> +                     </item> +                    </layout> +                   </widget> +                  </item> +                  <item> +                   <widget class="QGroupBox" name="player4Group"> +                    <property name="title"> +                     <string>Player 4</string> +                    </property> +                    <layout class="QHBoxLayout" name="horizontalLayout_16"> +                     <property name="spacing"> +                      <number>6</number> +                     </property> +                     <property name="leftMargin"> +                      <number>6</number> +                     </property> +                     <property name="topMargin"> +                      <number>0</number> +                     </property> +                     <property name="rightMargin"> +                      <number>6</number> +                     </property> +                     <property name="bottomMargin"> +                      <number>6</number> +                     </property> +                     <item> +                      <widget class="QWidget" name="player4LeftJoycon" native="true"> +                       <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_17"> +                        <property name="spacing"> +                         <number>0</number> +                        </property> +                        <property name="leftMargin"> +                         <number>0</number> +                        </property> +                        <property name="topMargin"> +                         <number>0</number> +                        </property> +                        <property name="rightMargin"> +                         <number>0</number> +                        </property> +                        <property name="bottomMargin"> +                         <number>0</number> +                        </property> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player4LeftBodyGroup"> +                          <property name="title"> +                           <string>L Body</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_78"> +                           <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="player4_left_body_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player4LeftButtonsGroup"> +                          <property name="title"> +                           <string>L Button</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_79"> +                           <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="player4_left_buttons_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                       </layout> +                      </widget> +                     </item> +                     <item> +                      <widget class="QWidget" name="player4RightJoycon" native="true"> +                       <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_17"> +                        <property name="spacing"> +                         <number>0</number> +                        </property> +                        <property name="leftMargin"> +                         <number>0</number> +                        </property> +                        <property name="topMargin"> +                         <number>0</number> +                        </property> +                        <property name="rightMargin"> +                         <number>0</number> +                        </property> +                        <property name="bottomMargin"> +                         <number>0</number> +                        </property> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player4RightBodyGroup"> +                          <property name="title"> +                           <string>R Body</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_76"> +                           <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="player4_right_body_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player4RightButtonsGroup"> +                          <property name="title"> +                           <string>R Button</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_77"> +                           <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="player4_right_buttons_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                       </layout> +                      </widget> +                     </item> +                    </layout> +                   </widget> +                  </item> +                 </layout> +                </widget> +               </item> +              </layout> +             </widget> +            </item> +            <item> +             <widget class="QWidget" name="bottomLeftInputAdvanced" native="true"> +              <layout class="QVBoxLayout" name="verticalLayout_5"> +               <property name="spacing"> +                <number>6</number> +               </property> +               <property name="leftMargin"> +                <number>0</number> +               </property> +               <property name="topMargin"> +                <number>0</number> +               </property> +               <property name="rightMargin"> +                <number>0</number> +               </property> +               <property name="bottomMargin"> +                <number>0</number> +               </property> +               <item> +                <widget class="QWidget" name="player56Widget" native="true"> +                 <layout class="QHBoxLayout" name="horizontalLayout_6"> +                  <property name="leftMargin"> +                   <number>0</number> +                  </property> +                  <property name="topMargin"> +                   <number>0</number> +                  </property> +                  <property name="rightMargin"> +                   <number>0</number> +                  </property> +                  <property name="bottomMargin"> +                   <number>0</number> +                  </property> +                  <item> +                   <widget class="QGroupBox" name="player5Group"> +                    <property name="title"> +                     <string>Player 5</string> +                    </property> +                    <layout class="QHBoxLayout" name="horizontalLayout_10"> +                     <property name="spacing"> +                      <number>6</number> +                     </property> +                     <property name="leftMargin"> +                      <number>6</number> +                     </property> +                     <property name="topMargin"> +                      <number>0</number> +                     </property> +                     <property name="rightMargin"> +                      <number>6</number> +                     </property> +                     <property name="bottomMargin"> +                      <number>6</number> +                     </property> +                     <item> +                      <widget class="QWidget" name="player5LeftJoycon" native="true"> +                       <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_10"> +                        <property name="spacing"> +                         <number>0</number> +                        </property> +                        <property name="leftMargin"> +                         <number>0</number> +                        </property> +                        <property name="topMargin"> +                         <number>0</number> +                        </property> +                        <property name="rightMargin"> +                         <number>0</number> +                        </property> +                        <property name="bottomMargin"> +                         <number>0</number> +                        </property> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player5LeftBodyGroup"> +                          <property name="title"> +                           <string>L Body</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_50"> +                           <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="player5_left_body_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player5LeftButtonsGroup"> +                          <property name="title"> +                           <string>L Button</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_51"> +                           <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="player5_left_buttons_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                       </layout> +                      </widget> +                     </item> +                     <item> +                      <widget class="QWidget" name="player5RightJoycon" native="true"> +                       <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_10"> +                        <property name="spacing"> +                         <number>0</number> +                        </property> +                        <property name="leftMargin"> +                         <number>0</number> +                        </property> +                        <property name="topMargin"> +                         <number>0</number> +                        </property> +                        <property name="rightMargin"> +                         <number>0</number> +                        </property> +                        <property name="bottomMargin"> +                         <number>0</number> +                        </property> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player5RightBodyGroup"> +                          <property name="title"> +                           <string>R Body</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_48"> +                           <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="player5_right_body_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player5RightButtonsGroup"> +                          <property name="title"> +                           <string>R Button</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_49"> +                           <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="player5_right_buttons_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                       </layout> +                      </widget> +                     </item> +                    </layout> +                   </widget> +                  </item> +                  <item> +                   <widget class="QGroupBox" name="player6Group"> +                    <property name="title"> +                     <string>Player 6</string> +                    </property> +                    <layout class="QHBoxLayout" name="horizontalLayout_11"> +                     <property name="spacing"> +                      <number>6</number> +                     </property> +                     <property name="leftMargin"> +                      <number>6</number> +                     </property> +                     <property name="topMargin"> +                      <number>0</number> +                     </property> +                     <property name="rightMargin"> +                      <number>6</number> +                     </property> +                     <property name="bottomMargin"> +                      <number>6</number> +                     </property> +                     <item> +                      <widget class="QWidget" name="player6LeftJoycon" native="true"> +                       <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_11"> +                        <property name="spacing"> +                         <number>0</number> +                        </property> +                        <property name="leftMargin"> +                         <number>0</number> +                        </property> +                        <property name="topMargin"> +                         <number>0</number> +                        </property> +                        <property name="rightMargin"> +                         <number>0</number> +                        </property> +                        <property name="bottomMargin"> +                         <number>0</number> +                        </property> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player6LeftBodyGroup"> +                          <property name="title"> +                           <string>L Body</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_54"> +                           <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="player6_left_body_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player6LeftButtonsGroup"> +                          <property name="title"> +                           <string>L Button</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_55"> +                           <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="player6_left_buttons_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                       </layout> +                      </widget> +                     </item> +                     <item> +                      <widget class="QWidget" name="player6RightJoycon" native="true"> +                       <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_11"> +                        <property name="spacing"> +                         <number>0</number> +                        </property> +                        <property name="leftMargin"> +                         <number>0</number> +                        </property> +                        <property name="topMargin"> +                         <number>0</number> +                        </property> +                        <property name="rightMargin"> +                         <number>0</number> +                        </property> +                        <property name="bottomMargin"> +                         <number>0</number> +                        </property> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player6RightBodyGroup"> +                          <property name="title"> +                           <string>R Body</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_52"> +                           <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="player6_right_body_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player6RightButtonsGroup"> +                          <property name="title"> +                           <string>R Button</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_53"> +                           <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="player6_right_buttons_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                       </layout> +                      </widget> +                     </item> +                    </layout> +                   </widget> +                  </item> +                 </layout> +                </widget> +               </item> +               <item> +                <widget class="QWidget" name="player78Widget" native="true"> +                 <layout class="QHBoxLayout" name="horizontalLayout_7"> +                  <property name="leftMargin"> +                   <number>0</number> +                  </property> +                  <property name="topMargin"> +                   <number>0</number> +                  </property> +                  <property name="rightMargin"> +                   <number>0</number> +                  </property> +                  <property name="bottomMargin"> +                   <number>0</number> +                  </property> +                  <item> +                   <widget class="QGroupBox" name="player7Group"> +                    <property name="title"> +                     <string>Player 7</string> +                    </property> +                    <layout class="QHBoxLayout" name="horizontalLayout_12"> +                     <property name="spacing"> +                      <number>6</number> +                     </property> +                     <property name="leftMargin"> +                      <number>6</number> +                     </property> +                     <property name="topMargin"> +                      <number>0</number> +                     </property> +                     <property name="rightMargin"> +                      <number>6</number> +                     </property> +                     <property name="bottomMargin"> +                      <number>6</number> +                     </property> +                     <item> +                      <widget class="QWidget" name="player7LeftJoycon" native="true"> +                       <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_12"> +                        <property name="spacing"> +                         <number>0</number> +                        </property> +                        <property name="leftMargin"> +                         <number>0</number> +                        </property> +                        <property name="topMargin"> +                         <number>0</number> +                        </property> +                        <property name="rightMargin"> +                         <number>0</number> +                        </property> +                        <property name="bottomMargin"> +                         <number>0</number> +                        </property> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player7LeftBodyGroup"> +                          <property name="title"> +                           <string>L Body</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_58"> +                           <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="player7_left_body_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player7LeftButtonsGroup"> +                          <property name="title"> +                           <string>L Button</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_59"> +                           <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="player7_left_buttons_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                       </layout> +                      </widget> +                     </item> +                     <item> +                      <widget class="QWidget" name="player7RightJoycon" native="true"> +                       <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_12"> +                        <property name="spacing"> +                         <number>0</number> +                        </property> +                        <property name="leftMargin"> +                         <number>0</number> +                        </property> +                        <property name="topMargin"> +                         <number>0</number> +                        </property> +                        <property name="rightMargin"> +                         <number>0</number> +                        </property> +                        <property name="bottomMargin"> +                         <number>0</number> +                        </property> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player7RightBodyGroup"> +                          <property name="title"> +                           <string>R Body</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_56"> +                           <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="player7_right_body_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player7RightButtonsGroup"> +                          <property name="title"> +                           <string>R Button</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_57"> +                           <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="player7_right_buttons_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                       </layout> +                      </widget> +                     </item> +                    </layout> +                   </widget> +                  </item> +                  <item> +                   <widget class="QGroupBox" name="player8Group"> +                    <property name="title"> +                     <string>Player 8</string> +                    </property> +                    <layout class="QHBoxLayout" name="horizontalLayout_13"> +                     <property name="spacing"> +                      <number>6</number> +                     </property> +                     <property name="leftMargin"> +                      <number>6</number> +                     </property> +                     <property name="topMargin"> +                      <number>0</number> +                     </property> +                     <property name="rightMargin"> +                      <number>6</number> +                     </property> +                     <property name="bottomMargin"> +                      <number>6</number> +                     </property> +                     <item> +                      <widget class="QWidget" name="player8LeftJoycon" native="true"> +                       <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_13"> +                        <property name="spacing"> +                         <number>0</number> +                        </property> +                        <property name="leftMargin"> +                         <number>0</number> +                        </property> +                        <property name="topMargin"> +                         <number>0</number> +                        </property> +                        <property name="rightMargin"> +                         <number>0</number> +                        </property> +                        <property name="bottomMargin"> +                         <number>0</number> +                        </property> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player8LeftBodyGroup"> +                          <property name="title"> +                           <string>L Body</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_62"> +                           <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="player8_left_body_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player8LeftButtonsGroup"> +                          <property name="title"> +                           <string>L Button</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_63"> +                           <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="player8_left_buttons_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                       </layout> +                      </widget> +                     </item> +                     <item> +                      <widget class="QWidget" name="player8RightJoycon" native="true"> +                       <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_13"> +                        <property name="spacing"> +                         <number>0</number> +                        </property> +                        <property name="leftMargin"> +                         <number>0</number> +                        </property> +                        <property name="topMargin"> +                         <number>0</number> +                        </property> +                        <property name="rightMargin"> +                         <number>0</number> +                        </property> +                        <property name="bottomMargin"> +                         <number>0</number> +                        </property> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player8RightBodyGroup"> +                          <property name="title"> +                           <string>R Body</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_60"> +                           <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="player8_right_body_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                        <item alignment="Qt::AlignHCenter"> +                         <widget class="QGroupBox" name="player8RightButtonsGroup"> +                          <property name="title"> +                           <string>R Button</string> +                          </property> +                          <property name="alignment"> +                           <set>Qt::AlignCenter</set> +                          </property> +                          <layout class="QVBoxLayout" name="verticalLayout_61"> +                           <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="player8_right_buttons_button"> +                             <property name="sizePolicy"> +                              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +                               <horstretch>0</horstretch> +                               <verstretch>0</verstretch> +                              </sizepolicy> +                             </property> +                             <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/> +                             </property> +                            </widget> +                           </item> +                          </layout> +                         </widget> +                        </item> +                       </layout> +                      </widget> +                     </item> +                    </layout> +                   </widget> +                  </item> +                 </layout> +                </widget> +               </item> +              </layout> +             </widget> +            </item> +           </layout> +          </widget> +         </item> +        </layout> +       </widget> +      </item> +      <item> +       <widget class="QWidget" name="rightInputAdvanced" native="true"> +        <layout class="QVBoxLayout" name="rightLayout" stretch="1,1"> +         <property name="spacing"> +          <number>3</number> +         </property> +         <property name="leftMargin"> +          <number>0</number> +         </property> +         <property name="topMargin"> +          <number>0</number> +         </property> +         <property name="rightMargin"> +          <number>0</number> +         </property> +         <property name="bottomMargin"> +          <number>0</number> +         </property> +         <item> +          <widget class="QWidget" name="topRightInputAdvanced" native="true"> +           <layout class="QVBoxLayout" name="verticalLayout"> +            <property name="leftMargin"> +             <number>0</number> +            </property> +            <property name="topMargin"> +             <number>0</number> +            </property> +            <property name="rightMargin"> +             <number>0</number> +            </property> +            <property name="bottomMargin"> +             <number>0</number> +            </property> +            <item> +             <widget class="QGroupBox" name="gridGroupBox_3"> +              <property name="title"> +               <string>Other</string> +              </property> +              <layout class="QGridLayout" name="gridLayout_3"> +               <item row="0" column="0"> +                <widget class="QCheckBox" name="keyboard_enabled"> +                 <property name="minimumSize"> +                  <size> +                   <width>0</width> +                   <height>23</height> +                  </size> +                 </property> +                 <property name="text"> +                  <string>Keyboard</string> +                 </property> +                </widget> +               </item> +               <item row="4" column="2"> +                <widget class="QPushButton" name="touchscreen_advanced"> +                 <property name="text"> +                  <string>Advanced</string> +                 </property> +                </widget> +               </item> +               <item row="1" column="1"> +                <spacer name="horizontalSpacer_8"> +                 <property name="orientation"> +                  <enum>Qt::Horizontal</enum> +                 </property> +                 <property name="sizeType"> +                  <enum>QSizePolicy::Fixed</enum> +                 </property> +                 <property name="sizeHint" stdset="0"> +                  <size> +                   <width>76</width> +                   <height>20</height> +                  </size> +                 </property> +                </spacer> +               </item> +               <item row="1" column="2"> +                <widget class="QPushButton" name="mouse_advanced"> +                 <property name="text"> +                  <string>Advanced</string> +                 </property> +                </widget> +               </item> +               <item row="4" column="0"> +                <widget class="QCheckBox" name="touchscreen_enabled"> +                 <property name="text"> +                  <string>Touchscreen</string> +                 </property> +                </widget> +               </item> +               <item row="1" column="0"> +                <widget class="QCheckBox" name="mouse_enabled"> +                 <property name="minimumSize"> +                  <size> +                   <width>0</width> +                   <height>23</height> +                  </size> +                 </property> +                 <property name="text"> +                  <string>Mouse</string> +                 </property> +                </widget> +               </item> +               <item row="6" column="0"> +                <widget class="QLabel" name="motion_touch"> +                 <property name="text"> +                  <string>Motion / Touch</string> +                 </property> +                </widget> +               </item> +               <item row="6" column="2"> +                <widget class="QPushButton" name="buttonMotionTouch"> +                 <property name="text"> +                  <string>Configure</string> +                 </property> +                </widget> +               </item> +               <item row="5" column="0"> +                <widget class="QCheckBox" name="debug_enabled"> +                 <property name="text"> +                  <string>Debug Controller</string> +                 </property> +                </widget> +               </item> +               <item row="5" column="2"> +                <widget class="QPushButton" name="debug_configure"> +                 <property name="text"> +                  <string>Configure</string> +                 </property> +                </widget> +               </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> +           </layout> +          </widget> +         </item> +         <item> +          <widget class="QWidget" name="bottomRightInputAdvanced" native="true"> +           <layout class="QVBoxLayout" name="verticalLayout_2"> +            <property name="leftMargin"> +             <number>0</number> +            </property> +            <property name="topMargin"> +             <number>0</number> +            </property> +            <property name="rightMargin"> +             <number>0</number> +            </property> +            <property name="bottomMargin"> +             <number>0</number> +            </property> +            <item> +             <spacer name="mainVerticalSpacer"> +              <property name="orientation"> +               <enum>Qt::Vertical</enum> +              </property> +              <property name="sizeHint" stdset="0"> +               <size> +                <width>20</width> +                <height>40</height> +               </size> +              </property> +             </spacer> +            </item> +           </layout> +          </widget> +         </item> +        </layout> +       </widget> +      </item> +     </layout> +    </widget> +   </item> +  </layout> + </widget> + <resources> + </resources> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 15ac30f12..56ab32a35 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -4,19 +4,28 @@  #include <algorithm>  #include <memory> +#include <thread>  #include <utility> -#include <QColorDialog>  #include <QGridLayout> +#include <QInputDialog>  #include <QKeyEvent>  #include <QMenu>  #include <QMessageBox>  #include <QTimer> -#include "common/assert.h"  #include "common/param_package.h" +#include "core/core.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" +#include "input_common/gcadapter/gc_poller.h"  #include "input_common/main.h" +#include "input_common/udp/udp.h"  #include "ui_configure_input_player.h"  #include "yuzu/configuration/config.h"  #include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_vibration.h" +#include "yuzu/configuration/input_profiles.h" +#include "yuzu/util/limitable_input_dialog.h"  const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>      ConfigureInputPlayer::analog_sub_buttons{{ @@ -24,20 +33,74 @@ const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>          "down",          "left",          "right", -        "modifier",      }}; -static void LayerGridElements(QGridLayout* grid, QWidget* item, QWidget* onTopOf) { -    const int index1 = grid->indexOf(item); -    const int index2 = grid->indexOf(onTopOf); -    int row, column, rowSpan, columnSpan; -    grid->getItemPosition(index2, &row, &column, &rowSpan, &columnSpan); -    grid->takeAt(index1); -    grid->addWidget(item, row, column, rowSpan, columnSpan); +namespace { + +constexpr std::size_t HANDHELD_INDEX = 8; + +void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, +                      bool connected) { +    Core::System& system{Core::System::GetInstance()}; +    if (!system.IsPoweredOn()) { +        return; +    } +    Service::SM::ServiceManager& sm = system.ServiceManager(); + +    auto& npad = +        sm.GetService<Service::HID::Hid>("hid") +            ->GetAppletResource() +            ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad); + +    npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected); +} + +/// Maps the controller type combobox index to Controller Type enum +constexpr Settings::ControllerType GetControllerTypeFromIndex(int index) { +    switch (index) { +    case 0: +    default: +        return Settings::ControllerType::ProController; +    case 1: +        return Settings::ControllerType::DualJoyconDetached; +    case 2: +        return Settings::ControllerType::LeftJoycon; +    case 3: +        return Settings::ControllerType::RightJoycon; +    case 4: +        return Settings::ControllerType::Handheld; +    } +} + +/// Maps the Controller Type enum to controller type combobox index +constexpr int GetIndexFromControllerType(Settings::ControllerType type) { +    switch (type) { +    case Settings::ControllerType::ProController: +    default: +        return 0; +    case Settings::ControllerType::DualJoyconDetached: +        return 1; +    case Settings::ControllerType::LeftJoycon: +        return 2; +    case Settings::ControllerType::RightJoycon: +        return 3; +    case Settings::ControllerType::Handheld: +        return 4; +    }  } -static QString GetKeyName(int key_code) { +QString GetKeyName(int key_code) {      switch (key_code) { +    case Qt::LeftButton: +        return QObject::tr("Click 0"); +    case Qt::RightButton: +        return QObject::tr("Click 1"); +    case Qt::MiddleButton: +        return QObject::tr("Click 2"); +    case Qt::BackButton: +        return QObject::tr("Click 3"); +    case Qt::ForwardButton: +        return QObject::tr("Click 4");      case Qt::Key_Shift:          return QObject::tr("Shift");      case Qt::Key_Control: @@ -51,18 +114,24 @@ static QString GetKeyName(int key_code) {      }  } -static void SetAnalogButton(const Common::ParamPackage& input_param, -                            Common::ParamPackage& analog_param, const std::string& button_name) { -    if (analog_param.Get("engine", "") != "analog_from_button") { +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"}, -            {"modifier_scale", "0.5"},          };      }      analog_param.Set(button_name, input_param.Serialize());  } -static QString ButtonToText(const Common::ParamPackage& param) { +QString ButtonToText(const Common::ParamPackage& param) {      if (!param.Has("engine")) {          return QObject::tr("[not set]");      } @@ -71,6 +140,28 @@ static QString ButtonToText(const Common::ParamPackage& param) {          return GetKeyName(param.Get("code", 0));      } +    if (param.Get("engine", "") == "gcpad") { +        if (param.Has("axis")) { +            const QString axis_str = QString::fromStdString(param.Get("axis", "")); +            const QString direction_str = QString::fromStdString(param.Get("direction", "")); + +            return QObject::tr("GC Axis %1%2").arg(axis_str, direction_str); +        } +        if (param.Has("button")) { +            const QString button_str = QString::number(int(std::log2(param.Get("button", 0)))); +            return QObject::tr("GC Button %1").arg(button_str); +        } +        return GetKeyName(param.Get("code", 0)); +    } + +    if (param.Get("engine", "") == "cemuhookudp") { +        if (param.Has("pad_index")) { +            const QString motion_str = QString::fromStdString(param.Get("pad_index", "")); +            return QObject::tr("Motion %1").arg(motion_str); +        } +        return GetKeyName(param.Get("code", 0)); +    } +      if (param.Get("engine", "") == "sdl") {          if (param.Has("hat")) {              const QString hat_str = QString::fromStdString(param.Get("hat", "")); @@ -98,7 +189,7 @@ static QString ButtonToText(const Common::ParamPackage& param) {      return QObject::tr("[unknown]");  } -static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) { +QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) {      if (!param.Has("engine")) {          return QObject::tr("[not set]");      } @@ -127,24 +218,47 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string          return {};      } +    if (param.Get("engine", "") == "gcpad") { +        if (dir == "modifier") { +            return QObject::tr("[unused]"); +        } + +        if (dir == "left" || dir == "right") { +            const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); + +            return QObject::tr("GC Axis %1").arg(axis_x_str); +        } + +        if (dir == "up" || dir == "down") { +            const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); + +            return QObject::tr("GC Axis %1").arg(axis_y_str); +        } + +        return {}; +    }      return QObject::tr("[unknown]");  } - -ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, bool debug) -    : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index), -      debug(debug), timeout_timer(std::make_unique<QTimer>()), -      poll_timer(std::make_unique<QTimer>()) { +} // namespace + +ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, +                                           QWidget* bottom_row, +                                           InputCommon::InputSubsystem* input_subsystem_, +                                           InputProfiles* profiles_, bool debug) +    : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index), +      debug(debug), input_subsystem{input_subsystem_}, profiles(profiles_), +      timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()), +      bottom_row(bottom_row) {      ui->setupUi(this); +      setFocusPolicy(Qt::ClickFocus);      button_map = { -        ui->buttonA,          ui->buttonB,        ui->buttonX,           ui->buttonY, -        ui->buttonLStick,     ui->buttonRStick,   ui->buttonL,           ui->buttonR, -        ui->buttonZL,         ui->buttonZR,       ui->buttonPlus,        ui->buttonMinus, -        ui->buttonDpadLeft,   ui->buttonDpadUp,   ui->buttonDpadRight,   ui->buttonDpadDown, -        ui->buttonLStickLeft, ui->buttonLStickUp, ui->buttonLStickRight, ui->buttonLStickDown, -        ui->buttonRStickLeft, ui->buttonRStickUp, ui->buttonRStickRight, ui->buttonRStickDown, -        ui->buttonSL,         ui->buttonSR,       ui->buttonHome,        ui->buttonScreenshot, +        ui->buttonA,        ui->buttonB,      ui->buttonX,         ui->buttonY, +        ui->buttonLStick,   ui->buttonRStick, ui->buttonL,         ui->buttonR, +        ui->buttonZL,       ui->buttonZR,     ui->buttonPlus,      ui->buttonMinus, +        ui->buttonDpadLeft, ui->buttonDpadUp, ui->buttonDpadRight, ui->buttonDpadDown, +        ui->buttonSL,       ui->buttonSR,     ui->buttonHome,      ui->buttonScreenshot,      };      analog_map_buttons = {{ @@ -153,219 +267,292 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i              ui->buttonLStickDown,              ui->buttonLStickLeft,              ui->buttonLStickRight, -            ui->buttonLStickMod,          },          {              ui->buttonRStickUp,              ui->buttonRStickDown,              ui->buttonRStickLeft,              ui->buttonRStickRight, -            ui->buttonRStickMod,          },      }}; -    debug_hidden = { -        ui->buttonSL,         ui->labelSL, -        ui->buttonSR,         ui->labelSR, -        ui->buttonLStick,     ui->labelLStickPressed, -        ui->buttonRStick,     ui->labelRStickPressed, -        ui->buttonHome,       ui->labelHome, -        ui->buttonScreenshot, ui->labelScreenshot, +    motion_map = { +        ui->buttonMotionLeft, +        ui->buttonMotionRight,      }; -    auto layout = Settings::values.players[player_index].type; -    if (debug) -        layout = Settings::ControllerType::DualJoycon; - -    switch (layout) { -    case Settings::ControllerType::ProController: -    case Settings::ControllerType::DualJoycon: -        layout_hidden = { -            ui->buttonSL, -            ui->labelSL, -            ui->buttonSR, -            ui->labelSR, -        }; -        break; -    case Settings::ControllerType::LeftJoycon: -        layout_hidden = { -            ui->right_body_button, -            ui->right_buttons_button, -            ui->right_body_label, -            ui->right_buttons_label, -            ui->buttonR, -            ui->labelR, -            ui->buttonZR, -            ui->labelZR, -            ui->labelHome, -            ui->buttonHome, -            ui->buttonPlus, -            ui->labelPlus, -            ui->RStick, -            ui->faceButtons, -        }; -        break; -    case Settings::ControllerType::RightJoycon: -        layout_hidden = { -            ui->left_body_button, ui->left_buttons_button, -            ui->left_body_label,  ui->left_buttons_label, -            ui->buttonL,          ui->labelL, -            ui->buttonZL,         ui->labelZL, -            ui->labelScreenshot,  ui->buttonScreenshot, -            ui->buttonMinus,      ui->labelMinus, -            ui->LStick,           ui->Dpad, -        }; -        break; -    } +    analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone}; +    analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone}; +    analog_map_modifier_groupbox = {ui->buttonLStickModGroup, ui->buttonRStickModGroup}; +    analog_map_modifier_button = {ui->buttonLStickMod, ui->buttonRStickMod}; +    analog_map_modifier_label = {ui->labelLStickModifierRange, ui->labelRStickModifierRange}; +    analog_map_modifier_slider = {ui->sliderLStickModifierRange, ui->sliderRStickModifierRange}; +    analog_map_range_groupbox = {ui->buttonLStickRangeGroup, ui->buttonRStickRangeGroup}; +    analog_map_range_spinbox = {ui->spinboxLStickRange, ui->spinboxRStickRange}; + +    const auto ConfigureButtonClick = [&](QPushButton* button, Common::ParamPackage* param, +                                          int default_val, InputCommon::Polling::DeviceType type) { +        connect(button, &QPushButton::clicked, [=, this] { +            HandleClick( +                button, +                [=, this](Common::ParamPackage params) { +                    // Workaround for ZL & ZR for analog triggers like on XBOX +                    // controllers. Analog triggers (from controllers like the XBOX +                    // controller) would not work due to a different range of their +                    // signals (from 0 to 255 on analog triggers instead of -32768 to +                    // 32768 on analog joysticks). The SDL driver misinterprets analog +                    // triggers as analog joysticks. +                    // TODO: reinterpret the signal range for analog triggers to map the +                    // values correctly. This is required for the correct emulation of +                    // the analog triggers of the GameCube controller. +                    if (button == ui->buttonZL || button == ui->buttonZR) { +                        params.Set("direction", "+"); +                        params.Set("threshold", "0.5"); +                    } +                    *param = std::move(params); +                }, +                type); +        }); +    }; -    if (debug || layout == Settings::ControllerType::ProController) { -        ui->controller_color->hide(); -    } else { -        if (layout == Settings::ControllerType::LeftJoycon || -            layout == Settings::ControllerType::RightJoycon) { -            ui->horizontalSpacer_4->setGeometry({0, 0, 0, 0}); +    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { +        auto* const button = button_map[button_id]; -            LayerGridElements(ui->buttons, ui->shoulderButtons, ui->Dpad); -            LayerGridElements(ui->buttons, ui->misc, ui->RStick); -            LayerGridElements(ui->buttons, ui->Dpad, ui->faceButtons); -            LayerGridElements(ui->buttons, ui->RStick, ui->LStick); +        if (button == nullptr) { +            continue;          } -    } -    for (auto* widget : layout_hidden) -        widget->setVisible(false); +        ConfigureButtonClick(button_map[button_id], &buttons_param[button_id], +                             Config::default_buttons[button_id], +                             InputCommon::Polling::DeviceType::Button); -    analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog}; -    analog_map_deadzone = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone}; -    analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone}; +        button->setContextMenuPolicy(Qt::CustomContextMenu); -    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { -        auto* const button = button_map[button_id]; +        connect(button, &QPushButton::customContextMenuRequested, +                [=, this](const QPoint& menu_location) { +                    QMenu context_menu; +                    context_menu.addAction(tr("Clear"), [&] { +                        buttons_param[button_id].Clear(); +                        button_map[button_id]->setText(tr("[not set]")); +                    }); +                    context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); +                }); +    } + +    for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { +        auto* const button = motion_map[motion_id];          if (button == nullptr) {              continue;          } +        ConfigureButtonClick(motion_map[motion_id], &motions_param[motion_id], +                             Config::default_motions[motion_id], +                             InputCommon::Polling::DeviceType::Motion); +          button->setContextMenuPolicy(Qt::CustomContextMenu); -        connect(button, &QPushButton::clicked, [=] { -            HandleClick(button_map[button_id], -                        [=](Common::ParamPackage params) { -                            // Workaround for ZL & ZR for analog triggers like on XBOX controllors. -                            // Analog triggers (from controllers like the XBOX controller) would not -                            // work due to a different range of their signals (from 0 to 255 on -                            // analog triggers instead of -32768 to 32768 on analog joysticks). The -                            // SDL driver misinterprets analog triggers as analog joysticks. -                            // TODO: reinterpret the signal range for analog triggers to map the -                            // values correctly. This is required for the correct emulation of the -                            // analog triggers of the GameCube controller. -                            if (button_id == Settings::NativeButton::ZL || -                                button_id == Settings::NativeButton::ZR) { -                                params.Set("direction", "+"); -                                params.Set("threshold", "0.5"); -                            } -                            buttons_param[button_id] = std::move(params); -                        }, -                        InputCommon::Polling::DeviceType::Button); -        }); -        connect(button, &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) { -            QMenu context_menu; -            context_menu.addAction(tr("Clear"), [&] { -                buttons_param[button_id].Clear(); -                button_map[button_id]->setText(tr("[not set]")); -            }); -            context_menu.addAction(tr("Restore Default"), [&] { -                buttons_param[button_id] = Common::ParamPackage{ -                    InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; -                button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); -            }); -            context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); -        }); + +        connect(button, &QPushButton::customContextMenuRequested, +                [=, this](const QPoint& menu_location) { +                    QMenu context_menu; +                    context_menu.addAction(tr("Clear"), [&] { +                        motions_param[motion_id].Clear(); +                        motion_map[motion_id]->setText(tr("[not set]")); +                    }); +                    context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location)); +                });      } -    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { -        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { +    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { +        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {              auto* const analog_button = analog_map_buttons[analog_id][sub_button_id]; +              if (analog_button == nullptr) {                  continue;              } -            analog_button->setContextMenuPolicy(Qt::CustomContextMenu); -            connect(analog_button, &QPushButton::clicked, [=]() { -                HandleClick(analog_map_buttons[analog_id][sub_button_id], -                            [=](const Common::ParamPackage& params) { -                                SetAnalogButton(params, analogs_param[analog_id], -                                                analog_sub_buttons[sub_button_id]); -                            }, -                            InputCommon::Polling::DeviceType::Button); +            connect(analog_button, &QPushButton::clicked, [=, this] { +                if (!map_analog_stick_accepted) { +                    map_analog_stick_accepted = +                        QMessageBox::information( +                            this, tr("Map Analog Stick"), +                            tr("After pressing OK, first move your joystick horizontally, and then " +                               "vertically.\nTo invert the axes, first move your joystick " +                               "vertically, and then horizontally."), +                            QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok; +                    if (!map_analog_stick_accepted) { +                        return; +                    } +                } +                HandleClick( +                    analog_map_buttons[analog_id][sub_button_id], +                    [=, this](const Common::ParamPackage& params) { +                        SetAnalogParam(params, analogs_param[analog_id], +                                       analog_sub_buttons[sub_button_id]); +                    }, +                    InputCommon::Polling::DeviceType::AnalogPreferred);              }); + +            analog_button->setContextMenuPolicy(Qt::CustomContextMenu); +              connect(analog_button, &QPushButton::customContextMenuRequested, -                    [=](const QPoint& menu_location) { +                    [=, this](const QPoint& menu_location) {                          QMenu context_menu;                          context_menu.addAction(tr("Clear"), [&] { -                            analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); +                            analogs_param[analog_id].Clear();                              analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));                          }); -                        context_menu.addAction(tr("Restore Default"), [&] { -                            Common::ParamPackage params{InputCommon::GenerateKeyboardParam( -                                Config::default_analogs[analog_id][sub_button_id])}; -                            SetAnalogButton(params, analogs_param[analog_id], -                                            analog_sub_buttons[sub_button_id]); -                            analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText( -                                analogs_param[analog_id], analog_sub_buttons[sub_button_id])); -                        });                          context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(                              menu_location));                      });          } -        connect(analog_map_stick[analog_id], &QPushButton::clicked, [=] { -            if (QMessageBox::information( -                    this, tr("Information"), -                    tr("After pressing OK, first move your joystick horizontally, " -                       "and then vertically."), -                    QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { -                HandleClick( -                    analog_map_stick[analog_id], -                    [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, -                    InputCommon::Polling::DeviceType::Analog); -            } + +        // Handle clicks for the modifier buttons as well. +        connect(analog_map_modifier_button[analog_id], &QPushButton::clicked, [=, this] { +            HandleClick( +                analog_map_modifier_button[analog_id], +                [=, this](const Common::ParamPackage& params) { +                    analogs_param[analog_id].Set("modifier", params.Serialize()); +                }, +                InputCommon::Polling::DeviceType::Button);          }); -        connect(analog_map_deadzone[analog_id], &QSlider::valueChanged, [=] { -            const float deadzone = analog_map_deadzone[analog_id]->value() / 100.0f; -            analog_map_deadzone_label[analog_id]->setText(tr("Deadzone: %1").arg(deadzone)); -            analogs_param[analog_id].Set("deadzone", deadzone); + +        analog_map_modifier_button[analog_id]->setContextMenuPolicy(Qt::CustomContextMenu); + +        connect(analog_map_modifier_button[analog_id], &QPushButton::customContextMenuRequested, +                [=, this](const QPoint& menu_location) { +                    QMenu context_menu; +                    context_menu.addAction(tr("Clear"), [&] { +                        analogs_param[analog_id].Set("modifier", ""); +                        analog_map_modifier_button[analog_id]->setText(tr("[not set]")); +                    }); +                    context_menu.exec( +                        analog_map_modifier_button[analog_id]->mapToGlobal(menu_location)); +                }); + +        connect(analog_map_range_spinbox[analog_id], qOverload<int>(&QSpinBox::valueChanged), +                [=, this] { +                    const auto spinbox_value = analog_map_range_spinbox[analog_id]->value(); +                    analogs_param[analog_id].Set("range", spinbox_value / 100.0f); +                }); + +        connect(analog_map_deadzone_slider[analog_id], &QSlider::valueChanged, [=, this] { +            const auto slider_value = analog_map_deadzone_slider[analog_id]->value(); +            analog_map_deadzone_label[analog_id]->setText(tr("Deadzone: %1%").arg(slider_value)); +            analogs_param[analog_id].Set("deadzone", slider_value / 100.0f); +        }); + +        connect(analog_map_modifier_slider[analog_id], &QSlider::valueChanged, [=, this] { +            const auto slider_value = analog_map_modifier_slider[analog_id]->value(); +            analog_map_modifier_label[analog_id]->setText( +                tr("Modifier Range: %1%").arg(slider_value)); +            analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f);          });      } -    connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); -    connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); +    // Player Connected checkbox +    connect(ui->groupConnectedController, &QGroupBox::toggled, +            [this](bool checked) { emit Connected(checked); }); + +    // Set up controller type. Only Player 1 can choose Handheld. +    ui->comboControllerType->clear(); + +    QStringList controller_types = { +        tr("Pro Controller"), +        tr("Dual Joycons"), +        tr("Left Joycon"), +        tr("Right Joycon"), +    }; + +    if (player_index == 0) { +        controller_types.append(tr("Handheld")); +        connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), +                [this](int index) { +                    emit HandheldStateChanged(GetControllerTypeFromIndex(index) == +                                              Settings::ControllerType::Handheld); +                }); +    } + +    if (debug || player_index == 9) { +        ui->groupConnectedController->setCheckable(false); +    } + +    // The Debug Controller can only choose the Pro Controller. +    if (debug) { +        ui->buttonScreenshot->setEnabled(false); +        ui->buttonHome->setEnabled(false); +        QStringList debug_controller_types = { +            tr("Pro Controller"), +        }; +        ui->comboControllerType->addItems(debug_controller_types); +    } else { +        ui->comboControllerType->addItems(controller_types); +    } + +    UpdateControllerIcon(); +    UpdateControllerAvailableButtons(); +    UpdateMotionButtons(); +    connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) { +        UpdateControllerIcon(); +        UpdateControllerAvailableButtons(); +        UpdateMotionButtons(); +    }); + +    connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this, +            &ConfigureInputPlayer::UpdateMappingWithDefaults); + +    ui->comboDevices->setCurrentIndex(-1); + +    ui->buttonRefreshDevices->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); +    connect(ui->buttonRefreshDevices, &QPushButton::clicked, +            [this] { emit RefreshInputDevices(); });      timeout_timer->setSingleShot(true);      connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });      connect(poll_timer.get(), &QTimer::timeout, [this] {          Common::ParamPackage params; +        if (input_subsystem->GetGCButtons()->IsPolling()) { +            params = input_subsystem->GetGCButtons()->GetNextInput(); +            if (params.Has("engine") && IsInputAcceptable(params)) { +                SetPollingResult(params, false); +                return; +            } +        } +        if (input_subsystem->GetGCAnalogs()->IsPolling()) { +            params = input_subsystem->GetGCAnalogs()->GetNextInput(); +            if (params.Has("engine") && IsInputAcceptable(params)) { +                SetPollingResult(params, false); +                return; +            } +        } +        if (input_subsystem->GetUDPMotions()->IsPolling()) { +            params = input_subsystem->GetUDPMotions()->GetNextInput(); +            if (params.Has("engine")) { +                SetPollingResult(params, false); +                return; +            } +        }          for (auto& poller : device_pollers) {              params = poller->GetNextInput(); -            if (params.Has("engine")) { +            if (params.Has("engine") && IsInputAcceptable(params)) {                  SetPollingResult(params, false);                  return;              }          }      }); -    controller_color_buttons = { -        ui->left_body_button, -        ui->left_buttons_button, -        ui->right_body_button, -        ui->right_buttons_button, -    }; +    UpdateInputProfiles(); -    for (std::size_t i = 0; i < controller_color_buttons.size(); ++i) { -        connect(controller_color_buttons[i], &QPushButton::clicked, this, -                [this, i] { OnControllerButtonClick(static_cast<int>(i)); }); -    } +    connect(ui->buttonProfilesNew, &QPushButton::clicked, this, +            &ConfigureInputPlayer::CreateProfile); +    connect(ui->buttonProfilesDelete, &QPushButton::clicked, this, +            &ConfigureInputPlayer::DeleteProfile); +    connect(ui->comboProfiles, qOverload<int>(&QComboBox::activated), this, +            &ConfigureInputPlayer::LoadProfile); +    connect(ui->buttonProfilesSave, &QPushButton::clicked, this, +            &ConfigureInputPlayer::SaveProfile);      LoadConfiguration(); -    resize(0, 0);      // TODO(wwylele): enable this when we actually emulate it      ui->buttonHome->setEnabled(false); @@ -374,27 +561,72 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i  ConfigureInputPlayer::~ConfigureInputPlayer() = default;  void ConfigureInputPlayer::ApplyConfiguration() { -    auto& buttons = -        debug ? Settings::values.debug_pad_buttons : Settings::values.players[player_index].buttons; -    auto& analogs = -        debug ? Settings::values.debug_pad_analogs : Settings::values.players[player_index].analogs; +    auto& player = Settings::values.players.GetValue()[player_index]; +    auto& buttons = debug ? Settings::values.debug_pad_buttons : player.buttons; +    auto& analogs = debug ? Settings::values.debug_pad_analogs : player.analogs;      std::transform(buttons_param.begin(), buttons_param.end(), buttons.begin(),                     [](const Common::ParamPackage& param) { return param.Serialize(); });      std::transform(analogs_param.begin(), analogs_param.end(), analogs.begin(),                     [](const Common::ParamPackage& param) { return param.Serialize(); }); -    if (debug) +    if (debug) { +        return; +    } + +    auto& motions = player.motions; + +    std::transform(motions_param.begin(), motions_param.end(), motions.begin(), +                   [](const Common::ParamPackage& param) { return param.Serialize(); }); + +    const auto controller_type = +        GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); +    const auto player_connected = ui->groupConnectedController->isChecked() && +                                  controller_type != Settings::ControllerType::Handheld; + +    if (player.controller_type == controller_type && player.connected == player_connected) { +        // Set vibration devices in the event that the input device has changed. +        ConfigureVibration::SetVibrationDevices(player_index); +        return; +    } + +    // Disconnect the controller first. +    UpdateController(controller_type, player_index, false); + +    player.controller_type = controller_type; +    player.connected = player_connected; + +    ConfigureVibration::SetVibrationDevices(player_index); + +    // Handheld +    if (player_index == 0) { +        auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; +        if (controller_type == Settings::ControllerType::Handheld) { +            handheld = player; +        } +        handheld.connected = ui->groupConnectedController->isChecked() && +                             controller_type == Settings::ControllerType::Handheld; +        UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected); +    } + +    if (!player.connected) {          return; +    } -    std::array<u32, 4> colors{}; -    std::transform(controller_colors.begin(), controller_colors.end(), colors.begin(), -                   [](QColor color) { return color.rgb(); }); +    // This emulates a delay between disconnecting and reconnecting controllers as some games +    // do not respond to a change in controller type if it was instantaneous. +    using namespace std::chrono_literals; +    std::this_thread::sleep_for(20ms); -    Settings::values.players[player_index].body_color_left = colors[0]; -    Settings::values.players[player_index].button_color_left = colors[1]; -    Settings::values.players[player_index].body_color_right = colors[2]; -    Settings::values.players[player_index].button_color_right = colors[3]; +    UpdateController(controller_type, player_index, player_connected); +} + +void ConfigureInputPlayer::showEvent(QShowEvent* event) { +    if (bottom_row == nullptr) { +        return; +    } +    QWidget::showEvent(event); +    ui->main->addWidget(bottom_row);  }  void ConfigureInputPlayer::changeEvent(QEvent* event) { @@ -402,24 +634,16 @@ void ConfigureInputPlayer::changeEvent(QEvent* event) {          RetranslateUI();      } -    QDialog::changeEvent(event); +    QWidget::changeEvent(event);  }  void ConfigureInputPlayer::RetranslateUI() {      ui->retranslateUi(this); -    UpdateButtonLabels(); -} - -void ConfigureInputPlayer::OnControllerButtonClick(int i) { -    const QColor new_bg_color = QColorDialog::getColor(controller_colors[i]); -    if (!new_bg_color.isValid()) -        return; -    controller_colors[i] = new_bg_color; -    controller_color_buttons[i]->setStyleSheet( -        QStringLiteral("QPushButton { background-color: %1 }").arg(controller_colors[i].name())); +    UpdateUI();  }  void ConfigureInputPlayer::LoadConfiguration() { +    auto& player = Settings::values.players.GetValue()[player_index];      if (debug) {          std::transform(Settings::values.debug_pad_buttons.begin(),                         Settings::values.debug_pad_buttons.end(), buttons_param.begin(), @@ -428,66 +652,109 @@ void ConfigureInputPlayer::LoadConfiguration() {                         Settings::values.debug_pad_analogs.end(), analogs_param.begin(),                         [](const std::string& str) { return Common::ParamPackage(str); });      } else { -        std::transform(Settings::values.players[player_index].buttons.begin(), -                       Settings::values.players[player_index].buttons.end(), buttons_param.begin(), +        std::transform(player.buttons.begin(), player.buttons.end(), buttons_param.begin(), +                       [](const std::string& str) { return Common::ParamPackage(str); }); +        std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(),                         [](const std::string& str) { return Common::ParamPackage(str); }); -        std::transform(Settings::values.players[player_index].analogs.begin(), -                       Settings::values.players[player_index].analogs.end(), analogs_param.begin(), +        std::transform(player.motions.begin(), player.motions.end(), motions_param.begin(),                         [](const std::string& str) { return Common::ParamPackage(str); });      } -    UpdateButtonLabels(); +    UpdateUI(); +    UpdateInputDeviceCombobox(); -    if (debug) +    if (debug) {          return; +    } -    std::array<u32, 4> colors = { -        Settings::values.players[player_index].body_color_left, -        Settings::values.players[player_index].button_color_left, -        Settings::values.players[player_index].body_color_right, -        Settings::values.players[player_index].button_color_right, -    }; +    ui->comboControllerType->setCurrentIndex(static_cast<int>(player.controller_type)); +    ui->groupConnectedController->setChecked( +        player.connected || +        (player_index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected)); +} -    std::transform(colors.begin(), colors.end(), controller_colors.begin(), -                   [](u32 rgb) { return QColor::fromRgb(rgb); }); +void ConfigureInputPlayer::ConnectPlayer(bool connected) { +    ui->groupConnectedController->setChecked(connected); +} -    for (std::size_t i = 0; i < colors.size(); ++i) { -        controller_color_buttons[i]->setStyleSheet( -            QStringLiteral("QPushButton { background-color: %1 }") -                .arg(controller_colors[i].name())); +void ConfigureInputPlayer::UpdateInputDeviceCombobox() { +    // Skip input device persistence if "Input Devices" is set to "Any". +    if (ui->comboDevices->currentIndex() == 0) { +        UpdateInputDevices(); +        return;      } -} -void ConfigureInputPlayer::RestoreDefaults() { -    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { -        buttons_param[button_id] = Common::ParamPackage{ -            InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; +    // Find the first button that isn't empty. +    const auto button_param = +        std::find_if(buttons_param.begin(), buttons_param.end(), +                     [](const Common::ParamPackage param) { return param.Has("engine"); }); +    const bool buttons_empty = button_param == buttons_param.end(); + +    const auto current_engine = button_param->Get("engine", ""); +    const auto current_guid = button_param->Get("guid", ""); +    const auto current_port = button_param->Get("port", ""); + +    const bool is_keyboard_mouse = current_engine == "keyboard" || current_engine == "mouse"; + +    UpdateInputDevices(); + +    if (buttons_empty) { +        return;      } -    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { -        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { -            Common::ParamPackage params{InputCommon::GenerateKeyboardParam( -                Config::default_analogs[analog_id][sub_button_id])}; -            SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); +    const bool all_one_device = +        std::all_of(buttons_param.begin(), buttons_param.end(), +                    [current_engine, current_guid, current_port, +                     is_keyboard_mouse](const Common::ParamPackage param) { +                        if (is_keyboard_mouse) { +                            return !param.Has("engine") || param.Get("engine", "") == "keyboard" || +                                   param.Get("engine", "") == "mouse"; +                        } +                        return !param.Has("engine") || (param.Get("engine", "") == current_engine && +                                                        param.Get("guid", "") == current_guid && +                                                        param.Get("port", "") == current_port); +                    }); + +    if (all_one_device) { +        if (is_keyboard_mouse) { +            ui->comboDevices->setCurrentIndex(1); +            return;          } +        const auto devices_it = std::find_if( +            input_devices.begin(), input_devices.end(), +            [current_engine, current_guid, current_port](const Common::ParamPackage param) { +                return param.Get("class", "") == current_engine && +                       param.Get("guid", "") == current_guid && +                       param.Get("port", "") == current_port; +            }); +        const int device_index = +            devices_it != input_devices.end() +                ? static_cast<int>(std::distance(input_devices.begin(), devices_it)) +                : 0; +        ui->comboDevices->setCurrentIndex(device_index); +    } else { +        ui->comboDevices->setCurrentIndex(0);      } -    UpdateButtonLabels(); +} + +void ConfigureInputPlayer::RestoreDefaults() { +    UpdateMappingWithDefaults();  }  void ConfigureInputPlayer::ClearAll() { -    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { +    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {          const auto* const button = button_map[button_id]; -        if (button == nullptr || !button->isEnabled()) { +        if (button == nullptr) {              continue;          }          buttons_param[button_id].Clear();      } -    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { -        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { +    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { +        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {              const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id]; -            if (analog_button == nullptr || !analog_button->isEnabled()) { +            if (analog_button == nullptr) {                  continue;              } @@ -495,16 +762,30 @@ void ConfigureInputPlayer::ClearAll() {          }      } -    UpdateButtonLabels(); +    for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { +        const auto* const motion_button = motion_map[motion_id]; +        if (motion_button == nullptr) { +            continue; +        } + +        motions_param[motion_id].Clear(); +    } + +    UpdateUI(); +    UpdateInputDevices();  } -void ConfigureInputPlayer::UpdateButtonLabels() { -    for (int button = 0; button < Settings::NativeButton::NumButtons; button++) { +void ConfigureInputPlayer::UpdateUI() { +    for (int button = 0; button < Settings::NativeButton::NumButtons; ++button) {          button_map[button]->setText(ButtonToText(buttons_param[button]));      } -    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { -        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { +    for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { +        motion_map[motion_id]->setText(ButtonToText(motions_param[motion_id])); +    } + +    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { +        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {              auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];              if (analog_button == nullptr) { @@ -514,80 +795,329 @@ void ConfigureInputPlayer::UpdateButtonLabels() {              analog_button->setText(                  AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));          } -        analog_map_stick[analog_id]->setText(tr("Set Analog Stick")); +        analog_map_modifier_button[analog_id]->setText( +            ButtonToText(Common::ParamPackage{analogs_param[analog_id].Get("modifier", "")})); + +        const auto deadzone_label = analog_map_deadzone_label[analog_id]; +        const auto deadzone_slider = analog_map_deadzone_slider[analog_id]; +        const auto modifier_groupbox = analog_map_modifier_groupbox[analog_id]; +        const auto modifier_label = analog_map_modifier_label[analog_id]; +        const auto modifier_slider = analog_map_modifier_slider[analog_id]; +        const auto range_groupbox = analog_map_range_groupbox[analog_id]; +        const auto range_spinbox = analog_map_range_spinbox[analog_id]; + +        int slider_value;          auto& param = analogs_param[analog_id]; -        auto* const analog_deadzone_slider = analog_map_deadzone[analog_id]; -        auto* const analog_deadzone_label = analog_map_deadzone_label[analog_id]; +        const bool is_controller = +            param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad"; -        if (param.Has("engine") && param.Get("engine", "") == "sdl") { +        if (is_controller) {              if (!param.Has("deadzone")) {                  param.Set("deadzone", 0.1f);              } +            slider_value = static_cast<int>(param.Get("deadzone", 0.1f) * 100); +            deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value)); +            deadzone_slider->setValue(slider_value); +            if (!param.Has("range")) { +                param.Set("range", 1.0f); +            } +            range_spinbox->setValue(static_cast<int>(param.Get("range", 1.0f) * 100)); +        } else { +            if (!param.Has("modifier_scale")) { +                param.Set("modifier_scale", 0.5f); +            } +            slider_value = static_cast<int>(param.Get("modifier_scale", 0.5f) * 100); +            modifier_label->setText(tr("Modifier Range: %1%").arg(slider_value)); +            modifier_slider->setValue(slider_value); +        } -            analog_deadzone_slider->setValue(static_cast<int>(param.Get("deadzone", 0.1f) * 100)); -            analog_deadzone_slider->setVisible(true); -            analog_deadzone_label->setVisible(true); +        deadzone_label->setVisible(is_controller); +        deadzone_slider->setVisible(is_controller); +        modifier_groupbox->setVisible(!is_controller); +        modifier_label->setVisible(!is_controller); +        modifier_slider->setVisible(!is_controller); +        range_groupbox->setVisible(is_controller); +    } +} + +void ConfigureInputPlayer::UpdateInputDevices() { +    input_devices = input_subsystem->GetInputDevices(); +    ui->comboDevices->clear(); +    for (auto device : input_devices) { +        ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {}); +    } +} + +void ConfigureInputPlayer::UpdateControllerIcon() { +    // We aren't using Qt's built in theme support here since we aren't drawing an icon (and its +    // "nonstandard" to use an image through the icon support) +    const QString stylesheet = [this] { +        switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) { +        case Settings::ControllerType::ProController: +            return QStringLiteral("image: url(:/controller/pro_controller%0)"); +        case Settings::ControllerType::DualJoyconDetached: +            return QStringLiteral("image: url(:/controller/dual_joycon%0)"); +        case Settings::ControllerType::LeftJoycon: +            return QStringLiteral("image: url(:/controller/single_joycon_left_vertical%0)"); +        case Settings::ControllerType::RightJoycon: +            return QStringLiteral("image: url(:/controller/single_joycon_right_vertical%0)"); +        case Settings::ControllerType::Handheld: +            return QStringLiteral("image: url(:/controller/handheld%0)"); +        default: +            return QString{}; +        } +    }(); + +    const QString theme = [] { +        if (QIcon::themeName().contains(QStringLiteral("dark"))) { +            return QStringLiteral("_dark"); +        } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { +            return QStringLiteral("_midnight");          } else { -            analog_deadzone_slider->setVisible(false); -            analog_deadzone_label->setVisible(false); +            return QString{};          } +    }(); + +    ui->controllerFrame->setStyleSheet(stylesheet.arg(theme)); +} + +void ConfigureInputPlayer::UpdateControllerAvailableButtons() { +    auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); +    if (debug) { +        layout = Settings::ControllerType::ProController; +    } + +    // List of all the widgets that will be hidden by any of the following layouts that need +    // "unhidden" after the controller type changes +    const std::array<QWidget*, 9> layout_show = { +        ui->buttonShoulderButtonsSLSR, +        ui->horizontalSpacerShoulderButtonsWidget, +        ui->horizontalSpacerShoulderButtonsWidget2, +        ui->buttonShoulderButtonsLeft, +        ui->buttonMiscButtonsMinusScreenshot, +        ui->bottomLeft, +        ui->buttonShoulderButtonsRight, +        ui->buttonMiscButtonsPlusHome, +        ui->bottomRight, +    }; + +    for (auto* widget : layout_show) { +        widget->show(); +    } + +    std::vector<QWidget*> layout_hidden; +    switch (layout) { +    case Settings::ControllerType::ProController: +    case Settings::ControllerType::DualJoyconDetached: +    case Settings::ControllerType::Handheld: +        layout_hidden = { +            ui->buttonShoulderButtonsSLSR, +            ui->horizontalSpacerShoulderButtonsWidget2, +        }; +        break; +    case Settings::ControllerType::LeftJoycon: +        layout_hidden = { +            ui->horizontalSpacerShoulderButtonsWidget2, +            ui->buttonShoulderButtonsRight, +            ui->buttonMiscButtonsPlusHome, +            ui->bottomRight, +        }; +        break; +    case Settings::ControllerType::RightJoycon: +        layout_hidden = { +            ui->horizontalSpacerShoulderButtonsWidget, +            ui->buttonShoulderButtonsLeft, +            ui->buttonMiscButtonsMinusScreenshot, +            ui->bottomLeft, +        }; +        break; +    } + +    for (auto* widget : layout_hidden) { +        widget->hide();      }  } +void ConfigureInputPlayer::UpdateMotionButtons() { +    if (debug) { +        // Motion isn't used with the debug controller, hide both groupboxes. +        ui->buttonMotionLeftGroup->hide(); +        ui->buttonMotionRightGroup->hide(); +        return; +    } + +    // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller. +    switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) { +    case Settings::ControllerType::ProController: +    case Settings::ControllerType::LeftJoycon: +    case Settings::ControllerType::Handheld: +        // Show "Motion 1" and hide "Motion 2". +        ui->buttonMotionLeftGroup->show(); +        ui->buttonMotionRightGroup->hide(); +        break; +    case Settings::ControllerType::RightJoycon: +        // Show "Motion 2" and hide "Motion 1". +        ui->buttonMotionLeftGroup->hide(); +        ui->buttonMotionRightGroup->show(); +        break; +    case Settings::ControllerType::DualJoyconDetached: +    default: +        // Show both "Motion 1/2". +        ui->buttonMotionLeftGroup->show(); +        ui->buttonMotionRightGroup->show(); +        break; +    } +} + +void ConfigureInputPlayer::UpdateMappingWithDefaults() { +    if (ui->comboDevices->currentIndex() == 0) { +        return; +    } + +    if (ui->comboDevices->currentIndex() == 1) { +        // Reset keyboard bindings +        for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { +            buttons_param[button_id] = Common::ParamPackage{ +                InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; +        } +        for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { +            for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { +                Common::ParamPackage params{InputCommon::GenerateKeyboardParam( +                    Config::default_analogs[analog_id][sub_button_id])}; +                SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); +            } + +            analogs_param[analog_id].Set("modifier", InputCommon::GenerateKeyboardParam( +                                                         Config::default_stick_mod[analog_id])); +        } + +        for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { +            motions_param[motion_id] = Common::ParamPackage{ +                InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])}; +        } + +        UpdateUI(); +        return; +    } + +    // Reset controller bindings +    const auto& device = input_devices[ui->comboDevices->currentIndex()]; +    auto button_mapping = input_subsystem->GetButtonMappingForDevice(device); +    auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device); +    for (std::size_t i = 0; i < buttons_param.size(); ++i) { +        buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)]; +    } +    for (std::size_t i = 0; i < analogs_param.size(); ++i) { +        analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)]; +    } + +    UpdateUI(); +} +  void ConfigureInputPlayer::HandleClick(      QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,      InputCommon::Polling::DeviceType type) { -    button->setText(tr("[press key]")); +    if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) { +        button->setText(tr("Shake!")); +    } else { +        button->setText(tr("[waiting]")); +    }      button->setFocus(); -    // Keyboard keys can only be used as button devices -    want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button; -    if (want_keyboard_keys) { -        const auto iter = std::find(button_map.begin(), button_map.end(), button); -        ASSERT(iter != button_map.end()); -        const auto index = std::distance(button_map.begin(), iter); -        ASSERT(index < Settings::NativeButton::NumButtons && index >= 0); -    } +    // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a +    // controller, then they don't want keyboard/mouse input +    want_keyboard_mouse = ui->comboDevices->currentIndex() < 2;      input_setter = new_input_setter; -    device_pollers = InputCommon::Polling::GetPollers(type); +    device_pollers = input_subsystem->GetPollers(type);      for (auto& poller : device_pollers) {          poller->Start();      } -    grabKeyboard(); -    grabMouse(); -    timeout_timer->start(5000); // Cancel after 5 seconds -    poll_timer->start(200);     // Check for new inputs every 200ms +    QWidget::grabMouse(); +    QWidget::grabKeyboard(); + +    if (type == InputCommon::Polling::DeviceType::Button) { +        input_subsystem->GetGCButtons()->BeginConfiguration(); +    } else { +        input_subsystem->GetGCAnalogs()->BeginConfiguration(); +    } + +    if (type == InputCommon::Polling::DeviceType::Motion) { +        input_subsystem->GetUDPMotions()->BeginConfiguration(); +    } + +    timeout_timer->start(2500); // Cancel after 2.5 seconds +    poll_timer->start(50);      // Check for new inputs every 50ms  }  void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) { -    releaseKeyboard(); -    releaseMouse();      timeout_timer->stop();      poll_timer->stop();      for (auto& poller : device_pollers) {          poller->Stop();      } +    QWidget::releaseMouse(); +    QWidget::releaseKeyboard(); + +    input_subsystem->GetGCButtons()->EndConfiguration(); +    input_subsystem->GetGCAnalogs()->EndConfiguration(); + +    input_subsystem->GetUDPMotions()->EndConfiguration(); +      if (!abort) {          (*input_setter)(params);      } -    UpdateButtonLabels(); +    UpdateUI(); +    UpdateInputDeviceCombobox(); +      input_setter = std::nullopt;  } +bool ConfigureInputPlayer::IsInputAcceptable(const Common::ParamPackage& params) const { +    if (ui->comboDevices->currentIndex() == 0) { +        return true; +    } + +    // Keyboard/Mouse +    if (ui->comboDevices->currentIndex() == 1) { +        return params.Get("engine", "") == "keyboard" || params.Get("engine", "") == "mouse"; +    } + +    const auto current_input_device = input_devices[ui->comboDevices->currentIndex()]; +    return params.Get("engine", "") == current_input_device.Get("class", "") && +           params.Get("guid", "") == current_input_device.Get("guid", "") && +           params.Get("port", "") == current_input_device.Get("port", ""); +} + +void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) { +    if (!input_setter || !event) { +        return; +    } + +    if (want_keyboard_mouse) { +        SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())}, +                         false); +    } else { +        // We don't want any mouse buttons, so don't stop polling +        return; +    } + +    SetPollingResult({}, true); +} +  void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {      if (!input_setter || !event) {          return;      }      if (event->key() != Qt::Key_Escape) { -        if (want_keyboard_keys) { +        if (want_keyboard_mouse) {              SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},                               false);          } else { @@ -595,5 +1125,105 @@ void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {              return;          }      } +      SetPollingResult({}, true);  } + +void ConfigureInputPlayer::CreateProfile() { +    const auto profile_name = +        LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20); + +    if (profile_name.isEmpty()) { +        return; +    } + +    if (!InputProfiles::IsProfileNameValid(profile_name.toStdString())) { +        QMessageBox::critical(this, tr("Create Input Profile"), +                              tr("The given profile name is not valid!")); +        return; +    } + +    ApplyConfiguration(); + +    if (!profiles->CreateProfile(profile_name.toStdString(), player_index)) { +        QMessageBox::critical(this, tr("Create Input Profile"), +                              tr("Failed to create the input profile \"%1\"").arg(profile_name)); +        UpdateInputProfiles(); +        emit RefreshInputProfiles(player_index); +        return; +    } + +    emit RefreshInputProfiles(player_index); + +    ui->comboProfiles->addItem(profile_name); +    ui->comboProfiles->setCurrentIndex(ui->comboProfiles->count() - 1); +} + +void ConfigureInputPlayer::DeleteProfile() { +    const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); + +    if (profile_name.isEmpty()) { +        return; +    } + +    if (!profiles->DeleteProfile(profile_name.toStdString())) { +        QMessageBox::critical(this, tr("Delete Input Profile"), +                              tr("Failed to delete the input profile \"%1\"").arg(profile_name)); +        UpdateInputProfiles(); +        emit RefreshInputProfiles(player_index); +        return; +    } + +    emit RefreshInputProfiles(player_index); + +    ui->comboProfiles->removeItem(ui->comboProfiles->currentIndex()); +    ui->comboProfiles->setCurrentIndex(-1); +} + +void ConfigureInputPlayer::LoadProfile() { +    const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); + +    if (profile_name.isEmpty()) { +        return; +    } + +    ApplyConfiguration(); + +    if (!profiles->LoadProfile(profile_name.toStdString(), player_index)) { +        QMessageBox::critical(this, tr("Load Input Profile"), +                              tr("Failed to load the input profile \"%1\"").arg(profile_name)); +        UpdateInputProfiles(); +        emit RefreshInputProfiles(player_index); +        return; +    } + +    LoadConfiguration(); +} + +void ConfigureInputPlayer::SaveProfile() { +    const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); + +    if (profile_name.isEmpty()) { +        return; +    } + +    ApplyConfiguration(); + +    if (!profiles->SaveProfile(profile_name.toStdString(), player_index)) { +        QMessageBox::critical(this, tr("Save Input Profile"), +                              tr("Failed to save the input profile \"%1\"").arg(profile_name)); +        UpdateInputProfiles(); +        emit RefreshInputProfiles(player_index); +        return; +    } +} + +void ConfigureInputPlayer::UpdateInputProfiles() { +    ui->comboProfiles->clear(); + +    for (const auto& profile_name : profiles->GetInputProfileNames()) { +        ui->comboProfiles->addItem(QString::fromStdString(profile_name)); +    } + +    ui->comboProfiles->setCurrentIndex(-1); +} diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index 045704e47..23cf6f958 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -10,16 +10,27 @@  #include <optional>  #include <string> -#include <QDialog> +#include <QWidget>  #include "common/param_package.h"  #include "core/settings.h"  #include "ui_configure_input.h" +class QCheckBox;  class QKeyEvent; +class QLabel;  class QPushButton; +class QSlider; +class QSpinBox;  class QString;  class QTimer; +class QWidget; + +class InputProfiles; + +namespace InputCommon { +class InputSubsystem; +}  namespace InputCommon::Polling {  class DevicePoller; @@ -30,84 +41,165 @@ namespace Ui {  class ConfigureInputPlayer;  } -class ConfigureInputPlayer : public QDialog { +class ConfigureInputPlayer : public QWidget {      Q_OBJECT  public: -    explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, bool debug = false); +    explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row, +                                  InputCommon::InputSubsystem* input_subsystem_, +                                  InputProfiles* profiles_, bool debug = false);      ~ConfigureInputPlayer() override; -    /// Save all button configurations to settings file +    /// Save all button configurations to settings file.      void ApplyConfiguration(); -private: -    void changeEvent(QEvent* event) override; -    void RetranslateUI(); +    /// Set the connection state checkbox (used to sync state). +    void ConnectPlayer(bool connected); -    void OnControllerButtonClick(int i); +    /// Update the input devices combobox. +    void UpdateInputDeviceCombobox(); + +    /// Updates the list of controller profiles. +    void UpdateInputProfiles(); -    /// Load configuration settings. -    void LoadConfiguration();      /// Restore all buttons to their default values.      void RestoreDefaults(); -    /// Clear all input configuration + +    /// Clear all input configuration.      void ClearAll(); -    /// Update UI to reflect current configuration. -    void UpdateButtonLabels(); +signals: +    /// Emitted when this controller is connected by the user. +    void Connected(bool connected); +    /// Emitted when the Handheld mode is selected (undocked with dual joycons attached). +    void HandheldStateChanged(bool is_handheld); +    /// Emitted when the input devices combobox is being refreshed. +    void RefreshInputDevices(); +    /** +     * Emitted when the input profiles combobox is being refreshed. +     * The player_index represents the current player's index, and the profile combobox +     * will not be updated for this index as they are already updated by other mechanisms. +     */ +    void RefreshInputProfiles(std::size_t player_index); + +protected: +    void showEvent(QShowEvent* event) override; + +private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); + +    /// Load configuration settings. +    void LoadConfiguration();      /// Called when the button was pressed.      void HandleClick(QPushButton* button,                       std::function<void(const Common::ParamPackage&)> new_input_setter,                       InputCommon::Polling::DeviceType type); -    /// Finish polling and configure input using the input_setter +    /// 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; +    /// Update UI to reflect current configuration. +    void UpdateUI(); + +    /// Update the available input devices. +    void UpdateInputDevices(); + +    /// Update the current controller icon. +    void UpdateControllerIcon(); + +    /// Hides and disables controller settings based on the current controller type. +    void UpdateControllerAvailableButtons(); + +    /// Shows or hides motion groupboxes based on the current controller type. +    void UpdateMotionButtons(); + +    /// Gets the default controller mapping for this device and auto configures the input to match. +    void UpdateMappingWithDefaults(); + +    /// Creates a controller profile. +    void CreateProfile(); + +    /// Deletes the selected controller profile. +    void DeleteProfile(); + +    /// Loads the selected controller profile. +    void LoadProfile(); + +    /// Saves the current controller configuration into a selected controller profile. +    void SaveProfile(); +      std::unique_ptr<Ui::ConfigureInputPlayer> ui;      std::size_t player_index;      bool debug; +    InputCommon::InputSubsystem* input_subsystem; + +    InputProfiles* profiles; +      std::unique_ptr<QTimer> timeout_timer;      std::unique_ptr<QTimer> poll_timer; +    static constexpr int PLAYER_COUNT = 8; +    std::array<QCheckBox*, PLAYER_COUNT> player_connected_checkbox; +      /// This will be the the setting function when an input is awaiting configuration.      std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;      std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;      std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; +    std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions> motions_param; -    static constexpr int ANALOG_SUB_BUTTONS_NUM = 5; +    static constexpr int ANALOG_SUB_BUTTONS_NUM = 4;      /// Each button input is represented by a QPushButton.      std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; -    std::vector<QWidget*> debug_hidden; -    std::vector<QWidget*> layout_hidden; - -    /// A group of five QPushButtons represent one analog input. The buttons each represent up, -    /// down, left, right, and modifier, respectively. +    /// A group of four QPushButtons represent one analog input. The buttons each represent up, +    /// down, left, right, respectively.      std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs>          analog_map_buttons; -    /// Analog inputs are also represented each with a single button, used to configure with an -    /// actual analog stick -    std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_stick; -    std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone; +    /// Each motion input is represented by a QPushButton. +    std::array<QPushButton*, Settings::NativeMotion::NumMotions> motion_map; +      std::array<QLabel*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone_label; +    std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone_slider; +    std::array<QGroupBox*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_groupbox; +    std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_button; +    std::array<QLabel*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_label; +    std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_slider; +    std::array<QGroupBox*, Settings::NativeAnalog::NumAnalogs> analog_map_range_groupbox; +    std::array<QSpinBox*, Settings::NativeAnalog::NumAnalogs> analog_map_range_spinbox;      static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;      std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; +    /// A flag to indicate that the "Map Analog Stick" pop-up has been shown and accepted once. +    bool map_analog_stick_accepted{}; +      /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,      /// keyboard events are ignored. -    bool want_keyboard_keys = false; +    bool want_keyboard_mouse{}; + +    /// List of physical devices users can map with. If a SDL backed device is selected, then you +    /// can use this device to get a default mapping. +    std::vector<Common::ParamPackage> input_devices; -    std::array<QPushButton*, 4> controller_color_buttons; -    std::array<QColor, 4> controller_colors; +    /// Bottom row is where console wide settings are held, and its "owned" by the parent +    /// ConfigureInput widget. On show, add this widget to the main layout. This will change the +    /// parent of the widget to this widget (but thats fine). +    QWidget* bottom_row;  }; diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui index 4b37746a1..1e78b4c10 100644 --- a/src/yuzu/configuration/configure_input_player.ui +++ b/src/yuzu/configuration/configure_input_player.ui @@ -1,1243 +1,3094 @@  <?xml version="1.0" encoding="UTF-8"?>  <ui version="4.0">   <class>ConfigureInputPlayer</class> - <widget class="QDialog" name="ConfigureInputPlayer"> + <widget class="QWidget" name="ConfigureInputPlayer">    <property name="geometry">     <rect>      <x>0</x>      <y>0</y> -    <width>408</width> -    <height>731</height> +    <width>780</width> +    <height>487</height>     </rect>    </property>    <property name="windowTitle">     <string>Configure Input</string>    </property> -  <layout class="QVBoxLayout" name="verticalLayout_5"> +  <layout class="QHBoxLayout" name="horizontalLayout_2"> +   <property name="spacing"> +    <number>0</number> +   </property> +   <property name="leftMargin"> +    <number>0</number> +   </property> +   <property name="topMargin"> +    <number>0</number> +   </property> +   <property name="rightMargin"> +    <number>0</number> +   </property> +   <property name="bottomMargin"> +    <number>0</number> +   </property>     <item> -    <layout class="QGridLayout" name="buttons"> -     <item row="1" column="1"> -      <widget class="QGroupBox" name="RStick"> -       <property name="title"> -        <string>Right Stick</string> -       </property> -       <property name="alignment"> -        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> -       </property> -       <property name="flat"> -        <bool>false</bool> +    <layout class="QVBoxLayout" name="main"> +     <property name="spacing"> +      <number>0</number> +     </property> +     <property name="leftMargin"> +      <number>0</number> +     </property> +     <property name="topMargin"> +      <number>0</number> +     </property> +     <property name="rightMargin"> +      <number>0</number> +     </property> +     <property name="bottomMargin"> +      <number>0</number> +     </property> +     <item> +      <layout class="QHBoxLayout" name="top" stretch="0,1,2"> +       <property name="spacing"> +        <number>3</number>         </property> -       <property name="checkable"> -        <bool>false</bool> +       <property name="topMargin"> +        <number>0</number>         </property> -       <layout class="QGridLayout" name="gridLayout_5"> -        <item row="1" column="1"> -         <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout"> +       <item> +        <widget class="QGroupBox" name="groupConnectedController"> +         <property name="layoutDirection"> +          <enum>Qt::LeftToRight</enum> +         </property> +         <property name="title"> +          <string>Connect Controller</string> +         </property> +         <property name="flat"> +          <bool>false</bool> +         </property> +         <property name="checkable"> +          <bool>true</bool> +         </property> +         <layout class="QHBoxLayout" name="horizontalLayout_3"> +          <property name="leftMargin"> +           <number>5</number> +          </property> +          <property name="topMargin"> +           <number>5</number> +          </property> +          <property name="rightMargin"> +           <number>5</number> +          </property> +          <property name="bottomMargin"> +           <number>5</number> +          </property>            <item> -           <layout class="QHBoxLayout" name="buttonRStickDownHorizontalLayout"> +           <widget class="QComboBox" name="comboControllerType"> +            <property name="minimumSize"> +             <size> +              <width>0</width> +              <height>21</height> +             </size> +            </property>              <item> -             <widget class="QLabel" name="labelRStickDown"> -              <property name="text"> -               <string>Down:</string> -              </property> -             </widget> +             <property name="text"> +              <string>Pro Controller</string> +             </property>              </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonRStickDown"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="0" column="1"> -         <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonRStickRightHorizontalLayout">              <item> -             <widget class="QLabel" name="labelRStickRight"> -              <property name="text"> -               <string>Right:</string> -              </property> -             </widget> +             <property name="text"> +              <string>Dual Joycons</string> +             </property>              </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonRStickRight"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="3" column="0" colspan="2"> -         <widget class="QPushButton" name="buttonRStickAnalog"> -          <property name="text"> -           <string>Set Analog Stick</string> -          </property> -         </widget> -        </item> -        <item row="0" column="0"> -         <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonRStickLeftHorizontalLayout">              <item> -             <widget class="QLabel" name="labelRStickLeft"> -              <property name="text"> -               <string>Left:</string> -              </property> -             </widget> +             <property name="text"> +              <string>Left Joycon</string> +             </property>              </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonRStickLeft"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="1" column="0"> -         <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonRStickUpHorizontalLayout">              <item> -             <widget class="QLabel" name="labelRStickUp"> -              <property name="text"> -               <string>Up:</string> -              </property> -             </widget> +             <property name="text"> +              <string>Right Joycon</string> +             </property>              </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonRStickUp"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="2" column="0"> -         <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonRStickPressedHorizontalLayout">              <item> -             <widget class="QLabel" name="labelRStickPressed"> -              <property name="text"> -               <string>Pressed:</string> -              </property> -             </widget> +             <property name="text"> +              <string>Handheld</string> +             </property>              </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonRStick"> -            <property name="text"> -             <string/> -            </property>             </widget>            </item>           </layout> -        </item> -        <item row="2" column="1"> -         <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout"> +        </widget> +       </item> +       <item> +        <widget class="QGroupBox" name="devicesGroup"> +         <property name="title"> +          <string>Input Device</string> +         </property> +         <layout class="QHBoxLayout" name="horizontalLayout_5"> +          <property name="spacing"> +           <number>3</number> +          </property> +          <property name="leftMargin"> +           <number>5</number> +          </property> +          <property name="topMargin"> +           <number>5</number> +          </property> +          <property name="rightMargin"> +           <number>5</number> +          </property> +          <property name="bottomMargin"> +           <number>5</number> +          </property>            <item> -           <layout class="QHBoxLayout" name="buttonRStickModHorizontalLayout"> +           <widget class="QComboBox" name="comboDevices"> +            <property name="minimumSize"> +             <size> +              <width>0</width> +              <height>21</height> +             </size> +            </property>              <item> -             <widget class="QLabel" name="labelRStickMod"> -              <property name="text"> -               <string>Modifier:</string> -              </property> -             </widget> +             <property name="text"> +              <string>Any</string> +             </property>              </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonRStickMod"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="4" column="0" colspan="2"> -         <layout class="QVBoxLayout" name="sliderRStickDeadzoneVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="sliderRStickDeadzoneHorizontalLayout">              <item> -             <widget class="QLabel" name="labelRStickDeadzone"> -              <property name="text"> -               <string>Deadzone: 0</string> -              </property> -              <property name="alignment"> -               <enum>Qt::AlignHCenter</enum> -              </property> -             </widget> +             <property name="text"> +              <string>Keyboard/Mouse</string> +             </property>              </item> -           </layout> +           </widget>            </item>            <item> -           <widget class="QSlider" name="sliderRStickDeadzone"> -            <property name="orientation"> -             <enum>Qt::Horizontal</enum> +           <widget class="QPushButton" name="buttonRefreshDevices"> +            <property name="minimumSize"> +             <size> +              <width>21</width> +              <height>21</height> +             </size> +            </property> +            <property name="maximumSize"> +             <size> +              <width>21</width> +              <height>21</height> +             </size> +            </property> +            <property name="styleSheet"> +             <string notr="true"/>              </property>             </widget>            </item>           </layout> -        </item> -        <item row="5" column="0"> -         <spacer name="RStick_verticalSpacer"> -          <property name="orientation"> -           <enum>Qt::Vertical</enum> +        </widget> +       </item> +       <item> +        <widget class="QGroupBox" name="profilesGroup"> +         <property name="minimumSize"> +          <size> +           <width>0</width> +           <height>0</height> +          </size> +         </property> +         <property name="title"> +          <string>Profile</string> +         </property> +         <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="2,0,0,0"> +          <property name="spacing"> +           <number>3</number>            </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>0</width> -            <height>0</height> -           </size> +          <property name="leftMargin"> +           <number>5</number> +          </property> +          <property name="topMargin"> +           <number>5</number> +          </property> +          <property name="rightMargin"> +           <number>5</number> +          </property> +          <property name="bottomMargin"> +           <number>5</number>            </property> -         </spacer> -        </item> -       </layout> -      </widget> -     </item> -     <item row="0" column="1"> -      <widget class="QGroupBox" name="Dpad"> -       <property name="title"> -        <string>Directional Pad</string> -       </property> -       <property name="flat"> -        <bool>false</bool> -       </property> -       <property name="checkable"> -        <bool>false</bool> -       </property> -       <layout class="QGridLayout" name="gridLayout_2"> -        <item row="1" column="0"> -         <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonDpadUpHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelDpadUp"> -              <property name="text"> -               <string>Up:</string> -              </property> -             </widget> -            </item> -           </layout> -          </item>            <item> -           <widget class="QPushButton" name="buttonDpadUp"> -            <property name="text"> -             <string/> +           <widget class="QComboBox" name="comboProfiles"> +            <property name="minimumSize"> +             <size> +              <width>0</width> +              <height>21</height> +             </size>              </property>             </widget>            </item> -         </layout> -        </item> -        <item row="1" column="1"> -         <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonDpadDownHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelDpadDown"> -              <property name="text"> -               <string>Down:</string> -              </property> -             </widget> -            </item> -           </layout> -          </item>            <item> -           <widget class="QPushButton" name="buttonDpadDown"> +           <widget class="QPushButton" name="buttonProfilesSave"> +            <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/> +             <string>Save</string>              </property>             </widget>            </item> -         </layout> -        </item> -        <item row="0" column="0"> -         <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonDpadLeftHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelDpadLeft"> -              <property name="minimumSize"> -               <size> -                <width>80</width> -                <height>0</height> -               </size> -              </property> -              <property name="text"> -               <string>Left:</string> -              </property> -             </widget> -            </item> -           </layout> -          </item>            <item> -           <widget class="QPushButton" name="buttonDpadLeft"> +           <widget class="QPushButton" name="buttonProfilesNew"> +            <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/> +             <string>New</string>              </property>             </widget>            </item> -         </layout> -        </item> -        <item row="0" column="1"> -         <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonDpadRightHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelDpadRight"> -              <property name="minimumSize"> -               <size> -                <width>80</width> -                <height>0</height> -               </size> -              </property> -              <property name="text"> -               <string>Right:</string> -              </property> -             </widget> -            </item> -           </layout> -          </item>            <item> -           <widget class="QPushButton" name="buttonDpadRight"> +           <widget class="QPushButton" name="buttonProfilesDelete"> +            <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/> +             <string>Delete</string>              </property>             </widget>            </item>           </layout> -        </item> -       </layout> -      </widget> +        </widget> +       </item> +      </layout>       </item> -     <item row="0" column="0"> -      <widget class="QGroupBox" name="faceButtons"> -       <property name="title"> -        <string>Face Buttons</string> -       </property> -       <property name="flat"> -        <bool>false</bool> -       </property> -       <property name="checkable"> -        <bool>false</bool> +     <item> +      <widget class="QFrame" name="bottom"> +       <property name="sizePolicy"> +        <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> +         <horstretch>0</horstretch> +         <verstretch>0</verstretch> +        </sizepolicy>         </property> -       <layout class="QGridLayout" name="gridLayout"> -        <item row="0" column="0"> -         <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonFaceButtonsAHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelA"> -              <property name="minimumSize"> -               <size> -                <width>80</width> -                <height>0</height> -               </size> +       <layout class="QHBoxLayout" name="_2"> +        <property name="sizeConstraint"> +         <enum>QLayout::SetMinimumSize</enum> +        </property> +        <property name="leftMargin"> +         <number>0</number> +        </property> +        <property name="topMargin"> +         <number>0</number> +        </property> +        <property name="rightMargin"> +         <number>0</number> +        </property> +        <property name="bottomMargin"> +         <number>0</number> +        </property> +        <item> +         <widget class="QWidget" name="bottomLeft" native="true"> +          <layout class="QVBoxLayout" name="bottomLeftLayout" stretch="0,0,0,0"> +           <property name="spacing"> +            <number>0</number> +           </property> +           <property name="sizeConstraint"> +            <enum>QLayout::SetDefaultConstraint</enum> +           </property> +           <property name="leftMargin"> +            <number>0</number> +           </property> +           <property name="topMargin"> +            <number>0</number> +           </property> +           <property name="rightMargin"> +            <number>0</number> +           </property> +           <property name="bottomMargin"> +            <number>0</number> +           </property> +           <item> +            <widget class="QGroupBox" name="LStick"> +             <property name="sizePolicy"> +              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +               <horstretch>0</horstretch> +               <verstretch>0</verstretch> +              </sizepolicy> +             </property> +             <property name="title"> +              <string>Left Stick</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="text"> -               <string>A:</string> +              <property name="sizeConstraint"> +               <enum>QLayout::SetDefaultConstraint</enum>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonA"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="0" column="1"> -         <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonFaceButtonsBHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelB"> -              <property name="minimumSize"> -               <size> -                <width>80</width> -                <height>0</height> -               </size> +              <property name="leftMargin"> +               <number>3</number>                </property> -              <property name="text"> -               <string>B:</string> +              <property name="topMargin"> +               <number>0</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonB"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="1" column="0"> -         <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonFaceButtonsXHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelX"> -              <property name="text"> -               <string>X:</string> +              <property name="rightMargin"> +               <number>3</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonX"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="1" column="1"> -         <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonFaceButtonsYHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelY"> -              <property name="text"> -               <string>Y:</string> +              <property name="bottomMargin"> +               <number>0</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonY"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -       </layout> -      </widget> -     </item> -     <item row="5" column="0" colspan="2"> -      <widget class="QGroupBox" name="controller_color"> -       <property name="title"> -        <string>Controller Color</string> -       </property> -       <layout class="QGridLayout" name="gridLayout_10" columnstretch="0,0,0,0,0,0,0"> -        <item row="0" column="0"> -         <spacer name="horizontalSpacer_2"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>40</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -        <item row="0" column="1"> -         <widget class="QLabel" name="left_body_label"> -          <property name="text"> -           <string>Left Body</string> -          </property> -         </widget> -        </item> -        <item row="0" column="6"> -         <spacer name="horizontalSpacer_3"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>40</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -        <item row="1" column="1"> -         <widget class="QLabel" name="left_buttons_label"> -          <property name="minimumSize"> -           <size> -            <width>90</width> -            <height>0</height> -           </size> -          </property> -          <property name="text"> -           <string>Left Buttons</string> -          </property> -         </widget> -        </item> -        <item row="1" column="5"> -         <widget class="QPushButton" name="right_buttons_button"> -          <property name="sizePolicy"> -           <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> -            <horstretch>0</horstretch> -            <verstretch>0</verstretch> -           </sizepolicy> -          </property> -          <property name="minimumSize"> -           <size> -            <width>32</width> -            <height>0</height> -           </size> -          </property> -          <property name="maximumSize"> -           <size> -            <width>40</width> -            <height>16777215</height> -           </size> -          </property> -          <property name="text"> -           <string/> -          </property> -         </widget> -        </item> -        <item row="0" column="4"> -         <widget class="QLabel" name="right_body_label"> -          <property name="text"> -           <string>Right Body</string> -          </property> -         </widget> -        </item> -        <item row="1" column="4"> -         <widget class="QLabel" name="right_buttons_label"> -          <property name="minimumSize"> -           <size> -            <width>90</width> -            <height>0</height> -           </size> -          </property> -          <property name="text"> -           <string>Right Buttons</string> -          </property> -         </widget> -        </item> -        <item row="1" column="2"> -         <widget class="QPushButton" name="left_buttons_button"> -          <property name="sizePolicy"> -           <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> -            <horstretch>0</horstretch> -            <verstretch>0</verstretch> -           </sizepolicy> -          </property> -          <property name="minimumSize"> -           <size> -            <width>32</width> -            <height>0</height> -           </size> -          </property> -          <property name="maximumSize"> -           <size> -            <width>40</width> -            <height>16777215</height> -           </size> -          </property> -          <property name="text"> -           <string/> -          </property> -         </widget> -        </item> -        <item row="0" column="2"> -         <widget class="QPushButton" name="left_body_button"> -          <property name="sizePolicy"> -           <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> -            <horstretch>0</horstretch> -            <verstretch>0</verstretch> -           </sizepolicy> -          </property> -          <property name="minimumSize"> -           <size> -            <width>32</width> -            <height>0</height> -           </size> -          </property> -          <property name="maximumSize"> -           <size> -            <width>40</width> -            <height>16777215</height> -           </size> -          </property> -          <property name="text"> -           <string/> -          </property> -         </widget> -        </item> -        <item row="0" column="5"> -         <widget class="QPushButton" name="right_body_button"> -          <property name="sizePolicy"> -           <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> -            <horstretch>0</horstretch> -            <verstretch>0</verstretch> -           </sizepolicy> -          </property> -          <property name="minimumSize"> -           <size> -            <width>32</width> -            <height>0</height> -           </size> -          </property> -          <property name="maximumSize"> -           <size> -            <width>40</width> -            <height>16777215</height> -           </size> -          </property> -          <property name="text"> -           <string/> -          </property> -         </widget> -        </item> -        <item row="0" column="3"> -         <spacer name="horizontalSpacer_4"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeType"> -           <enum>QSizePolicy::Fixed</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>20</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -       </layout> -      </widget> -     </item> -     <item row="1" column="0"> -      <widget class="QGroupBox" name="LStick"> -       <property name="title"> -        <string>Left Stick</string> -       </property> -       <property name="flat"> -        <bool>false</bool> -       </property> -       <property name="checkable"> -        <bool>false</bool> -       </property> -       <layout class="QGridLayout" name="gridLayout_4"> -        <item row="1" column="1"> -         <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonLStickUpHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelLStickUp"> -              <property name="text"> -               <string>Up:</string> +              <item> +               <widget class="QWidget" name="buttonLStickUpWidget" native="true"> +                <layout class="QHBoxLayout" name="horizontalLayout_20"> +                 <property name="spacing"> +                  <number>0</number> +                 </property> +                 <property name="leftMargin"> +                  <number>0</number> +                 </property> +                 <property name="topMargin"> +                  <number>0</number> +                 </property> +                 <property name="rightMargin"> +                  <number>0</number> +                 </property> +                 <property name="bottomMargin"> +                  <number>0</number> +                 </property> +                 <item> +                  <spacer name="horizontalSpacerLStickUpLeft"> +                   <property name="orientation"> +                    <enum>Qt::Horizontal</enum> +                   </property> +                   <property name="sizeHint" stdset="0"> +                    <size> +                     <width>20</width> +                     <height>20</height> +                    </size> +                   </property> +                  </spacer> +                 </item> +                 <item alignment="Qt::AlignHCenter"> +                  <widget class="QGroupBox" name="buttonLStickUpGroup"> +                   <property name="title"> +                    <string>Up</string> +                   </property> +                   <property name="alignment"> +                    <set>Qt::AlignCenter</set> +                   </property> +                   <property name="flat"> +                    <bool>false</bool> +                   </property> +                   <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout"> +                    <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="buttonLStickUp"> +                      <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>Up</string> +                      </property> +                     </widget> +                    </item> +                   </layout> +                  </widget> +                 </item> +                 <item> +                  <spacer name="horizontalSpacerLStickUpRight"> +                   <property name="orientation"> +                    <enum>Qt::Horizontal</enum> +                   </property> +                   <property name="sizeHint" stdset="0"> +                    <size> +                     <width>20</width> +                     <height>20</height> +                    </size> +                   </property> +                  </spacer> +                 </item> +                </layout> +               </widget> +              </item> +              <item> +               <layout class="QHBoxLayout" name="buttonLStickLeftRightHorizontaLayout"> +                <property name="spacing"> +                 <number>3</number> +                </property> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonLStickLeftGroup"> +                  <property name="title"> +                   <string>Left</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout"> +                   <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="buttonLStickLeft"> +                     <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>Left</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonLStickRightGroup"> +                  <property name="title"> +                   <string>Right</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout"> +                   <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="buttonLStickRight"> +                     <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>Right</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +               </layout> +              </item> +              <item> +               <widget class="QWidget" name="buttonLStickDownWidget" native="true"> +                <layout class="QHBoxLayout" name="horizontalLayout_22"> +                 <property name="spacing"> +                  <number>0</number> +                 </property> +                 <property name="leftMargin"> +                  <number>0</number> +                 </property> +                 <property name="topMargin"> +                  <number>0</number> +                 </property> +                 <property name="rightMargin"> +                  <number>0</number> +                 </property> +                 <property name="bottomMargin"> +                  <number>0</number> +                 </property> +                 <item> +                  <spacer name="horizontalSpacerLStickDownLeft"> +                   <property name="orientation"> +                    <enum>Qt::Horizontal</enum> +                   </property> +                   <property name="sizeHint" stdset="0"> +                    <size> +                     <width>20</width> +                     <height>20</height> +                    </size> +                   </property> +                  </spacer> +                 </item> +                 <item alignment="Qt::AlignHCenter"> +                  <widget class="QGroupBox" name="buttonLStickDownGroup"> +                   <property name="title"> +                    <string>Down</string> +                   </property> +                   <property name="alignment"> +                    <set>Qt::AlignCenter</set> +                   </property> +                   <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout"> +                    <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="buttonLStickDown"> +                      <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>Down</string> +                      </property> +                     </widget> +                    </item> +                   </layout> +                  </widget> +                 </item> +                 <item> +                  <spacer name="horizontalSpacerLStickDownRight"> +                   <property name="orientation"> +                    <enum>Qt::Horizontal</enum> +                   </property> +                   <property name="sizeHint" stdset="0"> +                    <size> +                     <width>20</width> +                     <height>20</height> +                    </size> +                   </property> +                  </spacer> +                 </item> +                </layout> +               </widget> +              </item> +              <item> +               <layout class="QHBoxLayout" name="buttonLStickPressedModifierHorizontalLayout"> +                <property name="spacing"> +                 <number>3</number> +                </property> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonLStickPressedGroup"> +                  <property name="title"> +                   <string>Pressed</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0"> +                   <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="buttonLStick"> +                     <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>Pressed</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonLStickModGroup"> +                  <property name="title"> +                   <string>Modifier</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout"> +                   <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="buttonLStickMod"> +                     <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>Modifier</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +                <item> +                 <widget class="QGroupBox" name="buttonLStickRangeGroup"> +                  <property name="title"> +                   <string>Range</string> +                  </property> +                  <layout class="QHBoxLayout" name="buttonLStickRangeGroupHorizontalLayout"> +                   <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="QSpinBox" name="spinboxLStickRange"> +                     <property name="minimumSize"> +                      <size> +                       <width>68</width> +                       <height>21</height> +                      </size> +                     </property> +                     <property name="maximumSize"> +                      <size> +                       <width>68</width> +                       <height>16777215</height> +                      </size> +                     </property> +                     <property name="suffix"> +                      <string>%</string> +                     </property> +                     <property name="minimum"> +                      <number>50</number> +                     </property> +                     <property name="maximum"> +                      <number>150</number> +                     </property> +                     <property name="value"> +                      <number>100</number> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +               </layout> +              </item> +              <item> +               <layout class="QVBoxLayout" name="sliderLStickDeadzoneModifierRangeVerticalLayout"> +                <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>2</number> +                </property> +                <property name="rightMargin"> +                 <number>0</number> +                </property> +                <property name="bottomMargin"> +                 <number>3</number> +                </property> +                <item> +                 <layout class="QHBoxLayout" name="sliderLStickDeadzoneHorizontalLayout"> +                  <item> +                   <widget class="QLabel" name="labelLStickDeadzone"> +                    <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="sliderLStickDeadzone"> +                  <property name="maximum"> +                   <number>100</number> +                  </property> +                  <property name="orientation"> +                   <enum>Qt::Horizontal</enum> +                  </property> +                 </widget> +                </item> +                <item> +                 <layout class="QHBoxLayout" name="sliderLStickModifierRangeHorizontalLayout"> +                  <item> +                   <widget class="QLabel" name="labelLStickModifierRange"> +                    <property name="text"> +                     <string>Modifier Range: 0%</string> +                    </property> +                    <property name="alignment"> +                     <set>Qt::AlignHCenter</set> +                    </property> +                   </widget> +                  </item> +                 </layout> +                </item> +                <item> +                 <widget class="QSlider" name="sliderLStickModifierRange"> +                  <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="verticalSpacerBottomLeft"> +             <property name="orientation"> +              <enum>Qt::Vertical</enum> +             </property> +             <property name="sizeHint" stdset="0"> +              <size> +               <width>20</width> +               <height>0</height> +              </size> +             </property> +            </spacer> +           </item> +           <item> +            <widget class="QGroupBox" name="Dpad"> +             <property name="sizePolicy"> +              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +               <horstretch>0</horstretch> +               <verstretch>0</verstretch> +              </sizepolicy> +             </property> +             <property name="title"> +              <string>D-Pad</string> +             </property> +             <property name="flat"> +              <bool>false</bool> +             </property> +             <property name="checkable"> +              <bool>false</bool> +             </property> +             <layout class="QVBoxLayout" name="verticalLayout_5"> +              <property name="spacing"> +               <number>0</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonLStickUp"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="0" column="2"> -         <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonLStickRightHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelLStickRight"> -              <property name="text"> -               <string>Right:</string> +              <property name="leftMargin"> +               <number>3</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonLStickRight"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="4" column="1" colspan="2"> -         <widget class="QPushButton" name="buttonLStickAnalog"> -          <property name="text"> -           <string>Set Analog Stick</string> -          </property> -         </widget> -        </item> -        <item row="0" column="1"> -         <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonLStickLeftHorizontalLayout_2"> -            <item> -             <widget class="QLabel" name="labelLStickLeft"> -              <property name="text"> -               <string>Left:</string> +              <property name="topMargin"> +               <number>0</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonLStickLeft"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="1" column="2"> -         <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonLStickDownHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelLStickDown"> -              <property name="text"> -               <string>Down:</string> +              <property name="rightMargin"> +               <number>3</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonLStickDown"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="3" column="2"> -         <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonLStickModHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelLStickMod"> -              <property name="text"> -               <string>Modifier:</string> +              <property name="bottomMargin"> +               <number>3</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonLStickMod"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +              <item> +               <widget class="QWidget" name="buttonDpadUpWidget" native="true"> +                <layout class="QHBoxLayout" name="horizontalLayout_23"> +                 <property name="spacing"> +                  <number>0</number> +                 </property> +                 <property name="leftMargin"> +                  <number>0</number> +                 </property> +                 <property name="topMargin"> +                  <number>0</number> +                 </property> +                 <property name="rightMargin"> +                  <number>0</number> +                 </property> +                 <property name="bottomMargin"> +                  <number>0</number> +                 </property> +                 <item> +                  <spacer name="horizontalSpacerDpadUpLeft"> +                   <property name="orientation"> +                    <enum>Qt::Horizontal</enum> +                   </property> +                   <property name="sizeHint" stdset="0"> +                    <size> +                     <width>20</width> +                     <height>20</height> +                    </size> +                   </property> +                  </spacer> +                 </item> +                 <item alignment="Qt::AlignHCenter"> +                  <widget class="QGroupBox" name="buttonDpadUpGroup"> +                   <property name="title"> +                    <string>Up</string> +                   </property> +                   <property name="alignment"> +                    <set>Qt::AlignCenter</set> +                   </property> +                   <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout"> +                    <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="buttonDpadUp"> +                      <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>Up</string> +                      </property> +                     </widget> +                    </item> +                   </layout> +                  </widget> +                 </item> +                 <item> +                  <spacer name="horizontalSpacerDpadUpRight"> +                   <property name="orientation"> +                    <enum>Qt::Horizontal</enum> +                   </property> +                   <property name="sizeHint" stdset="0"> +                    <size> +                     <width>20</width> +                     <height>20</height> +                    </size> +                   </property> +                  </spacer> +                 </item> +                </layout> +               </widget> +              </item> +              <item> +               <layout class="QHBoxLayout" name="buttonDpadLeftRightHorizontalLayout"> +                <property name="spacing"> +                 <number>3</number> +                </property> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonDpadLeftGroup"> +                  <property name="title"> +                   <string>Left</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout"> +                   <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="buttonDpadLeft"> +                     <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>Left</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonDpadRightGroup"> +                  <property name="title"> +                   <string>Right</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout"> +                   <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="buttonDpadRight"> +                     <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>Right</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +               </layout> +              </item> +              <item> +               <widget class="QWidget" name="buttonDpadDownWidget" native="true"> +                <layout class="QHBoxLayout" name="horizontalLayout_24"> +                 <property name="spacing"> +                  <number>0</number> +                 </property> +                 <property name="leftMargin"> +                  <number>0</number> +                 </property> +                 <property name="topMargin"> +                  <number>0</number> +                 </property> +                 <property name="rightMargin"> +                  <number>0</number> +                 </property> +                 <property name="bottomMargin"> +                  <number>0</number> +                 </property> +                 <item> +                  <spacer name="horizontalSpacerDpadDownLeft"> +                   <property name="orientation"> +                    <enum>Qt::Horizontal</enum> +                   </property> +                   <property name="sizeHint" stdset="0"> +                    <size> +                     <width>20</width> +                     <height>20</height> +                    </size> +                   </property> +                  </spacer> +                 </item> +                 <item alignment="Qt::AlignHCenter"> +                  <widget class="QGroupBox" name="buttonDpadDownGroup"> +                   <property name="title"> +                    <string>Down</string> +                   </property> +                   <property name="alignment"> +                    <set>Qt::AlignCenter</set> +                   </property> +                   <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout"> +                    <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="buttonDpadDown"> +                      <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>Down</string> +                      </property> +                     </widget> +                    </item> +                   </layout> +                  </widget> +                 </item> +                 <item> +                  <spacer name="horizontalSpacerDpadDownRight"> +                   <property name="orientation"> +                    <enum>Qt::Horizontal</enum> +                   </property> +                   <property name="sizeHint" stdset="0"> +                    <size> +                     <width>20</width> +                     <height>20</height> +                    </size> +                   </property> +                  </spacer> +                 </item> +                </layout> +               </widget> +              </item> +             </layout> +            </widget> +           </item> +           <item> +            <spacer name="verticalSpacerBottomLeft_2"> +             <property name="orientation"> +              <enum>Qt::Vertical</enum> +             </property> +             <property name="sizeHint" stdset="0"> +              <size> +               <width>20</width> +               <height>0</height> +              </size> +             </property> +            </spacer> +           </item> +          </layout> +         </widget>          </item> -        <item row="3" column="1"> -         <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0,0"> -          <item> -           <layout class="QHBoxLayout" name="buttonLStickPressedHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelLStickPressed"> -              <property name="text"> -               <string>Pressed:</string> +        <item> +         <widget class="QWidget" name="bottomMiddle" native="true"> +          <layout class="QVBoxLayout" stretch="0,0,0"> +           <property name="spacing"> +            <number>6</number> +           </property> +           <property name="leftMargin"> +            <number>0</number> +           </property> +           <property name="topMargin"> +            <number>0</number> +           </property> +           <property name="rightMargin"> +            <number>0</number> +           </property> +           <property name="bottomMargin"> +            <number>0</number> +           </property> +           <item> +            <layout class="QHBoxLayout" name="shoulderButtons"> +             <property name="spacing"> +              <number>3</number> +             </property> +             <item> +              <widget class="QWidget" name="buttonShoulderButtonsLeft" native="true"> +               <layout class="QVBoxLayout" name="buttonShoulderButtonsLeftVerticalLayout"> +                <property name="spacing"> +                 <number>0</number> +                </property> +                <property name="leftMargin"> +                 <number>0</number> +                </property> +                <property name="topMargin"> +                 <number>0</number> +                </property> +                <property name="rightMargin"> +                 <number>0</number> +                </property> +                <property name="bottomMargin"> +                 <number>0</number> +                </property> +                <item> +                 <widget class="QGroupBox" name="buttonShoulderButtonsButtonLGroup"> +                  <property name="title"> +                   <string>L</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout"> +                   <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="buttonL"> +                     <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>L</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +                <item> +                 <widget class="QGroupBox" name="buttonShoulderButtonsButtonZLGroup"> +                  <property name="title"> +                   <string>ZL</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout"> +                   <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="buttonZL"> +                     <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>ZL</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +               </layout> +              </widget> +             </item> +             <item> +              <widget class="QWidget" name="horizontalSpacerShoulderButtonsWidget" native="true"> +               <layout class="QHBoxLayout" name="horizontalSpacerShoulderButtonsWidgetLayout"> +                <property name="spacing"> +                 <number>0</number> +                </property> +                <property name="leftMargin"> +                 <number>0</number> +                </property> +                <property name="topMargin"> +                 <number>0</number> +                </property> +                <property name="rightMargin"> +                 <number>0</number> +                </property> +                <property name="bottomMargin"> +                 <number>0</number> +                </property> +                <item> +                 <spacer name="horizontalSpacerShoulderButtons1"> +                  <property name="orientation"> +                   <enum>Qt::Horizontal</enum> +                  </property> +                  <property name="sizeHint" stdset="0"> +                   <size> +                    <width>0</width> +                    <height>20</height> +                   </size> +                  </property> +                 </spacer> +                </item> +               </layout> +              </widget> +             </item> +             <item> +              <widget class="QWidget" name="buttonMiscButtonsMinusScreenshot" native="true"> +               <layout class="QVBoxLayout" name="buttonMiscButtonsMinusScreenshotVerticalLayout"> +                <property name="spacing"> +                 <number>0</number> +                </property> +                <property name="leftMargin"> +                 <number>0</number> +                </property> +                <property name="topMargin"> +                 <number>0</number> +                </property> +                <property name="rightMargin"> +                 <number>0</number> +                </property> +                <property name="bottomMargin"> +                 <number>0</number> +                </property> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonMiscButtonsMinusGroup"> +                  <property name="title"> +                   <string>Minus</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout"> +                   <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="buttonMinus"> +                     <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>Minus</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonMiscButtonsScreenshotGroup"> +                  <property name="title"> +                   <string>Capture</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout"> +                   <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="buttonScreenshot"> +                     <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>Capture</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +               </layout> +              </widget> +             </item> +             <item> +              <widget class="QWidget" name="buttonMiscButtonsPlusHome" native="true"> +               <layout class="QVBoxLayout" name="buttonMiscButtonsPlusHomeVerticalLayout"> +                <property name="spacing"> +                 <number>0</number> +                </property> +                <property name="leftMargin"> +                 <number>0</number> +                </property> +                <property name="topMargin"> +                 <number>0</number> +                </property> +                <property name="rightMargin"> +                 <number>0</number> +                </property> +                <property name="bottomMargin"> +                 <number>0</number> +                </property> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonMiscButtonsPlusGroup"> +                  <property name="title"> +                   <string>Plus</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout"> +                   <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="buttonPlus"> +                     <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>Plus</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonMiscButtonsHomeGroup"> +                  <property name="title"> +                   <string>Home</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout"> +                   <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="buttonHome"> +                     <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>Home</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +               </layout> +              </widget> +             </item> +             <item> +              <widget class="QWidget" name="horizontalSpacerShoulderButtonsWidget3" native="true"> +               <layout class="QHBoxLayout" name="horizontalSpacerShoulderButtonsWidget3Layout"> +                <property name="spacing"> +                 <number>0</number> +                </property> +                <property name="leftMargin"> +                 <number>0</number> +                </property> +                <property name="topMargin"> +                 <number>0</number> +                </property> +                <property name="rightMargin"> +                 <number>0</number> +                </property> +                <property name="bottomMargin"> +                 <number>0</number> +                </property> +                <item> +                 <spacer name="horizontalSpacerShoulderButtons2"> +                  <property name="orientation"> +                   <enum>Qt::Horizontal</enum> +                  </property> +                  <property name="sizeHint" stdset="0"> +                   <size> +                    <width>0</width> +                    <height>20</height> +                   </size> +                  </property> +                 </spacer> +                </item> +               </layout> +              </widget> +             </item> +             <item> +              <widget class="QWidget" name="buttonShoulderButtonsRight" native="true"> +               <layout class="QVBoxLayout" name="buttonShoulderButtonsRightVerticalLayout"> +                <property name="spacing"> +                 <number>0</number> +                </property> +                <property name="leftMargin"> +                 <number>0</number> +                </property> +                <property name="topMargin"> +                 <number>0</number> +                </property> +                <property name="rightMargin"> +                 <number>0</number> +                </property> +                <property name="bottomMargin"> +                 <number>0</number> +                </property> +                <item> +                 <widget class="QGroupBox" name="buttonShoulderButtonsRGroup"> +                  <property name="title"> +                   <string>R</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout"> +                   <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="buttonR"> +                     <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>R</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +                <item> +                 <widget class="QGroupBox" name="buttonShoulderButtonsZRGroup"> +                  <property name="title"> +                   <string>ZR</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout"> +                   <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="buttonZR"> +                     <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>ZR</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +               </layout> +              </widget> +             </item> +             <item> +              <widget class="QWidget" name="horizontalSpacerShoulderButtonsWidget2" native="true"> +               <layout class="QHBoxLayout" name="horizontalSpacerShoulderButtonsWidget2Layout"> +                <property name="spacing"> +                 <number>0</number> +                </property> +                <property name="leftMargin"> +                 <number>0</number> +                </property> +                <property name="topMargin"> +                 <number>0</number> +                </property> +                <property name="rightMargin"> +                 <number>0</number> +                </property> +                <property name="bottomMargin"> +                 <number>0</number> +                </property> +                <item> +                 <spacer name="horizontalSpacerShoulderButtons3"> +                  <property name="orientation"> +                   <enum>Qt::Horizontal</enum> +                  </property> +                  <property name="sizeHint" stdset="0"> +                   <size> +                    <width>0</width> +                    <height>20</height> +                   </size> +                  </property> +                 </spacer> +                </item> +               </layout> +              </widget> +             </item> +             <item> +              <widget class="QWidget" name="buttonShoulderButtonsSLSR" native="true"> +               <layout class="QVBoxLayout" name="buttonShoulderButtonsSLSRVerticalLayout"> +                <property name="spacing"> +                 <number>0</number> +                </property> +                <property name="leftMargin"> +                 <number>0</number> +                </property> +                <property name="topMargin"> +                 <number>0</number> +                </property> +                <property name="rightMargin"> +                 <number>0</number> +                </property> +                <property name="bottomMargin"> +                 <number>0</number> +                </property> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonShoulderButtonsSLGroup"> +                  <property name="title"> +                   <string>SL</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout"> +                   <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="buttonSL"> +                     <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>SL</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonShoulderButtonsSRGroup"> +                  <property name="title"> +                   <string>SR</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout"> +                   <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="buttonSR"> +                     <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>SR</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +               </layout> +              </widget> +             </item> +            </layout> +           </item> +           <item> +            <widget class="QFrame" name="controllerFrame"> +             <property name="sizePolicy"> +              <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> +               <horstretch>0</horstretch> +               <verstretch>0</verstretch> +              </sizepolicy> +             </property> +             <property name="font"> +              <font> +               <weight>75</weight> +               <bold>true</bold> +              </font> +             </property> +             <property name="styleSheet"> +              <string notr="true">image: url(:/controller/pro);</string> +             </property> +             <layout class="QVBoxLayout" name="verticalLayout_4"> +              <property name="leftMargin"> +               <number>0</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonLStick"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="5" column="1" colspan="2"> -         <layout class="QVBoxLayout" name="sliderLStickDeadzoneVerticalLayout"> -          <property name="sizeConstraint"> -           <enum>QLayout::SetDefaultConstraint</enum> -          </property> -          <item> -           <layout class="QHBoxLayout" name="sliderLStickDeadzoneHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelLStickDeadzone"> -              <property name="text"> -               <string>Deadzone: 0</string> +              <property name="topMargin"> +               <number>0</number>                </property> -              <property name="alignment"> -               <enum>Qt::AlignHCenter</enum> +              <property name="rightMargin"> +               <number>0</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QSlider" name="sliderLStickDeadzone"> -            <property name="orientation"> -             <enum>Qt::Horizontal</enum> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="6" column="1"> -         <spacer name="LStick_verticalSpacer"> -          <property name="orientation"> -           <enum>Qt::Vertical</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>0</width> -            <height>0</height> -           </size> -          </property> -         </spacer> -        </item> -       </layout> -      </widget> -     </item> -     <item row="3" column="0"> -      <widget class="QGroupBox" name="shoulderButtons"> -       <property name="title"> -        <string>Shoulder Buttons</string> -       </property> -       <property name="flat"> -        <bool>false</bool> -       </property> -       <property name="checkable"> -        <bool>false</bool> -       </property> -       <layout class="QGridLayout" name="gridLayout_3"> -        <item row="0" column="0"> -         <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonShoulderButtonsLHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelL"> -              <property name="text"> -               <string>L:</string> +              <property name="bottomMargin"> +               <number>0</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonL"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +             </layout> +            </widget> +           </item> +           <item> +            <layout class="QHBoxLayout" name="miscButtons"> +             <property name="spacing"> +              <number>3</number> +             </property> +             <property name="topMargin"> +              <number>0</number> +             </property> +             <item> +              <spacer name="horizontalSpacerMiscButtons1"> +               <property name="orientation"> +                <enum>Qt::Horizontal</enum> +               </property> +               <property name="sizeHint" stdset="0"> +                <size> +                 <width>20</width> +                 <height>20</height> +                </size> +               </property> +              </spacer> +             </item> +             <item> +              <widget class="QGroupBox" name="buttonMotionLeftGroup"> +               <property name="title"> +                <string>Motion 1</string> +               </property> +               <property name="alignment"> +                <set>Qt::AlignCenter</set> +               </property> +               <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout_2"> +                <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="buttonMotionLeft"> +                  <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>Left</string> +                  </property> +                 </widget> +                </item> +               </layout> +              </widget> +             </item> +             <item> +              <widget class="QGroupBox" name="buttonMotionRightGroup"> +               <property name="title"> +                <string>Motion 2</string> +               </property> +               <property name="alignment"> +                <set>Qt::AlignCenter</set> +               </property> +               <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout_2"> +                <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="buttonMotionRight"> +                  <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>Right</string> +                  </property> +                 </widget> +                </item> +               </layout> +              </widget> +             </item> +             <item> +              <spacer name="horizontalSpacerMiscButtons4"> +               <property name="orientation"> +                <enum>Qt::Horizontal</enum> +               </property> +               <property name="sizeHint" stdset="0"> +                <size> +                 <width>20</width> +                 <height>20</height> +                </size> +               </property> +              </spacer> +             </item> +            </layout> +           </item> +          </layout> +         </widget>          </item> -        <item row="0" column="1"> -         <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonShoulderButtonsRHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelR"> -              <property name="text"> -               <string>R:</string> +        <item> +         <widget class="QWidget" name="bottomRight" native="true"> +          <layout class="QVBoxLayout" name="bottomRightLayout"> +           <property name="spacing"> +            <number>0</number> +           </property> +           <property name="leftMargin"> +            <number>0</number> +           </property> +           <property name="topMargin"> +            <number>0</number> +           </property> +           <property name="rightMargin"> +            <number>0</number> +           </property> +           <property name="bottomMargin"> +            <number>0</number> +           </property> +           <item> +            <widget class="QGroupBox" name="faceButtons"> +             <property name="sizePolicy"> +              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +               <horstretch>0</horstretch> +               <verstretch>0</verstretch> +              </sizepolicy> +             </property> +             <property name="title"> +              <string>Face Buttons</string> +             </property> +             <property name="flat"> +              <bool>false</bool> +             </property> +             <property name="checkable"> +              <bool>false</bool> +             </property> +             <layout class="QVBoxLayout" name="verticalLayout"> +              <property name="spacing"> +               <number>0</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonR"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="1" column="0"> -         <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonShoulderButtonsZLHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelZL"> -              <property name="text"> -               <string>ZL:</string> +              <property name="leftMargin"> +               <number>3</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonZL"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="1" column="1"> -         <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonShoulderButtonsZRHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelZR"> -              <property name="text"> -               <string>ZR:</string> +              <property name="topMargin"> +               <number>0</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonZR"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="0" column="2"> -         <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonShoulderButtonsSLHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelSL"> -              <property name="text"> -               <string>SL:</string> +              <property name="rightMargin"> +               <number>3</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonSL"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="1" column="2"> -         <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonShoulderButtonsSRHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelSR"> -              <property name="text"> -               <string>SR:</string> +              <property name="bottomMargin"> +               <number>3</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonSR"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -       </layout> -      </widget> -     </item> -     <item row="3" column="1"> -      <widget class="QGroupBox" name="misc"> -       <property name="title"> -        <string>Misc.</string> -       </property> -       <property name="flat"> -        <bool>false</bool> -       </property> -       <property name="checkable"> -        <bool>false</bool> -       </property> -       <layout class="QGridLayout" name="gridLayout_6"> -        <item row="1" column="0"> -         <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonMiscMinusHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelMinus"> -              <property name="text"> -               <string>Minus:</string> +              <item> +               <widget class="QWidget" name="buttonFaceButtonsBWidget" native="true"> +                <layout class="QHBoxLayout" name="horizontalLayout_6"> +                 <property name="spacing"> +                  <number>0</number> +                 </property> +                 <property name="leftMargin"> +                  <number>0</number> +                 </property> +                 <property name="topMargin"> +                  <number>0</number> +                 </property> +                 <property name="rightMargin"> +                  <number>0</number> +                 </property> +                 <property name="bottomMargin"> +                  <number>0</number> +                 </property> +                 <item> +                  <spacer name="horizontalSpacerBLeft"> +                   <property name="orientation"> +                    <enum>Qt::Horizontal</enum> +                   </property> +                   <property name="sizeHint" stdset="0"> +                    <size> +                     <width>20</width> +                     <height>20</height> +                    </size> +                   </property> +                  </spacer> +                 </item> +                 <item alignment="Qt::AlignHCenter"> +                  <widget class="QGroupBox" name="buttonFaceButtonsXGroup"> +                   <property name="title"> +                    <string>X</string> +                   </property> +                   <property name="alignment"> +                    <set>Qt::AlignCenter</set> +                   </property> +                   <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout"> +                    <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="buttonX"> +                      <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>X</string> +                      </property> +                     </widget> +                    </item> +                   </layout> +                  </widget> +                 </item> +                 <item> +                  <spacer name="horizontalSpacerBRight"> +                   <property name="orientation"> +                    <enum>Qt::Horizontal</enum> +                   </property> +                   <property name="sizeHint" stdset="0"> +                    <size> +                     <width>20</width> +                     <height>20</height> +                    </size> +                   </property> +                  </spacer> +                 </item> +                </layout> +               </widget> +              </item> +              <item> +               <layout class="QHBoxLayout" name="buttonFaceButtonsYAHorizontalLayout"> +                <property name="spacing"> +                 <number>3</number> +                </property> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonFaceButtonsYGroup"> +                  <property name="title"> +                   <string>Y</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout"> +                   <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="buttonY"> +                     <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>Y</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonFaceButtonsAGroup"> +                  <property name="title"> +                   <string>A</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout"> +                   <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="buttonA"> +                     <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>A</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +               </layout> +              </item> +              <item> +               <widget class="QWidget" name="buttonFaceButtonsXWidget" native="true"> +                <layout class="QHBoxLayout" name="horizontalLayout_10"> +                 <property name="spacing"> +                  <number>0</number> +                 </property> +                 <property name="leftMargin"> +                  <number>0</number> +                 </property> +                 <property name="topMargin"> +                  <number>0</number> +                 </property> +                 <property name="rightMargin"> +                  <number>0</number> +                 </property> +                 <property name="bottomMargin"> +                  <number>0</number> +                 </property> +                 <item> +                  <spacer name="horizontalSpacerXLeft"> +                   <property name="orientation"> +                    <enum>Qt::Horizontal</enum> +                   </property> +                   <property name="sizeHint" stdset="0"> +                    <size> +                     <width>20</width> +                     <height>20</height> +                    </size> +                   </property> +                  </spacer> +                 </item> +                 <item alignment="Qt::AlignHCenter"> +                  <widget class="QGroupBox" name="buttonFaceButtonsBWidget_2"> +                   <property name="title"> +                    <string>B</string> +                   </property> +                   <property name="alignment"> +                    <set>Qt::AlignCenter</set> +                   </property> +                   <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout"> +                    <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="buttonB"> +                      <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>B</string> +                      </property> +                     </widget> +                    </item> +                   </layout> +                  </widget> +                 </item> +                 <item> +                  <spacer name="horizontalSpacerXRight"> +                   <property name="orientation"> +                    <enum>Qt::Horizontal</enum> +                   </property> +                   <property name="sizeHint" stdset="0"> +                    <size> +                     <width>20</width> +                     <height>20</height> +                    </size> +                   </property> +                  </spacer> +                 </item> +                </layout> +               </widget> +              </item> +             </layout> +            </widget> +           </item> +           <item> +            <spacer name="verticalSpacerBottomRight"> +             <property name="orientation"> +              <enum>Qt::Vertical</enum> +             </property> +             <property name="sizeHint" stdset="0"> +              <size> +               <width>20</width> +               <height>0</height> +              </size> +             </property> +            </spacer> +           </item> +           <item> +            <widget class="QGroupBox" name="RStick"> +             <property name="sizePolicy"> +              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +               <horstretch>0</horstretch> +               <verstretch>0</verstretch> +              </sizepolicy> +             </property> +             <property name="title"> +              <string>Right Stick</string> +             </property> +             <property name="alignment"> +              <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> +             </property> +             <property name="flat"> +              <bool>false</bool> +             </property> +             <property name="checkable"> +              <bool>false</bool> +             </property> +             <layout class="QVBoxLayout" name="verticalLayout_2"> +              <property name="spacing"> +               <number>0</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonMinus"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="3" column="1"> -         <spacer name="verticalSpacer_2"> -          <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 row="0" column="0"> -         <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonMiscPlusHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelPlus"> -              <property name="text"> -               <string>Plus:</string> +              <property name="leftMargin"> +               <number>3</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonPlus"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="0" column="1"> -         <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonMiscHomeHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelHome"> -              <property name="text"> -               <string>Home:</string> +              <property name="topMargin"> +               <number>0</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonHome"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="1" column="1"> -         <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout"> -          <item> -           <layout class="QHBoxLayout" name="buttonMiscScrCapHorizontalLayout"> -            <item> -             <widget class="QLabel" name="labelScreenshot"> -              <property name="text"> -               <string>Screen Capture:</string> +              <property name="rightMargin"> +               <number>3</number>                </property> -              <property name="wordWrap"> -               <bool>false</bool> +              <property name="bottomMargin"> +               <number>0</number>                </property> -             </widget> -            </item> -           </layout> -          </item> -          <item> -           <widget class="QPushButton" name="buttonScreenshot"> -            <property name="sizePolicy"> -             <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> -              <horstretch>0</horstretch> -              <verstretch>0</verstretch> -             </sizepolicy> -            </property> -            <property name="maximumSize"> -             <size> -              <width>80</width> -              <height>16777215</height> -             </size> -            </property> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +              <item> +               <widget class="QWidget" name="buttonRStickUpWidget" native="true"> +                <property name="minimumSize"> +                 <size> +                  <width>0</width> +                  <height>0</height> +                 </size> +                </property> +                <layout class="QHBoxLayout" name="horizontalLayout_9"> +                 <property name="spacing"> +                  <number>0</number> +                 </property> +                 <property name="leftMargin"> +                  <number>0</number> +                 </property> +                 <property name="topMargin"> +                  <number>0</number> +                 </property> +                 <property name="rightMargin"> +                  <number>0</number> +                 </property> +                 <property name="bottomMargin"> +                  <number>0</number> +                 </property> +                 <item> +                  <spacer name="horizontalSpacerRStickUpLeft"> +                   <property name="orientation"> +                    <enum>Qt::Horizontal</enum> +                   </property> +                   <property name="sizeHint" stdset="0"> +                    <size> +                     <width>20</width> +                     <height>20</height> +                    </size> +                   </property> +                  </spacer> +                 </item> +                 <item alignment="Qt::AlignHCenter"> +                  <widget class="QGroupBox" name="buttonRStickUpGroup"> +                   <property name="title"> +                    <string>Up</string> +                   </property> +                   <property name="alignment"> +                    <set>Qt::AlignCenter</set> +                   </property> +                   <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout"> +                    <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="buttonRStickUp"> +                      <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>Up</string> +                      </property> +                     </widget> +                    </item> +                   </layout> +                  </widget> +                 </item> +                 <item> +                  <spacer name="horizontalSpacerRStickUpRight"> +                   <property name="orientation"> +                    <enum>Qt::Horizontal</enum> +                   </property> +                   <property name="sizeHint" stdset="0"> +                    <size> +                     <width>20</width> +                     <height>20</height> +                    </size> +                   </property> +                  </spacer> +                 </item> +                </layout> +               </widget> +              </item> +              <item> +               <layout class="QHBoxLayout" name="buttonRStickLeftRightHorizontalLayout"> +                <property name="spacing"> +                 <number>3</number> +                </property> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonRStickLeftGroup"> +                  <property name="title"> +                   <string>Left</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout"> +                   <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="buttonRStickLeft"> +                     <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>Left</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonRStickRightGroup"> +                  <property name="title"> +                   <string>Right</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout"> +                   <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="buttonRStickRight"> +                     <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>Right</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +               </layout> +              </item> +              <item> +               <widget class="QWidget" name="buttonRStickDownWidget" native="true"> +                <layout class="QHBoxLayout" name="horizontalLayout_11"> +                 <property name="spacing"> +                  <number>0</number> +                 </property> +                 <property name="leftMargin"> +                  <number>0</number> +                 </property> +                 <property name="topMargin"> +                  <number>0</number> +                 </property> +                 <property name="rightMargin"> +                  <number>0</number> +                 </property> +                 <property name="bottomMargin"> +                  <number>0</number> +                 </property> +                 <item> +                  <spacer name="horizontalSpacerRStickDownLeft"> +                   <property name="orientation"> +                    <enum>Qt::Horizontal</enum> +                   </property> +                   <property name="sizeHint" stdset="0"> +                    <size> +                     <width>20</width> +                     <height>20</height> +                    </size> +                   </property> +                  </spacer> +                 </item> +                 <item> +                  <widget class="QGroupBox" name="buttonRStickDownGroup"> +                   <property name="title"> +                    <string>Down</string> +                   </property> +                   <property name="alignment"> +                    <set>Qt::AlignCenter</set> +                   </property> +                   <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout"> +                    <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="buttonRStickDown"> +                      <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>Down</string> +                      </property> +                     </widget> +                    </item> +                   </layout> +                  </widget> +                 </item> +                 <item> +                  <spacer name="horizontalSpacerRStickDownRight"> +                   <property name="orientation"> +                    <enum>Qt::Horizontal</enum> +                   </property> +                   <property name="sizeHint" stdset="0"> +                    <size> +                     <width>20</width> +                     <height>20</height> +                    </size> +                   </property> +                  </spacer> +                 </item> +                </layout> +               </widget> +              </item> +              <item> +               <layout class="QHBoxLayout" name="buttonRStickPressedModifierHorizontalLayout"> +                <property name="spacing"> +                 <number>3</number> +                </property> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="groupRStickPressed"> +                  <property name="title"> +                   <string>Pressed</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout"> +                   <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="buttonRStick"> +                     <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>Pressed</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +                <item alignment="Qt::AlignHCenter"> +                 <widget class="QGroupBox" name="buttonRStickModGroup"> +                  <property name="title"> +                   <string>Modifier</string> +                  </property> +                  <property name="alignment"> +                   <set>Qt::AlignCenter</set> +                  </property> +                  <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout"> +                   <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="buttonRStickMod"> +                     <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>Modifier</string> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +                <item> +                 <widget class="QGroupBox" name="buttonRStickRangeGroup"> +                  <property name="title"> +                   <string>Range</string> +                  </property> +                  <layout class="QHBoxLayout" name="buttonRStickRangeGroupHorizontalLayout"> +                   <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="QSpinBox" name="spinboxRStickRange"> +                     <property name="minimumSize"> +                      <size> +                       <width>68</width> +                       <height>21</height> +                      </size> +                     </property> +                     <property name="maximumSize"> +                      <size> +                       <width>68</width> +                       <height>16777215</height> +                      </size> +                     </property> +                     <property name="suffix"> +                      <string>%</string> +                     </property> +                     <property name="minimum"> +                      <number>50</number> +                     </property> +                     <property name="maximum"> +                      <number>150</number> +                     </property> +                     <property name="value"> +                      <number>100</number> +                     </property> +                    </widget> +                   </item> +                  </layout> +                 </widget> +                </item> +               </layout> +              </item> +              <item> +               <layout class="QVBoxLayout" name="sliderRStickDeadzoneModifierRangeVerticalLayout"> +                <property name="spacing"> +                 <number>3</number> +                </property> +                <property name="leftMargin"> +                 <number>0</number> +                </property> +                <property name="topMargin"> +                 <number>2</number> +                </property> +                <property name="rightMargin"> +                 <number>0</number> +                </property> +                <property name="bottomMargin"> +                 <number>3</number> +                </property> +                <item> +                 <layout class="QHBoxLayout" name="sliderRStickDeadzoneHorizontalLayout"> +                  <item> +                   <widget class="QLabel" name="labelRStickDeadzone"> +                    <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="sliderRStickDeadzone"> +                  <property name="maximum"> +                   <number>100</number> +                  </property> +                  <property name="orientation"> +                   <enum>Qt::Horizontal</enum> +                  </property> +                 </widget> +                </item> +                <item> +                 <layout class="QHBoxLayout" name="sliderRStickModifierRangeHorizontalLayout"> +                  <item> +                   <widget class="QLabel" name="labelRStickModifierRange"> +                    <property name="text"> +                     <string>Modifier Range: 0%</string> +                    </property> +                    <property name="alignment"> +                     <set>Qt::AlignHCenter</set> +                    </property> +                   </widget> +                  </item> +                 </layout> +                </item> +                <item> +                 <widget class="QSlider" name="sliderRStickModifierRange"> +                  <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="verticalSpacerBottomRight_2"> +             <property name="orientation"> +              <enum>Qt::Vertical</enum> +             </property> +             <property name="sizeHint" stdset="0"> +              <size> +               <width>20</width> +               <height>0</height> +              </size> +             </property> +            </spacer> +           </item> +          </layout> +         </widget>          </item>         </layout>        </widget>       </item>      </layout>     </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> -   <item> -    <layout class="QHBoxLayout" name="horizontalLayout_2"> -     <item> -      <widget class="QPushButton" name="buttonClearAll"> -       <property name="sizePolicy"> -        <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> -         <horstretch>0</horstretch> -         <verstretch>0</verstretch> -        </sizepolicy> -       </property> -       <property name="sizeIncrement"> -        <size> -         <width>0</width> -         <height>0</height> -        </size> -       </property> -       <property name="baseSize"> -        <size> -         <width>0</width> -         <height>0</height> -        </size> -       </property> -       <property name="layoutDirection"> -        <enum>Qt::LeftToRight</enum> -       </property> -       <property name="text"> -        <string>Clear All</string> -       </property> -      </widget> -     </item> -     <item> -      <widget class="QPushButton" name="buttonRestoreDefaults"> -       <property name="sizePolicy"> -        <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> -         <horstretch>0</horstretch> -         <verstretch>0</verstretch> -        </sizepolicy> -       </property> -       <property name="sizeIncrement"> -        <size> -         <width>0</width> -         <height>0</height> -        </size> -       </property> -       <property name="baseSize"> -        <size> -         <width>0</width> -         <height>0</height> -        </size> -       </property> -       <property name="layoutDirection"> -        <enum>Qt::LeftToRight</enum> -       </property> -       <property name="text"> -        <string>Restore Defaults</string> -       </property> -      </widget> -     </item> -     <item> -      <spacer name="horizontalSpacer"> -       <property name="orientation"> -        <enum>Qt::Horizontal</enum> -       </property> -       <property name="sizeHint" stdset="0"> -        <size> -         <width>40</width> -         <height>20</height> -        </size> -       </property> -      </spacer> -     </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>ConfigureInputPlayer</receiver> -   <slot>accept()</slot> -   <hints> -    <hint type="sourcelabel"> -     <x>371</x> -     <y>730</y> -    </hint> -    <hint type="destinationlabel"> -     <x>229</x> -     <y>375</y> -    </hint> -   </hints> -  </connection> -  <connection> -   <sender>buttonBox</sender> -   <signal>rejected()</signal> -   <receiver>ConfigureInputPlayer</receiver> -   <slot>reject()</slot> -   <hints> -    <hint type="sourcelabel"> -     <x>371</x> -     <y>730</y> -    </hint> -    <hint type="destinationlabel"> -     <x>229</x> -     <y>375</y> -    </hint> -   </hints> -  </connection> - </connections> + <resources> +  <include location="../../../dist/icons/controller/controller.qrc"/> + </resources> + <connections/>  </ui> diff --git a/src/yuzu/configuration/configure_input_profile_dialog.cpp b/src/yuzu/configuration/configure_input_profile_dialog.cpp new file mode 100644 index 000000000..1f5cfa75b --- /dev/null +++ b/src/yuzu/configuration/configure_input_profile_dialog.cpp @@ -0,0 +1,37 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "ui_configure_input_profile_dialog.h" +#include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_input_profile_dialog.h" + +ConfigureInputProfileDialog::ConfigureInputProfileDialog( +    QWidget* parent, InputCommon::InputSubsystem* input_subsystem, InputProfiles* profiles) +    : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputProfileDialog>()), +      profile_widget(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, false)) { +    ui->setupUi(this); + +    ui->controllerLayout->addWidget(profile_widget); + +    connect(ui->clear_all_button, &QPushButton::clicked, this, +            [this] { profile_widget->ClearAll(); }); +    connect(ui->restore_defaults_button, &QPushButton::clicked, this, +            [this] { profile_widget->RestoreDefaults(); }); + +    RetranslateUI(); +} + +ConfigureInputProfileDialog::~ConfigureInputProfileDialog() = default; + +void ConfigureInputProfileDialog::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QDialog::changeEvent(event); +} + +void ConfigureInputProfileDialog::RetranslateUI() { +    ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_input_profile_dialog.h b/src/yuzu/configuration/configure_input_profile_dialog.h new file mode 100644 index 000000000..e6386bdbb --- /dev/null +++ b/src/yuzu/configuration/configure_input_profile_dialog.h @@ -0,0 +1,40 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QDialog> + +class QPushButton; + +class ConfigureInputPlayer; + +class InputProfiles; + +namespace InputCommon { +class InputSubsystem; +} + +namespace Ui { +class ConfigureInputProfileDialog; +} + +class ConfigureInputProfileDialog : public QDialog { +    Q_OBJECT + +public: +    explicit ConfigureInputProfileDialog(QWidget* parent, +                                         InputCommon::InputSubsystem* input_subsystem, +                                         InputProfiles* profiles); +    ~ConfigureInputProfileDialog() override; + +private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); + +    std::unique_ptr<Ui::ConfigureInputProfileDialog> ui; + +    ConfigureInputPlayer* profile_widget; +}; diff --git a/src/yuzu/configuration/configure_input_profile_dialog.ui b/src/yuzu/configuration/configure_input_profile_dialog.ui new file mode 100644 index 000000000..726cf6905 --- /dev/null +++ b/src/yuzu/configuration/configure_input_profile_dialog.ui @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureInputProfileDialog</class> + <widget class="QDialog" name="ConfigureInputProfileDialog"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>70</width> +    <height>540</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Create Input Profile</string> +  </property> +  <layout class="QVBoxLayout" name="verticalLayout"> +   <property name="spacing"> +    <number>2</number> +   </property> +   <property name="leftMargin"> +    <number>9</number> +   </property> +   <property name="topMargin"> +    <number>9</number> +   </property> +   <property name="rightMargin"> +    <number>9</number> +   </property> +   <property name="bottomMargin"> +    <number>9</number> +   </property> +   <item> +    <layout class="QHBoxLayout" name="controllerLayout"/> +   </item> +   <item> +    <layout class="QHBoxLayout" name="horizontalLayout"> +     <item> +      <widget class="QPushButton" name="clear_all_button"> +       <property name="text"> +        <string>Clear</string> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QPushButton" name="restore_defaults_button"> +       <property name="text"> +        <string>Defaults</string> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QDialogButtonBox" name="buttonBox"> +       <property name="standardButtons"> +        <set>QDialogButtonBox::Ok</set> +       </property> +      </widget> +     </item> +    </layout> +   </item> +  </layout> + </widget> + <resources/> + <connections> +  <connection> +   <sender>buttonBox</sender> +   <signal>accepted()</signal> +   <receiver>ConfigureInputProfileDialog</receiver> +   <slot>accept()</slot> +  </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_input_simple.cpp b/src/yuzu/configuration/configure_input_simple.cpp deleted file mode 100644 index 0e0e8f113..000000000 --- a/src/yuzu/configuration/configure_input_simple.cpp +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <array> -#include <tuple> - -#include "ui_configure_input_simple.h" -#include "yuzu/configuration/configure_input.h" -#include "yuzu/configuration/configure_input_player.h" -#include "yuzu/configuration/configure_input_simple.h" -#include "yuzu/uisettings.h" - -namespace { - -template <typename Dialog, typename... Args> -void CallConfigureDialog(ConfigureInputSimple* caller, Args&&... args) { -    caller->ApplyConfiguration(); -    Dialog dialog(caller, std::forward<Args>(args)...); - -    const auto res = dialog.exec(); -    if (res == QDialog::Accepted) { -        dialog.ApplyConfiguration(); -    } -} - -// OnProfileSelect functions should (when applicable): -// - Set controller types -// - Set controller enabled -// - Set docked mode -// - Set advanced controller config/enabled (i.e. debug, kbd, mouse, touch) -// -// OnProfileSelect function should NOT however: -// - Reset any button mappings -// - Open any dialogs -// - Block in any way - -constexpr std::size_t PLAYER_0_INDEX = 0; -constexpr std::size_t HANDHELD_INDEX = 8; - -void HandheldOnProfileSelect() { -    Settings::values.players[HANDHELD_INDEX].connected = true; -    Settings::values.players[HANDHELD_INDEX].type = Settings::ControllerType::DualJoycon; - -    for (std::size_t player = 0; player < HANDHELD_INDEX; ++player) { -        Settings::values.players[player].connected = false; -    } - -    Settings::values.use_docked_mode = false; -    Settings::values.keyboard_enabled = false; -    Settings::values.mouse_enabled = false; -    Settings::values.debug_pad_enabled = false; -    Settings::values.touchscreen.enabled = true; -} - -void DualJoyconsDockedOnProfileSelect() { -    Settings::values.players[PLAYER_0_INDEX].connected = true; -    Settings::values.players[PLAYER_0_INDEX].type = Settings::ControllerType::DualJoycon; - -    for (std::size_t player = 1; player <= HANDHELD_INDEX; ++player) { -        Settings::values.players[player].connected = false; -    } - -    Settings::values.use_docked_mode = true; -    Settings::values.keyboard_enabled = false; -    Settings::values.mouse_enabled = false; -    Settings::values.debug_pad_enabled = false; -    Settings::values.touchscreen.enabled = true; -} - -// Name, OnProfileSelect (called when selected in drop down), OnConfigure (called when configure -// is clicked) -using InputProfile = std::tuple<const char*, void (*)(), void (*)(ConfigureInputSimple*)>; - -constexpr std::array<InputProfile, 3> INPUT_PROFILES{{ -    {QT_TR_NOOP("Single Player - Handheld - Undocked"), HandheldOnProfileSelect, -     [](ConfigureInputSimple* caller) { -         CallConfigureDialog<ConfigureInputPlayer>(caller, HANDHELD_INDEX, false); -     }}, -    {QT_TR_NOOP("Single Player - Dual Joycons - Docked"), DualJoyconsDockedOnProfileSelect, -     [](ConfigureInputSimple* caller) { -         CallConfigureDialog<ConfigureInputPlayer>(caller, PLAYER_0_INDEX, false); -     }}, -    {QT_TR_NOOP("Custom"), [] {}, CallConfigureDialog<ConfigureInput>}, -}}; - -} // namespace - -void ApplyInputProfileConfiguration(int profile_index) { -    std::get<1>( -        INPUT_PROFILES.at(std::min(profile_index, static_cast<int>(INPUT_PROFILES.size() - 1))))(); -} - -ConfigureInputSimple::ConfigureInputSimple(QWidget* parent) -    : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputSimple>()) { -    ui->setupUi(this); - -    for (const auto& profile : INPUT_PROFILES) { -        const QString label = tr(std::get<0>(profile)); -        ui->profile_combobox->addItem(label, label); -    } - -    connect(ui->profile_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, -            &ConfigureInputSimple::OnSelectProfile); -    connect(ui->profile_configure, &QPushButton::clicked, this, &ConfigureInputSimple::OnConfigure); - -    LoadConfiguration(); -} - -ConfigureInputSimple::~ConfigureInputSimple() = default; - -void ConfigureInputSimple::ApplyConfiguration() { -    auto index = ui->profile_combobox->currentIndex(); -    // Make the stored index for "Custom" very large so that if new profiles are added it -    // doesn't change. -    if (index >= static_cast<int>(INPUT_PROFILES.size() - 1)) { -        index = std::numeric_limits<int>::max(); -    } - -    UISettings::values.profile_index = index; -} - -void ConfigureInputSimple::changeEvent(QEvent* event) { -    if (event->type() == QEvent::LanguageChange) { -        RetranslateUI(); -    } - -    QWidget::changeEvent(event); -} - -void ConfigureInputSimple::RetranslateUI() { -    ui->retranslateUi(this); -} - -void ConfigureInputSimple::LoadConfiguration() { -    const auto index = UISettings::values.profile_index; -    if (index >= static_cast<int>(INPUT_PROFILES.size()) || index < 0) { -        ui->profile_combobox->setCurrentIndex(static_cast<int>(INPUT_PROFILES.size() - 1)); -    } else { -        ui->profile_combobox->setCurrentIndex(index); -    } -} - -void ConfigureInputSimple::OnSelectProfile(int index) { -    const auto old_docked = Settings::values.use_docked_mode; -    ApplyInputProfileConfiguration(index); -    OnDockedModeChanged(old_docked, Settings::values.use_docked_mode); -} - -void ConfigureInputSimple::OnConfigure() { -    std::get<2>(INPUT_PROFILES.at(ui->profile_combobox->currentIndex()))(this); -} diff --git a/src/yuzu/configuration/configure_input_simple.h b/src/yuzu/configuration/configure_input_simple.h deleted file mode 100644 index bb5050224..000000000 --- a/src/yuzu/configuration/configure_input_simple.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> - -#include <QWidget> - -class QPushButton; -class QString; -class QTimer; - -namespace Ui { -class ConfigureInputSimple; -} - -// Used by configuration loader to apply a profile if the input is invalid. -void ApplyInputProfileConfiguration(int profile_index); - -class ConfigureInputSimple : public QWidget { -    Q_OBJECT - -public: -    explicit ConfigureInputSimple(QWidget* parent = nullptr); -    ~ConfigureInputSimple() override; - -    /// Save all button configurations to settings file -    void ApplyConfiguration(); - -private: -    void changeEvent(QEvent* event) override; -    void RetranslateUI(); - -    /// Load configuration settings. -    void LoadConfiguration(); - -    void OnSelectProfile(int index); -    void OnConfigure(); - -    std::unique_ptr<Ui::ConfigureInputSimple> ui; -}; diff --git a/src/yuzu/configuration/configure_input_simple.ui b/src/yuzu/configuration/configure_input_simple.ui deleted file mode 100644 index c4889caa9..000000000 --- a/src/yuzu/configuration/configure_input_simple.ui +++ /dev/null @@ -1,97 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>ConfigureInputSimple</class> - <widget class="QWidget" name="ConfigureInputSimple"> -  <property name="geometry"> -   <rect> -    <x>0</x> -    <y>0</y> -    <width>473</width> -    <height>685</height> -   </rect> -  </property> -  <property name="windowTitle"> -   <string>ConfigureInputSimple</string> -  </property> -  <layout class="QVBoxLayout" name="verticalLayout_5"> -   <item> -    <layout class="QVBoxLayout" name="verticalLayout"> -     <item> -      <widget class="QGroupBox" name="gridGroupBox"> -       <property name="title"> -        <string>Profile</string> -       </property> -       <layout class="QGridLayout" name="gridLayout"> -        <item row="1" column="2"> -         <widget class="QPushButton" name="profile_configure"> -          <property name="text"> -           <string>Configure</string> -          </property> -         </widget> -        </item> -        <item row="1" column="0"> -         <spacer name="horizontalSpacer"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>40</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -        <item row="1" column="3"> -         <spacer name="horizontalSpacer_2"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>40</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -        <item row="1" column="1"> -         <widget class="QComboBox" name="profile_combobox"> -          <property name="minimumSize"> -           <size> -            <width>250</width> -            <height>0</height> -           </size> -          </property> -         </widget> -        </item> -        <item row="0" column="1" colspan="2"> -         <widget class="QLabel" name="label"> -          <property name="text"> -           <string>Choose a controller configuration:</string> -          </property> -         </widget> -        </item> -       </layout> -      </widget> -     </item> -    </layout> -   </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> -  </layout> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp new file mode 100644 index 000000000..170574d9b --- /dev/null +++ b/src/yuzu/configuration/configure_motion_touch.cpp @@ -0,0 +1,314 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <QCloseEvent> +#include <QLabel> +#include <QMessageBox> +#include <QPushButton> +#include <QVBoxLayout> +#include "common/logging/log.h" +#include "core/settings.h" +#include "input_common/main.h" +#include "input_common/udp/client.h" +#include "input_common/udp/udp.h" +#include "ui_configure_motion_touch.h" +#include "yuzu/configuration/configure_motion_touch.h" +#include "yuzu/configuration/configure_touch_from_button.h" + +CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent, +                                                               const std::string& host, u16 port, +                                                               u8 pad_index, u16 client_id) +    : QDialog(parent) { +    layout = new QVBoxLayout; +    status_label = new QLabel(tr("Communicating with the server...")); +    cancel_button = new QPushButton(tr("Cancel")); +    connect(cancel_button, &QPushButton::clicked, this, [this] { +        if (!completed) { +            job->Stop(); +        } +        accept(); +    }); +    layout->addWidget(status_label); +    layout->addWidget(cancel_button); +    setLayout(layout); + +    using namespace InputCommon::CemuhookUDP; +    job = std::make_unique<CalibrationConfigurationJob>( +        host, port, pad_index, client_id, +        [this](CalibrationConfigurationJob::Status status) { +            QString text; +            switch (status) { +            case CalibrationConfigurationJob::Status::Ready: +                text = tr("Touch the top left corner <br>of your touchpad."); +                break; +            case CalibrationConfigurationJob::Status::Stage1Completed: +                text = tr("Now touch the bottom right corner <br>of your touchpad."); +                break; +            case CalibrationConfigurationJob::Status::Completed: +                text = tr("Configuration completed!"); +                break; +            } +            QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text)); +            if (status == CalibrationConfigurationJob::Status::Completed) { +                QMetaObject::invokeMethod(this, "UpdateButtonText", Q_ARG(QString, tr("OK"))); +            } +        }, +        [this](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) { +            completed = true; +            min_x = min_x_; +            min_y = min_y_; +            max_x = max_x_; +            max_y = max_y_; +        }); +} + +CalibrationConfigurationDialog::~CalibrationConfigurationDialog() = default; + +void CalibrationConfigurationDialog::UpdateLabelText(const QString& text) { +    status_label->setText(text); +} + +void CalibrationConfigurationDialog::UpdateButtonText(const QString& text) { +    cancel_button->setText(text); +} + +constexpr std::array<std::pair<const char*, const char*>, 2> MotionProviders = {{ +    {"motion_emu", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Mouse (Right Click)")}, +    {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")}, +}}; + +constexpr std::array<std::pair<const char*, const char*>, 2> TouchProviders = {{ +    {"emu_window", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Emulator Window")}, +    {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")}, +}}; + +ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent, +                                           InputCommon::InputSubsystem* input_subsystem_) +    : QDialog(parent), input_subsystem{input_subsystem_}, +      ui(std::make_unique<Ui::ConfigureMotionTouch>()) { +    ui->setupUi(this); +    for (const auto& [provider, name] : MotionProviders) { +        ui->motion_provider->addItem(tr(name), QString::fromUtf8(provider)); +    } +    for (const auto& [provider, name] : TouchProviders) { +        ui->touch_provider->addItem(tr(name), QString::fromUtf8(provider)); +    } + +    ui->udp_learn_more->setOpenExternalLinks(true); +    ui->udp_learn_more->setText( +        tr("<a " +           "href='https://yuzu-emu.org/wiki/" +           "using-a-controller-or-android-phone-for-motion-or-touch-input'><span " +           "style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>")); + +    SetConfiguration(); +    UpdateUiDisplay(); +    ConnectEvents(); +} + +ConfigureMotionTouch::~ConfigureMotionTouch() = default; + +void ConfigureMotionTouch::SetConfiguration() { +    const Common::ParamPackage motion_param(Settings::values.motion_device); +    const Common::ParamPackage touch_param(Settings::values.touch_device); +    const std::string motion_engine = motion_param.Get("engine", "motion_emu"); +    const std::string touch_engine = touch_param.Get("engine", "emu_window"); + +    ui->motion_provider->setCurrentIndex( +        ui->motion_provider->findData(QString::fromStdString(motion_engine))); +    ui->touch_provider->setCurrentIndex( +        ui->touch_provider->findData(QString::fromStdString(touch_engine))); +    ui->touch_from_button_checkbox->setChecked(Settings::values.use_touch_from_button); +    touch_from_button_maps = Settings::values.touch_from_button_maps; +    for (const auto& touch_map : touch_from_button_maps) { +        ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name)); +    } +    ui->touch_from_button_map->setCurrentIndex(Settings::values.touch_from_button_map_index); +    ui->motion_sensitivity->setValue(motion_param.Get("sensitivity", 0.01f)); + +    min_x = touch_param.Get("min_x", 100); +    min_y = touch_param.Get("min_y", 50); +    max_x = touch_param.Get("max_x", 1800); +    max_y = touch_param.Get("max_y", 850); + +    ui->udp_server->setText(QString::fromStdString(Settings::values.udp_input_address)); +    ui->udp_port->setText(QString::number(Settings::values.udp_input_port)); +    ui->udp_pad_index->setCurrentIndex(Settings::values.udp_pad_index); +} + +void ConfigureMotionTouch::UpdateUiDisplay() { +    const QString motion_engine = ui->motion_provider->currentData().toString(); +    const QString touch_engine = ui->touch_provider->currentData().toString(); +    const QString cemuhook_udp = QStringLiteral("cemuhookudp"); + +    if (motion_engine == QStringLiteral("motion_emu")) { +        ui->motion_sensitivity_label->setVisible(true); +        ui->motion_sensitivity->setVisible(true); +    } else { +        ui->motion_sensitivity_label->setVisible(false); +        ui->motion_sensitivity->setVisible(false); +    } + +    if (touch_engine == cemuhook_udp) { +        ui->touch_calibration->setVisible(true); +        ui->touch_calibration_config->setVisible(true); +        ui->touch_calibration_label->setVisible(true); +        ui->touch_calibration->setText( +            QStringLiteral("(%1, %2) - (%3, %4)").arg(min_x).arg(min_y).arg(max_x).arg(max_y)); +    } else { +        ui->touch_calibration->setVisible(false); +        ui->touch_calibration_config->setVisible(false); +        ui->touch_calibration_label->setVisible(false); +    } + +    if (motion_engine == cemuhook_udp || touch_engine == cemuhook_udp) { +        ui->udp_config_group_box->setVisible(true); +    } else { +        ui->udp_config_group_box->setVisible(false); +    } +} + +void ConfigureMotionTouch::ConnectEvents() { +    connect(ui->motion_provider, qOverload<int>(&QComboBox::currentIndexChanged), this, +            [this](int index) { UpdateUiDisplay(); }); +    connect(ui->touch_provider, qOverload<int>(&QComboBox::currentIndexChanged), this, +            [this](int index) { UpdateUiDisplay(); }); +    connect(ui->udp_test, &QPushButton::clicked, this, &ConfigureMotionTouch::OnCemuhookUDPTest); +    connect(ui->touch_calibration_config, &QPushButton::clicked, this, +            &ConfigureMotionTouch::OnConfigureTouchCalibration); +    connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this, +            &ConfigureMotionTouch::OnConfigureTouchFromButton); +    connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] { +        if (CanCloseDialog()) { +            reject(); +        } +    }); +} + +void ConfigureMotionTouch::OnCemuhookUDPTest() { +    ui->udp_test->setEnabled(false); +    ui->udp_test->setText(tr("Testing")); +    udp_test_in_progress = true; +    InputCommon::CemuhookUDP::TestCommunication( +        ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toInt()), +        static_cast<u32>(ui->udp_pad_index->currentIndex()), 24872, +        [this] { +            LOG_INFO(Frontend, "UDP input test success"); +            QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, true)); +        }, +        [this] { +            LOG_ERROR(Frontend, "UDP input test failed"); +            QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, false)); +        }); +} + +void ConfigureMotionTouch::OnConfigureTouchCalibration() { +    ui->touch_calibration_config->setEnabled(false); +    ui->touch_calibration_config->setText(tr("Configuring")); +    CalibrationConfigurationDialog dialog( +        this, ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toUInt()), +        static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872); +    dialog.exec(); +    if (dialog.completed) { +        min_x = dialog.min_x; +        min_y = dialog.min_y; +        max_x = dialog.max_x; +        max_y = dialog.max_y; +        LOG_INFO(Frontend, +                 "UDP touchpad calibration config success: min_x={}, min_y={}, max_x={}, max_y={}", +                 min_x, min_y, max_x, max_y); +        UpdateUiDisplay(); +    } else { +        LOG_ERROR(Frontend, "UDP touchpad calibration config failed"); +    } +    ui->touch_calibration_config->setEnabled(true); +    ui->touch_calibration_config->setText(tr("Configure")); +} + +void ConfigureMotionTouch::closeEvent(QCloseEvent* event) { +    if (CanCloseDialog()) { +        event->accept(); +    } else { +        event->ignore(); +    } +} + +void ConfigureMotionTouch::ShowUDPTestResult(bool result) { +    udp_test_in_progress = false; +    if (result) { +        QMessageBox::information(this, tr("Test Successful"), +                                 tr("Successfully received data from the server.")); +    } else { +        QMessageBox::warning(this, tr("Test Failed"), +                             tr("Could not receive valid data from the server.<br>Please verify " +                                "that the server is set up correctly and " +                                "the address and port are correct.")); +    } +    ui->udp_test->setEnabled(true); +    ui->udp_test->setText(tr("Test")); +} + +void ConfigureMotionTouch::OnConfigureTouchFromButton() { +    ConfigureTouchFromButton dialog{this, touch_from_button_maps, input_subsystem, +                                    ui->touch_from_button_map->currentIndex()}; +    if (dialog.exec() != QDialog::Accepted) { +        return; +    } +    touch_from_button_maps = dialog.GetMaps(); + +    while (ui->touch_from_button_map->count() > 0) { +        ui->touch_from_button_map->removeItem(0); +    } +    for (const auto& touch_map : touch_from_button_maps) { +        ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name)); +    } +    ui->touch_from_button_map->setCurrentIndex(dialog.GetSelectedIndex()); +} + +bool ConfigureMotionTouch::CanCloseDialog() { +    if (udp_test_in_progress) { +        QMessageBox::warning(this, tr("Citra"), +                             tr("UDP Test or calibration configuration is in progress.<br>Please " +                                "wait for them to finish.")); +        return false; +    } +    return true; +} + +void ConfigureMotionTouch::ApplyConfiguration() { +    if (!CanCloseDialog()) { +        return; +    } + +    std::string motion_engine = ui->motion_provider->currentData().toString().toStdString(); +    std::string touch_engine = ui->touch_provider->currentData().toString().toStdString(); + +    Common::ParamPackage motion_param{}, touch_param{}; +    motion_param.Set("engine", std::move(motion_engine)); +    touch_param.Set("engine", std::move(touch_engine)); + +    if (motion_engine == "motion_emu") { +        motion_param.Set("sensitivity", static_cast<float>(ui->motion_sensitivity->value())); +    } + +    if (touch_engine == "cemuhookudp") { +        touch_param.Set("min_x", min_x); +        touch_param.Set("min_y", min_y); +        touch_param.Set("max_x", max_x); +        touch_param.Set("max_y", max_y); +    } + +    Settings::values.motion_device = motion_param.Serialize(); +    Settings::values.touch_device = touch_param.Serialize(); +    Settings::values.use_touch_from_button = ui->touch_from_button_checkbox->isChecked(); +    Settings::values.touch_from_button_map_index = ui->touch_from_button_map->currentIndex(); +    Settings::values.touch_from_button_maps = touch_from_button_maps; +    Settings::values.udp_input_address = ui->udp_server->text().toStdString(); +    Settings::values.udp_input_port = static_cast<u16>(ui->udp_port->text().toInt()); +    Settings::values.udp_pad_index = static_cast<u8>(ui->udp_pad_index->currentIndex()); +    input_subsystem->ReloadInputDevices(); + +    accept(); +} diff --git a/src/yuzu/configuration/configure_motion_touch.h b/src/yuzu/configuration/configure_motion_touch.h new file mode 100644 index 000000000..3d4b5d659 --- /dev/null +++ b/src/yuzu/configuration/configure_motion_touch.h @@ -0,0 +1,90 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QDialog> +#include "common/param_package.h" + +class QLabel; +class QPushButton; +class QVBoxLayout; + +namespace InputCommon { +class InputSubsystem; +} + +namespace InputCommon::CemuhookUDP { +class CalibrationConfigurationJob; +} + +namespace Ui { +class ConfigureMotionTouch; +} + +/// A dialog for touchpad calibration configuration. +class CalibrationConfigurationDialog : public QDialog { +    Q_OBJECT +public: +    explicit CalibrationConfigurationDialog(QWidget* parent, const std::string& host, u16 port, +                                            u8 pad_index, u16 client_id); +    ~CalibrationConfigurationDialog() override; + +private: +    Q_INVOKABLE void UpdateLabelText(const QString& text); +    Q_INVOKABLE void UpdateButtonText(const QString& text); + +    QVBoxLayout* layout; +    QLabel* status_label; +    QPushButton* cancel_button; +    std::unique_ptr<InputCommon::CemuhookUDP::CalibrationConfigurationJob> job; + +    // Configuration results +    bool completed{}; +    u16 min_x{}; +    u16 min_y{}; +    u16 max_x{}; +    u16 max_y{}; + +    friend class ConfigureMotionTouch; +}; + +class ConfigureMotionTouch : public QDialog { +    Q_OBJECT + +public: +    explicit ConfigureMotionTouch(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_); +    ~ConfigureMotionTouch() override; + +public slots: +    void ApplyConfiguration(); + +private slots: +    void OnCemuhookUDPTest(); +    void OnConfigureTouchCalibration(); +    void OnConfigureTouchFromButton(); + +private: +    void closeEvent(QCloseEvent* event) override; +    Q_INVOKABLE void ShowUDPTestResult(bool result); +    void SetConfiguration(); +    void UpdateUiDisplay(); +    void ConnectEvents(); +    bool CanCloseDialog(); + +    InputCommon::InputSubsystem* input_subsystem; + +    std::unique_ptr<Ui::ConfigureMotionTouch> ui; + +    // Coordinate system of the CemuhookUDP touch provider +    int min_x{}; +    int min_y{}; +    int max_x{}; +    int max_y{}; + +    bool udp_test_in_progress{}; + +    std::vector<Settings::TouchFromButtonMap> touch_from_button_maps; +}; diff --git a/src/yuzu/configuration/configure_motion_touch.ui b/src/yuzu/configuration/configure_motion_touch.ui new file mode 100644 index 000000000..5b78c5a4b --- /dev/null +++ b/src/yuzu/configuration/configure_motion_touch.ui @@ -0,0 +1,317 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureMotionTouch</class> + <widget class="QDialog" name="ConfigureMotionTouch"> +  <property name="windowTitle"> +   <string>Configure Motion / Touch</string> +  </property> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>500</width> +    <height>450</height> +   </rect> +  </property> +  <layout class="QVBoxLayout"> +   <item> +    <widget class="QGroupBox" name="motion_group_box"> +     <property name="title"> +      <string>Motion</string> +     </property> +     <layout class="QVBoxLayout"> +      <item> +       <layout class="QHBoxLayout"> +        <item> +         <widget class="QLabel" name="motion_provider_label"> +          <property name="text"> +           <string>Motion Provider:</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QComboBox" name="motion_provider"/> +        </item> +       </layout> +      </item> +      <item> +       <layout class="QHBoxLayout"> +        <item> +         <widget class="QLabel" name="motion_sensitivity_label"> +          <property name="text"> +           <string>Sensitivity:</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QDoubleSpinBox" name="motion_sensitivity"> +          <property name="alignment"> +           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> +          </property> +          <property name="decimals"> +           <number>4</number> +          </property> +          <property name="minimum"> +           <double>0.010000000000000</double> +          </property> +          <property name="maximum"> +           <double>10.000000000000000</double> +          </property> +          <property name="singleStep"> +           <double>0.001000000000000</double> +          </property> +          <property name="value"> +           <double>0.010000000000000</double> +          </property> +         </widget> +        </item> +       </layout> +      </item> +     </layout> +    </widget> +   </item> +   <item> +    <widget class="QGroupBox" name="touch_group_box"> +     <property name="title"> +      <string>Touch</string> +     </property> +     <layout class="QVBoxLayout"> +      <item> +       <layout class="QHBoxLayout"> +        <item> +         <widget class="QLabel" name="touch_provider_label"> +          <property name="text"> +           <string>Touch Provider:</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QComboBox" name="touch_provider"/> +        </item> +       </layout> +      </item> +      <item> +       <layout class="QHBoxLayout"> +        <item> +         <widget class="QLabel" name="touch_calibration_label"> +          <property name="text"> +           <string>Calibration:</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QLabel" name="touch_calibration"> +          <property name="text"> +           <string>(100, 50) - (1800, 850)</string> +          </property> +          <property name="alignment"> +           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QPushButton" name="touch_calibration_config"> +          <property name="sizePolicy"> +           <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> +            <horstretch>0</horstretch> +            <verstretch>0</verstretch> +           </sizepolicy> +          </property> +          <property name="text"> +           <string>Configure</string> +          </property> +         </widget> +        </item> +       </layout> +      </item> +      <item> +       <layout class="QHBoxLayout"> +        <item> +         <widget class="QCheckBox" name="touch_from_button_checkbox"> +          <property name="sizePolicy"> +           <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> +            <horstretch>0</horstretch> +            <verstretch>0</verstretch> +           </sizepolicy> +          </property> +          <property name="text"> +           <string>Use button mapping:</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QComboBox" name="touch_from_button_map"/> +        </item> +        <item> +         <widget class="QPushButton" name="touch_from_button_config_btn"> +          <property name="sizePolicy"> +           <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> +            <horstretch>0</horstretch> +            <verstretch>0</verstretch> +           </sizepolicy> +          </property> +          <property name="text"> +           <string>Configure</string> +          </property> +         </widget> +        </item> +       </layout> +      </item> +     </layout> +    </widget> +   </item> +   <item> +    <widget class="QGroupBox" name="udp_config_group_box"> +     <property name="title"> +      <string>CemuhookUDP Config</string> +     </property> +     <layout class="QVBoxLayout"> +      <item> +       <widget class="QLabel" name="udp_help"> +        <property name="text"> +         <string>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</string> +        </property> +        <property name="alignment"> +         <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> +        </property> +        <property name="wordWrap"> +         <bool>true</bool> +        </property> +       </widget> +      </item> +      <item> +       <layout class="QHBoxLayout"> +        <item> +         <widget class="QLabel" name="udp_server_label"> +          <property name="text"> +           <string>Server:</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QLineEdit" name="udp_server"> +          <property name="sizePolicy"> +           <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> +            <horstretch>0</horstretch> +            <verstretch>0</verstretch> +           </sizepolicy> +          </property> +         </widget> +        </item> +       </layout> +      </item> +      <item> +       <layout class="QHBoxLayout"> +        <item> +         <widget class="QLabel" name="udp_port_label"> +          <property name="text"> +           <string>Port:</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QLineEdit" name="udp_port"> +          <property name="sizePolicy"> +           <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> +            <horstretch>0</horstretch> +            <verstretch>0</verstretch> +           </sizepolicy> +          </property> +         </widget> +        </item> +       </layout> +      </item> +      <item> +       <layout class="QHBoxLayout"> +        <item> +         <widget class="QLabel" name="udp_pad_index_label"> +          <property name="text"> +           <string>Pad:</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QComboBox" name="udp_pad_index"> +          <item> +           <property name="text"> +            <string>Pad 1</string> +           </property> +          </item> +          <item> +           <property name="text"> +            <string>Pad 2</string> +           </property> +          </item> +          <item> +           <property name="text"> +            <string>Pad 3</string> +           </property> +          </item> +          <item> +           <property name="text"> +            <string>Pad 4</string> +           </property> +          </item> +         </widget> +        </item> +       </layout> +      </item> +      <item> +       <layout class="QHBoxLayout"> +        <item> +         <widget class="QLabel" name="udp_learn_more"> +          <property name="text"> +           <string>Learn More</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QPushButton" name="udp_test"> +          <property name="sizePolicy"> +           <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> +            <horstretch>0</horstretch> +            <verstretch>0</verstretch> +           </sizepolicy> +          </property> +          <property name="text"> +           <string>Test</string> +          </property> +         </widget> +        </item> +       </layout> +      </item> +     </layout> +    </widget> +   </item> +   <item> +    <spacer> +     <property name="orientation"> +      <enum>Qt::Vertical</enum> +     </property> +     <property name="sizeHint" stdset="0"> +      <size> +       <width>167</width> +       <height>55</height> +      </size> +     </property> +    </spacer> +   </item> +   <item> +    <widget class="QDialogButtonBox" name="buttonBox"> +     <property name="standardButtons"> +      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +     </property> +    </widget> +   </item> +  </layout> + </widget> + <resources/> + <connections> +  <connection> +   <sender>buttonBox</sender> +   <signal>accepted()</signal> +   <receiver>ConfigureMotionTouch</receiver> +   <slot>ApplyConfiguration()</slot> +  </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_mouse_advanced.cpp b/src/yuzu/configuration/configure_mouse_advanced.cpp index e0647ea5b..2af3afda8 100644 --- a/src/yuzu/configuration/configure_mouse_advanced.cpp +++ b/src/yuzu/configuration/configure_mouse_advanced.cpp @@ -18,6 +18,16 @@  static QString GetKeyName(int key_code) {      switch (key_code) { +    case Qt::LeftButton: +        return QObject::tr("Click 0"); +    case Qt::RightButton: +        return QObject::tr("Click 1"); +    case Qt::MiddleButton: +        return QObject::tr("Click 2"); +    case Qt::BackButton: +        return QObject::tr("Click 3"); +    case Qt::ForwardButton: +        return QObject::tr("Click 4");      case Qt::Key_Shift:          return QObject::tr("Shift");      case Qt::Key_Control: @@ -66,8 +76,10 @@ static QString ButtonToText(const Common::ParamPackage& param) {      return QObject::tr("[unknown]");  } -ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent) -    : QDialog(parent), ui(std::make_unique<Ui::ConfigureMouseAdvanced>()), +ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent, +                                               InputCommon::InputSubsystem* input_subsystem_) +    : QDialog(parent), +      ui(std::make_unique<Ui::ConfigureMouseAdvanced>()), input_subsystem{input_subsystem_},        timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {      ui->setupUi(this);      setFocusPolicy(Qt::ClickFocus); @@ -83,25 +95,29 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent)          }          button->setContextMenuPolicy(Qt::CustomContextMenu); -        connect(button, &QPushButton::clicked, [=] { +        connect(button, &QPushButton::clicked, [=, this] {              HandleClick(                  button_map[button_id], -                [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, +                [=, this](const Common::ParamPackage& params) { +                    buttons_param[button_id] = params; +                },                  InputCommon::Polling::DeviceType::Button);          }); -        connect(button, &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) { -            QMenu context_menu; -            context_menu.addAction(tr("Clear"), [&] { -                buttons_param[button_id].Clear(); -                button_map[button_id]->setText(tr("[not set]")); -            }); -            context_menu.addAction(tr("Restore Default"), [&] { -                buttons_param[button_id] = Common::ParamPackage{ -                    InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])}; -                button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); -            }); -            context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); -        }); +        connect(button, &QPushButton::customContextMenuRequested, +                [=, this](const QPoint& menu_location) { +                    QMenu context_menu; +                    context_menu.addAction(tr("Clear"), [&] { +                        buttons_param[button_id].Clear(); +                        button_map[button_id]->setText(tr("[not set]")); +                    }); +                    context_menu.addAction(tr("Restore Default"), [&] { +                        buttons_param[button_id] = +                            Common::ParamPackage{InputCommon::GenerateKeyboardParam( +                                Config::default_mouse_buttons[button_id])}; +                        button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); +                    }); +                    context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); +                });      }      connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); @@ -184,9 +200,9 @@ void ConfigureMouseAdvanced::HandleClick(      button->setText(tr("[press key]"));      button->setFocus(); -    // Keyboard keys can only be used as button devices -    want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button; -    if (want_keyboard_keys) { +    // Keyboard keys or mouse buttons can only be used as button devices +    want_keyboard_mouse = type == InputCommon::Polling::DeviceType::Button; +    if (want_keyboard_mouse) {          const auto iter = std::find(button_map.begin(), button_map.end(), button);          ASSERT(iter != button_map.end());          const auto index = std::distance(button_map.begin(), iter); @@ -195,27 +211,29 @@ void ConfigureMouseAdvanced::HandleClick(      input_setter = new_input_setter; -    device_pollers = InputCommon::Polling::GetPollers(type); +    device_pollers = input_subsystem->GetPollers(type);      for (auto& poller : device_pollers) {          poller->Start();      } -    grabKeyboard(); -    grabMouse(); -    timeout_timer->start(5000); // Cancel after 5 seconds -    poll_timer->start(200);     // Check for new inputs every 200ms +    QWidget::grabMouse(); +    QWidget::grabKeyboard(); + +    timeout_timer->start(2500); // Cancel after 2.5 seconds +    poll_timer->start(50);      // Check for new inputs every 50ms  }  void ConfigureMouseAdvanced::SetPollingResult(const Common::ParamPackage& params, bool abort) { -    releaseKeyboard(); -    releaseMouse();      timeout_timer->stop();      poll_timer->stop();      for (auto& poller : device_pollers) {          poller->Stop();      } +    QWidget::releaseMouse(); +    QWidget::releaseKeyboard(); +      if (!abort) {          (*input_setter)(params);      } @@ -224,13 +242,29 @@ void ConfigureMouseAdvanced::SetPollingResult(const Common::ParamPackage& params      input_setter = std::nullopt;  } +void ConfigureMouseAdvanced::mousePressEvent(QMouseEvent* event) { +    if (!input_setter || !event) { +        return; +    } + +    if (want_keyboard_mouse) { +        SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())}, +                         false); +    } else { +        // We don't want any mouse buttons, so don't stop polling +        return; +    } + +    SetPollingResult({}, true); +} +  void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) {      if (!input_setter || !event) {          return;      }      if (event->key() != Qt::Key_Escape) { -        if (want_keyboard_keys) { +        if (want_keyboard_mouse) {              SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},                               false);          } else { diff --git a/src/yuzu/configuration/configure_mouse_advanced.h b/src/yuzu/configuration/configure_mouse_advanced.h index 342b82412..65b6fca9a 100644 --- a/src/yuzu/configuration/configure_mouse_advanced.h +++ b/src/yuzu/configuration/configure_mouse_advanced.h @@ -8,12 +8,14 @@  #include <optional>  #include <QDialog> -#include "core/settings.h" -  class QCheckBox;  class QPushButton;  class QTimer; +namespace InputCommon { +class InputSubsystem; +} +  namespace Ui {  class ConfigureMouseAdvanced;  } @@ -22,7 +24,7 @@ class ConfigureMouseAdvanced : public QDialog {      Q_OBJECT  public: -    explicit ConfigureMouseAdvanced(QWidget* parent); +    explicit ConfigureMouseAdvanced(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_);      ~ConfigureMouseAdvanced() override;      void ApplyConfiguration(); @@ -49,11 +51,16 @@ private:      /// Finish polling and configure input using the input_setter      void SetPollingResult(const Common::ParamPackage& params, bool abort); +    /// Handle mouse button press events. +    void mousePressEvent(QMouseEvent* event) override; +      /// Handle key press events.      void keyPressEvent(QKeyEvent* event) override;      std::unique_ptr<Ui::ConfigureMouseAdvanced> ui; +    InputCommon::InputSubsystem* input_subsystem; +      /// This will be the the setting function when an input is awaiting configuration.      std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; @@ -67,5 +74,5 @@ private:      /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,      /// keyboard events are ignored. -    bool want_keyboard_keys = false; +    bool want_keyboard_mouse = false;  }; diff --git a/src/yuzu/configuration/configure_mouse_advanced.ui b/src/yuzu/configuration/configure_mouse_advanced.ui index 08245ecf0..5b99e1c37 100644 --- a/src/yuzu/configuration/configure_mouse_advanced.ui +++ b/src/yuzu/configuration/configure_mouse_advanced.ui @@ -6,13 +6,18 @@     <rect>      <x>0</x>      <y>0</y> -    <width>250</width> -    <height>261</height> +    <width>310</width> +    <height>193</height>     </rect>    </property>    <property name="windowTitle">     <string>Configure Mouse</string>    </property> +  <property name="styleSheet"> +   <string notr="true">QPushButton { +  min-width: 60px; +}</string> +  </property>    <layout class="QVBoxLayout" name="verticalLayout">     <item>      <widget class="QGroupBox" name="gridGroupBox"> @@ -20,81 +25,33 @@        <string>Mouse Buttons</string>       </property>       <layout class="QGridLayout" name="gridLayout"> -      <item row="0" column="4"> -       <spacer name="horizontalSpacer_2"> -        <property name="orientation"> -         <enum>Qt::Horizontal</enum> -        </property> -        <property name="sizeType"> -         <enum>QSizePolicy::Fixed</enum> -        </property> -        <property name="sizeHint" stdset="0"> -         <size> -          <width>20</width> -          <height>20</height> -         </size> -        </property> -       </spacer> -      </item> -      <item row="0" column="3"> -       <layout class="QVBoxLayout" name="verticalLayout_4"> +      <item row="3" column="5"> +       <layout class="QVBoxLayout" name="verticalLayout_6">          <item> -         <layout class="QHBoxLayout" name="horizontalLayout_3"> +         <layout class="QHBoxLayout" name="horizontalLayout_5">            <item> -           <widget class="QLabel" name="label_3"> +           <widget class="QLabel" name="label_5">              <property name="text"> -             <string>Right:</string> +             <string>Forward:</string>              </property>             </widget>            </item>           </layout>          </item>          <item> -         <widget class="QPushButton" name="right_button"> +         <widget class="QPushButton" name="forward_button">            <property name="minimumSize">             <size> -            <width>75</width> +            <width>68</width>              <height>0</height>             </size>            </property> -          <property name="text"> -           <string/> +          <property name="maximumSize"> +           <size> +            <width>68</width> +            <height>16777215</height> +           </size>            </property> -         </widget> -        </item> -       </layout> -      </item> -      <item row="0" column="0"> -       <spacer name="horizontalSpacer"> -        <property name="orientation"> -         <enum>Qt::Horizontal</enum> -        </property> -        <property name="sizeType"> -         <enum>QSizePolicy::Fixed</enum> -        </property> -        <property name="sizeHint" stdset="0"> -         <size> -          <width>20</width> -          <height>20</height> -         </size> -        </property> -       </spacer> -      </item> -      <item row="2" column="1"> -       <layout class="QVBoxLayout" name="verticalLayout_3"> -        <item> -         <layout class="QHBoxLayout" name="horizontalLayout_2"> -          <item> -           <widget class="QLabel" name="label_2"> -            <property name="text"> -             <string>Middle:</string> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item> -         <widget class="QPushButton" name="middle_button">            <property name="text">             <string/>            </property> @@ -123,6 +80,12 @@          </item>          <item>           <widget class="QPushButton" name="back_button"> +          <property name="minimumSize"> +           <size> +            <width>68</width> +            <height>0</height> +           </size> +          </property>            <property name="text">             <string/>            </property> @@ -147,7 +110,7 @@           <widget class="QPushButton" name="left_button">            <property name="minimumSize">             <size> -            <width>75</width> +            <width>68</width>              <height>0</height>             </size>            </property> @@ -158,21 +121,99 @@          </item>         </layout>        </item> -      <item row="3" column="3"> -       <layout class="QVBoxLayout" name="verticalLayout_6"> +      <item row="0" column="3"> +       <layout class="QVBoxLayout" name="verticalLayout_3">          <item> -         <layout class="QHBoxLayout" name="horizontalLayout_5"> +         <layout class="QHBoxLayout" name="horizontalLayout_2">            <item> -           <widget class="QLabel" name="label_5"> +           <widget class="QLabel" name="label_2">              <property name="text"> -             <string>Forward:</string> +             <string>Middle:</string>              </property>             </widget>            </item>           </layout>          </item>          <item> -         <widget class="QPushButton" name="forward_button"> +         <widget class="QPushButton" name="middle_button"> +          <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="text"> +           <string/> +          </property> +         </widget> +        </item> +       </layout> +      </item> +      <item row="0" column="6"> +       <spacer name="horizontalSpacer_2"> +        <property name="orientation"> +         <enum>Qt::Horizontal</enum> +        </property> +        <property name="sizeType"> +         <enum>QSizePolicy::Fixed</enum> +        </property> +        <property name="sizeHint" stdset="0"> +         <size> +          <width>0</width> +          <height>20</height> +         </size> +        </property> +       </spacer> +      </item> +      <item row="0" column="0"> +       <spacer name="horizontalSpacer"> +        <property name="orientation"> +         <enum>Qt::Horizontal</enum> +        </property> +        <property name="sizeType"> +         <enum>QSizePolicy::Fixed</enum> +        </property> +        <property name="sizeHint" stdset="0"> +         <size> +          <width>0</width> +          <height>20</height> +         </size> +        </property> +       </spacer> +      </item> +      <item row="0" column="5"> +       <layout class="QVBoxLayout" name="verticalLayout_4"> +        <item> +         <layout class="QHBoxLayout" name="horizontalLayout_3"> +          <item> +           <widget class="QLabel" name="label_3"> +            <property name="text"> +             <string>Right:</string> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item> +         <widget class="QPushButton" name="right_button"> +          <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="text">             <string/>            </property> @@ -180,6 +221,32 @@          </item>         </layout>        </item> +      <item row="0" column="2"> +       <spacer name="horizontalSpacer_4"> +        <property name="orientation"> +         <enum>Qt::Horizontal</enum> +        </property> +        <property name="sizeHint" stdset="0"> +         <size> +          <width>0</width> +          <height>20</height> +         </size> +        </property> +       </spacer> +      </item> +      <item row="0" column="4"> +       <spacer name="horizontalSpacer_5"> +        <property name="orientation"> +         <enum>Qt::Horizontal</enum> +        </property> +        <property name="sizeHint" stdset="0"> +         <size> +          <width>0</width> +          <height>20</height> +         </size> +        </property> +       </spacer> +      </item>       </layout>      </widget>     </item> @@ -187,15 +254,39 @@      <layout class="QHBoxLayout" name="horizontalLayout_6">       <item>        <widget class="QPushButton" name="buttonClearAll"> +       <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="text"> -        <string>Clear All</string> +        <string>Clear</string>         </property>        </widget>       </item>       <item>        <widget class="QPushButton" name="buttonRestoreDefaults"> +       <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="text"> -        <string>Restore Defaults</string> +        <string>Defaults</string>         </property>        </widget>       </item> @@ -206,21 +297,24 @@         </property>         <property name="sizeHint" stdset="0">          <size> -         <width>40</width> +         <width>0</width>           <height>20</height>          </size>         </property>        </spacer>       </item> +     <item> +      <widget class="QDialogButtonBox" name="buttonBox"> +       <property name="styleSheet"> +        <string notr="true"/> +       </property> +       <property name="standardButtons"> +        <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +       </property> +      </widget> +     </item>      </layout>     </item> -   <item> -    <widget class="QDialogButtonBox" name="buttonBox"> -     <property name="standardButtons"> -      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> -     </property> -    </widget> -   </item>    </layout>   </widget>   <resources/> @@ -230,32 +324,12 @@     <signal>accepted()</signal>     <receiver>ConfigureMouseAdvanced</receiver>     <slot>accept()</slot> -   <hints> -    <hint type="sourcelabel"> -     <x>124</x> -     <y>266</y> -    </hint> -    <hint type="destinationlabel"> -     <x>124</x> -     <y>143</y> -    </hint> -   </hints>    </connection>    <connection>     <sender>buttonBox</sender>     <signal>rejected()</signal>     <receiver>ConfigureMouseAdvanced</receiver>     <slot>reject()</slot> -   <hints> -    <hint type="sourcelabel"> -     <x>124</x> -     <y>266</y> -    </hint> -    <hint type="destinationlabel"> -     <x>124</x> -     <y>143</y> -    </hint> -   </hints>    </connection>   </connections>  </ui> diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp new file mode 100644 index 000000000..8eac3bd9d --- /dev/null +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -0,0 +1,144 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> +#include <utility> + +#include <QCheckBox> +#include <QHeaderView> +#include <QMenu> +#include <QStandardItemModel> +#include <QString> +#include <QTimer> +#include <QTreeView> + +#include "common/common_paths.h" +#include "common/file_util.h" +#include "core/core.h" +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/xts_archive.h" +#include "core/loader/loader.h" +#include "ui_configure_per_game.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_per_game.h" +#include "yuzu/uisettings.h" +#include "yuzu/util/util.h" + +ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id) +    : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id(title_id) { +    game_config = std::make_unique<Config>(fmt::format("{:016X}", title_id), +                                           Config::ConfigType::PerGameConfig); + +    Settings::SetConfiguringGlobal(false); + +    ui->setupUi(this); +    setFocusPolicy(Qt::ClickFocus); +    setWindowTitle(tr("Properties")); + +    ui->addonsTab->SetTitleId(title_id); + +    scene = new QGraphicsScene; +    ui->icon_view->setScene(scene); + +    LoadConfiguration(); +} + +ConfigurePerGame::~ConfigurePerGame() = default; + +void ConfigurePerGame::ApplyConfiguration() { +    ui->addonsTab->ApplyConfiguration(); +    ui->generalTab->ApplyConfiguration(); +    ui->systemTab->ApplyConfiguration(); +    ui->graphicsTab->ApplyConfiguration(); +    ui->graphicsAdvancedTab->ApplyConfiguration(); +    ui->audioTab->ApplyConfiguration(); + +    Settings::Apply(); +    Settings::LogSettings(); + +    game_config->Save(); +} + +void ConfigurePerGame::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QDialog::changeEvent(event); +} + +void ConfigurePerGame::RetranslateUI() { +    ui->retranslateUi(this); +} + +void ConfigurePerGame::LoadFromFile(FileSys::VirtualFile file) { +    this->file = std::move(file); +    LoadConfiguration(); +} + +void ConfigurePerGame::LoadConfiguration() { +    if (file == nullptr) { +        return; +    } + +    ui->addonsTab->LoadFromFile(file); + +    ui->display_title_id->setText( +        QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper()); + +    auto& system = Core::System::GetInstance(); +    const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), +                                   system.GetContentProvider()}; +    const auto control = pm.GetControlMetadata(); +    const auto loader = Loader::GetLoader(system, file); + +    if (control.first != nullptr) { +        ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); +        ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName())); +        ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName())); +    } else { +        std::string title; +        if (loader->ReadTitle(title) == Loader::ResultStatus::Success) +            ui->display_name->setText(QString::fromStdString(title)); + +        FileSys::NACP nacp; +        if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) +            ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName())); + +        ui->display_version->setText(QStringLiteral("1.0.0")); +    } + +    if (control.second != nullptr) { +        scene->clear(); + +        QPixmap map; +        const auto bytes = control.second->ReadAllBytes(); +        map.loadFromData(bytes.data(), static_cast<u32>(bytes.size())); + +        scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), +                                    Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); +    } else { +        std::vector<u8> bytes; +        if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) { +            scene->clear(); + +            QPixmap map; +            map.loadFromData(bytes.data(), static_cast<u32>(bytes.size())); + +            scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), +                                        Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); +        } +    } + +    ui->display_filename->setText(QString::fromStdString(file->GetName())); + +    ui->display_format->setText( +        QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); + +    const auto valueText = ReadableByteSize(file->GetSize()); +    ui->display_size->setText(valueText); +} diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h new file mode 100644 index 000000000..5f9a08cef --- /dev/null +++ b/src/yuzu/configuration/configure_per_game.h @@ -0,0 +1,51 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <vector> + +#include <QDialog> +#include <QList> + +#include "core/file_sys/vfs_types.h" +#include "yuzu/configuration/config.h" + +class QGraphicsScene; +class QStandardItem; +class QStandardItemModel; +class QTreeView; +class QVBoxLayout; + +namespace Ui { +class ConfigurePerGame; +} + +class ConfigurePerGame : public QDialog { +    Q_OBJECT + +public: +    explicit ConfigurePerGame(QWidget* parent, u64 title_id); +    ~ConfigurePerGame() override; + +    /// Save all button configurations to settings file +    void ApplyConfiguration(); + +    void LoadFromFile(FileSys::VirtualFile file); + +private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); + +    void LoadConfiguration(); + +    std::unique_ptr<Ui::ConfigurePerGame> ui; +    FileSys::VirtualFile file; +    u64 title_id; + +    QGraphicsScene* scene; + +    std::unique_ptr<Config> game_config; +}; diff --git a/src/yuzu/configuration/configure_per_game.ui b/src/yuzu/configuration/configure_per_game.ui new file mode 100644 index 000000000..25975b3b9 --- /dev/null +++ b/src/yuzu/configuration/configure_per_game.ui @@ -0,0 +1,330 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigurePerGame</class> + <widget class="QDialog" name="ConfigurePerGame"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>800</width> +    <height>600</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Dialog</string> +  </property> +  <layout class="QVBoxLayout" name="verticalLayout_3"> +   <item> +    <layout class="QHBoxLayout" name="horizontalLayout"> +     <item> +      <widget class="QGroupBox" name="groupBox"> +       <property name="sizePolicy"> +        <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> +         <horstretch>0</horstretch> +         <verstretch>0</verstretch> +        </sizepolicy> +       </property> +       <property name="title"> +        <string>Info</string> +       </property> +       <layout class="QVBoxLayout" name="verticalLayout"> +        <item alignment="Qt::AlignHCenter"> +         <widget class="QGraphicsView" name="icon_view"> +          <property name="sizePolicy"> +           <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> +            <horstretch>0</horstretch> +            <verstretch>0</verstretch> +           </sizepolicy> +          </property> +          <property name="minimumSize"> +           <size> +            <width>256</width> +            <height>256</height> +           </size> +          </property> +          <property name="maximumSize"> +           <size> +            <width>256</width> +            <height>256</height> +           </size> +          </property> +          <property name="verticalScrollBarPolicy"> +           <enum>Qt::ScrollBarAlwaysOff</enum> +          </property> +          <property name="horizontalScrollBarPolicy"> +           <enum>Qt::ScrollBarAlwaysOff</enum> +          </property> +          <property name="interactive"> +           <bool>false</bool> +          </property> +         </widget> +        </item> +        <item> +         <layout class="QGridLayout" name="gridLayout_2"> +          <item row="6" column="1"> +           <widget class="QLineEdit" name="display_size"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="3" column="1"> +           <widget class="QLineEdit" name="display_version"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="1" column="0"> +           <widget class="QLabel" name="label"> +            <property name="text"> +             <string>Name</string> +            </property> +           </widget> +          </item> +          <item row="4" column="0"> +           <widget class="QLabel" name="label_4"> +            <property name="text"> +             <string>Title ID</string> +            </property> +           </widget> +          </item> +          <item row="4" column="1"> +           <widget class="QLineEdit" name="display_title_id"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="7" column="1"> +           <widget class="QLineEdit" name="display_filename"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="5" column="1"> +           <widget class="QLineEdit" name="display_format"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="7" column="0"> +           <widget class="QLabel" name="label_7"> +            <property name="text"> +             <string>Filename</string> +            </property> +           </widget> +          </item> +          <item row="1" column="1"> +           <widget class="QLineEdit" name="display_name"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="2" column="1"> +           <widget class="QLineEdit" name="display_developer"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="5" column="0"> +           <widget class="QLabel" name="label_5"> +            <property name="text"> +             <string>Format</string> +            </property> +           </widget> +          </item> +          <item row="3" column="0"> +           <widget class="QLabel" name="label_3"> +            <property name="text"> +             <string>Version</string> +            </property> +           </widget> +          </item> +          <item row="6" column="0"> +           <widget class="QLabel" name="label_6"> +            <property name="text"> +             <string>Size</string> +            </property> +           </widget> +          </item> +          <item row="2" column="0"> +           <widget class="QLabel" name="label_2"> +            <property name="text"> +             <string>Developer</string> +            </property> +           </widget> +          </item> +         </layout> +        </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> +       </layout> +      </widget> +     </item> +     <item> +      <layout class="QVBoxLayout" name="VerticalLayout"> +       <item> +        <layout class="QVBoxLayout" name="verticalLayout_2"/> +       </item> +       <item> +        <widget class="QTabWidget" name="tabWidget"> +         <property name="enabled"> +          <bool>true</bool> +         </property> +         <property name="currentIndex"> +          <number>0</number> +         </property> +         <property name="usesScrollButtons"> +          <bool>true</bool> +         </property> +         <property name="documentMode"> +          <bool>false</bool> +         </property> +         <property name="tabsClosable"> +          <bool>false</bool> +         </property> +         <widget class="ConfigurePerGameAddons" name="addonsTab"> +          <attribute name="title"> +           <string>Add-Ons</string> +          </attribute> +         </widget> +         <widget class="ConfigureGeneral" name="generalTab"> +          <attribute name="title"> +           <string>General</string> +          </attribute> +         </widget> +         <widget class="ConfigureSystem" name="systemTab"> +          <attribute name="title"> +           <string>System</string> +          </attribute> +         </widget> +         <widget class="ConfigureGraphics" name="graphicsTab"> +          <attribute name="title"> +           <string>Graphics</string> +          </attribute> +         </widget> +         <widget class="ConfigureGraphicsAdvanced" name="graphicsAdvancedTab"> +          <attribute name="title"> +           <string>Adv. Graphics</string> +          </attribute> +         </widget> +         <widget class="ConfigureAudio" name="audioTab"> +          <attribute name="title"> +           <string>Audio</string> +          </attribute> +         </widget> +        </widget> +       </item> +      </layout> +     </item> +    </layout> +   </item> +   <item> +    <widget class="QDialogButtonBox" name="buttonBox"> +     <property name="sizePolicy"> +      <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> +       <horstretch>0</horstretch> +       <verstretch>0</verstretch> +      </sizepolicy> +     </property> +     <property name="orientation"> +      <enum>Qt::Horizontal</enum> +     </property> +     <property name="standardButtons"> +      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +     </property> +    </widget> +   </item> +  </layout> + </widget> + <customwidgets> +  <customwidget> +   <class>ConfigureGeneral</class> +   <extends>QWidget</extends> +   <header>configuration/configure_general.h</header> +   <container>1</container> +  </customwidget> +  <customwidget> +   <class>ConfigureSystem</class> +   <extends>QWidget</extends> +   <header>configuration/configure_system.h</header> +   <container>1</container> +  </customwidget> +  <customwidget> +   <class>ConfigureAudio</class> +   <extends>QWidget</extends> +   <header>configuration/configure_audio.h</header> +   <container>1</container> +  </customwidget> +  <customwidget> +   <class>ConfigureGraphics</class> +   <extends>QWidget</extends> +   <header>configuration/configure_graphics.h</header> +   <container>1</container> +  </customwidget> +  <customwidget> +   <class>ConfigureGraphicsAdvanced</class> +   <extends>QWidget</extends> +   <header>configuration/configure_graphics_advanced.h</header> +   <container>1</container> +  </customwidget> +  <customwidget> +   <class>ConfigurePerGameAddons</class> +   <extends>QWidget</extends> +   <header>configuration/configure_per_game_addons.h</header> +   <container>1</container> +  </customwidget> + </customwidgets> + <resources/> + <connections> +  <connection> +   <sender>buttonBox</sender> +   <signal>accepted()</signal> +   <receiver>ConfigurePerGame</receiver> +   <slot>accept()</slot> +  </connection> +  <connection> +   <sender>buttonBox</sender> +   <signal>rejected()</signal> +   <receiver>ConfigurePerGame</receiver> +   <slot>reject()</slot> +  </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index d7f259f12..cdeeec01c 100644 --- a/src/yuzu/configuration/configure_per_general.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -15,23 +15,20 @@  #include "common/common_paths.h"  #include "common/file_util.h" -#include "core/file_sys/control_metadata.h" +#include "core/core.h"  #include "core/file_sys/patch_manager.h"  #include "core/file_sys/xts_archive.h"  #include "core/loader/loader.h" -#include "ui_configure_per_general.h" +#include "ui_configure_per_game_addons.h"  #include "yuzu/configuration/config.h"  #include "yuzu/configuration/configure_input.h" -#include "yuzu/configuration/configure_per_general.h" +#include "yuzu/configuration/configure_per_game_addons.h"  #include "yuzu/uisettings.h"  #include "yuzu/util/util.h" -ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id) -    : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGameGeneral>()), title_id(title_id) { - +ConfigurePerGameAddons::ConfigurePerGameAddons(QWidget* parent) +    : QWidget(parent), ui(new Ui::ConfigurePerGameAddons) {      ui->setupUi(this); -    setFocusPolicy(Qt::ClickFocus); -    setWindowTitle(tr("Properties"));      layout = new QVBoxLayout;      tree_view = new QTreeView; @@ -52,7 +49,7 @@ ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id)      item_model->setHeaderData(1, Qt::Horizontal, tr("Version"));      // 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 umbrells of custom types. +    // with signals/slots. In this case, QList falls under the umbrella of custom types.      qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");      layout->setContentsMargins(0, 0, 0, 0); @@ -61,18 +58,15 @@ ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id)      ui->scrollArea->setLayout(layout); -    scene = new QGraphicsScene; -    ui->icon_view->setScene(scene); +    ui->scrollArea->setEnabled(!Core::System::GetInstance().IsPoweredOn());      connect(item_model, &QStandardItemModel::itemChanged,              [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); - -    LoadConfiguration();  } -ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default; +ConfigurePerGameAddons::~ConfigurePerGameAddons() = default; -void ConfigurePerGameGeneral::ApplyConfiguration() { +void ConfigurePerGameAddons::ApplyConfiguration() {      std::vector<std::string> disabled_addons;      for (const auto& item : list_items) { @@ -85,78 +79,43 @@ void ConfigurePerGameGeneral::ApplyConfiguration() {      std::sort(disabled_addons.begin(), disabled_addons.end());      std::sort(current.begin(), current.end());      if (disabled_addons != current) { -        FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + -                         "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id)); +        Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + +                           "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id));      }      Settings::values.disabled_addons[title_id] = disabled_addons;  } -void ConfigurePerGameGeneral::changeEvent(QEvent* event) { +void ConfigurePerGameAddons::LoadFromFile(FileSys::VirtualFile file) { +    this->file = std::move(file); +    LoadConfiguration(); +} + +void ConfigurePerGameAddons::SetTitleId(u64 id) { +    this->title_id = id; +} + +void ConfigurePerGameAddons::changeEvent(QEvent* event) {      if (event->type() == QEvent::LanguageChange) {          RetranslateUI();      } -    QDialog::changeEvent(event); +    QWidget::changeEvent(event);  } -void ConfigurePerGameGeneral::RetranslateUI() { +void ConfigurePerGameAddons::RetranslateUI() {      ui->retranslateUi(this);  } -void ConfigurePerGameGeneral::LoadFromFile(FileSys::VirtualFile file) { -    this->file = std::move(file); -    LoadConfiguration(); -} - -void ConfigurePerGameGeneral::LoadConfiguration() { +void ConfigurePerGameAddons::LoadConfiguration() {      if (file == nullptr) {          return;      } -    ui->display_title_id->setText(QString::fromStdString(fmt::format("{:016X}", title_id))); - -    FileSys::PatchManager pm{title_id}; -    const auto control = pm.GetControlMetadata(); -    const auto loader = Loader::GetLoader(file); - -    if (control.first != nullptr) { -        ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); -        ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName())); -        ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName())); -    } else { -        std::string title; -        if (loader->ReadTitle(title) == Loader::ResultStatus::Success) -            ui->display_name->setText(QString::fromStdString(title)); - -        FileSys::NACP nacp; -        if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) -            ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName())); - -        ui->display_version->setText(QStringLiteral("1.0.0")); -    } - -    if (control.second != nullptr) { -        scene->clear(); - -        QPixmap map; -        const auto bytes = control.second->ReadAllBytes(); -        map.loadFromData(bytes.data(), static_cast<u32>(bytes.size())); - -        scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), -                                    Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); -    } else { -        std::vector<u8> bytes; -        if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) { -            scene->clear(); - -            QPixmap map; -            map.loadFromData(bytes.data(), static_cast<u32>(bytes.size())); - -            scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), -                                        Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); -        } -    } +    auto& system = Core::System::GetInstance(); +    const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), +                                   system.GetContentProvider()}; +    const auto loader = Loader::GetLoader(system, file);      FileSys::VirtualFile update_raw;      loader->ReadUpdateRaw(update_raw); @@ -182,12 +141,4 @@ void ConfigurePerGameGeneral::LoadConfiguration() {      }      tree_view->setColumnWidth(0, 5 * tree_view->width() / 16); - -    ui->display_filename->setText(QString::fromStdString(file->GetName())); - -    ui->display_format->setText( -        QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); - -    const auto valueText = ReadableByteSize(file->GetSize()); -    ui->display_size->setText(valueText);  } diff --git a/src/yuzu/configuration/configure_per_general.h b/src/yuzu/configuration/configure_per_game_addons.h index a3b2cdeff..a00ec3539 100644 --- a/src/yuzu/configuration/configure_per_general.h +++ b/src/yuzu/configuration/configure_per_game_addons.h @@ -1,4 +1,4 @@ -// Copyright 2016 Citra Emulator Project +// Copyright 2016 Citra Emulator Project  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. @@ -7,7 +7,6 @@  #include <memory>  #include <vector> -#include <QDialog>  #include <QList>  #include "core/file_sys/vfs_types.h" @@ -19,35 +18,36 @@ class QTreeView;  class QVBoxLayout;  namespace Ui { -class ConfigurePerGameGeneral; +class ConfigurePerGameAddons;  } -class ConfigurePerGameGeneral : public QDialog { +class ConfigurePerGameAddons : public QWidget {      Q_OBJECT  public: -    explicit ConfigurePerGameGeneral(QWidget* parent, u64 title_id); -    ~ConfigurePerGameGeneral() override; +    explicit ConfigurePerGameAddons(QWidget* parent = nullptr); +    ~ConfigurePerGameAddons() override;      /// Save all button configurations to settings file      void ApplyConfiguration();      void LoadFromFile(FileSys::VirtualFile file); +    void SetTitleId(u64 id); +  private:      void changeEvent(QEvent* event) override;      void RetranslateUI();      void LoadConfiguration(); -    std::unique_ptr<Ui::ConfigurePerGameGeneral> ui; +    std::unique_ptr<Ui::ConfigurePerGameAddons> ui;      FileSys::VirtualFile file;      u64 title_id;      QVBoxLayout* layout;      QTreeView* tree_view;      QStandardItemModel* item_model; -    QGraphicsScene* scene;      std::vector<QList<QStandardItem*>> list_items;  }; diff --git a/src/yuzu/configuration/configure_per_game_addons.ui b/src/yuzu/configuration/configure_per_game_addons.ui new file mode 100644 index 000000000..aefdebfcd --- /dev/null +++ b/src/yuzu/configuration/configure_per_game_addons.ui @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigurePerGameAddons</class> + <widget class="QWidget" name="ConfigurePerGameAddons"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>400</width> +    <height>300</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Form</string> +  </property> +  <layout class="QGridLayout" name="gridLayout"> +   <item row="0" column="0"> +    <widget class="QScrollArea" name="scrollArea"> +     <property name="widgetResizable"> +      <bool>true</bool> +     </property> +     <widget class="QWidget" name="scrollAreaWidgetContents"> +      <property name="geometry"> +       <rect> +        <x>0</x> +        <y>0</y> +        <width>380</width> +        <height>280</height> +       </rect> +      </property> +     </widget> +    </widget> +   </item> +  </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_per_general.ui b/src/yuzu/configuration/configure_per_general.ui deleted file mode 100644 index 8fdd96fa4..000000000 --- a/src/yuzu/configuration/configure_per_general.ui +++ /dev/null @@ -1,276 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>ConfigurePerGameGeneral</class> - <widget class="QDialog" name="ConfigurePerGameGeneral"> -  <property name="geometry"> -   <rect> -    <x>0</x> -    <y>0</y> -    <width>400</width> -    <height>520</height> -   </rect> -  </property> -  <property name="windowTitle"> -   <string>ConfigurePerGameGeneral</string> -  </property> -  <layout class="QHBoxLayout" name="HorizontalLayout"> -   <item> -    <layout class="QVBoxLayout" name="VerticalLayout"> -     <item> -      <widget class="QGroupBox" name="GeneralGroupBox"> -       <property name="title"> -        <string>Info</string> -       </property> -       <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> -        <item> -         <layout class="QGridLayout" name="gridLayout_2"> -          <item row="6" column="1" colspan="2"> -           <widget class="QLineEdit" name="display_filename"> -            <property name="enabled"> -             <bool>true</bool> -            </property> -            <property name="readOnly"> -             <bool>true</bool> -            </property> -           </widget> -          </item> -          <item row="0" column="1"> -           <widget class="QLineEdit" name="display_name"> -            <property name="enabled"> -             <bool>true</bool> -            </property> -            <property name="readOnly"> -             <bool>true</bool> -            </property> -           </widget> -          </item> -          <item row="1" column="0"> -           <widget class="QLabel" name="label_2"> -            <property name="text"> -             <string>Developer</string> -            </property> -           </widget> -          </item> -          <item row="5" column="1" colspan="2"> -           <widget class="QLineEdit" name="display_size"> -            <property name="enabled"> -             <bool>true</bool> -            </property> -            <property name="readOnly"> -             <bool>true</bool> -            </property> -           </widget> -          </item> -          <item row="0" column="0"> -           <widget class="QLabel" name="label"> -            <property name="text"> -             <string>Name</string> -            </property> -           </widget> -          </item> -          <item row="6" column="0"> -           <widget class="QLabel" name="label_7"> -            <property name="text"> -             <string>Filename</string> -            </property> -           </widget> -          </item> -          <item row="2" column="0"> -           <widget class="QLabel" name="label_3"> -            <property name="text"> -             <string>Version</string> -            </property> -           </widget> -          </item> -          <item row="4" column="0"> -           <widget class="QLabel" name="label_5"> -            <property name="text"> -             <string>Format</string> -            </property> -           </widget> -          </item> -          <item row="2" column="1"> -           <widget class="QLineEdit" name="display_version"> -            <property name="enabled"> -             <bool>true</bool> -            </property> -            <property name="readOnly"> -             <bool>true</bool> -            </property> -           </widget> -          </item> -          <item row="4" column="1"> -           <widget class="QLineEdit" name="display_format"> -            <property name="enabled"> -             <bool>true</bool> -            </property> -            <property name="readOnly"> -             <bool>true</bool> -            </property> -           </widget> -          </item> -          <item row="5" column="0"> -           <widget class="QLabel" name="label_6"> -            <property name="text"> -             <string>Size</string> -            </property> -           </widget> -          </item> -          <item row="1" column="1"> -           <widget class="QLineEdit" name="display_developer"> -            <property name="enabled"> -             <bool>true</bool> -            </property> -            <property name="readOnly"> -             <bool>true</bool> -            </property> -           </widget> -          </item> -          <item row="3" column="0"> -           <widget class="QLabel" name="label_4"> -            <property name="text"> -             <string>Title ID</string> -            </property> -           </widget> -          </item> -          <item row="3" column="1"> -           <widget class="QLineEdit" name="display_title_id"> -            <property name="enabled"> -             <bool>true</bool> -            </property> -            <property name="readOnly"> -             <bool>true</bool> -            </property> -           </widget> -          </item> -          <item row="0" column="2" rowspan="5"> -           <widget class="QGraphicsView" name="icon_view"> -            <property name="sizePolicy"> -             <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> -              <horstretch>0</horstretch> -              <verstretch>0</verstretch> -             </sizepolicy> -            </property> -            <property name="minimumSize"> -             <size> -              <width>128</width> -              <height>128</height> -             </size> -            </property> -            <property name="maximumSize"> -             <size> -              <width>128</width> -              <height>128</height> -             </size> -            </property> -            <property name="verticalScrollBarPolicy"> -             <enum>Qt::ScrollBarAlwaysOff</enum> -            </property> -            <property name="horizontalScrollBarPolicy"> -             <enum>Qt::ScrollBarAlwaysOff</enum> -            </property> -            <property name="sizeAdjustPolicy"> -             <enum>QAbstractScrollArea::AdjustToContents</enum> -            </property> -            <property name="interactive"> -             <bool>false</bool> -            </property> -           </widget> -          </item> -         </layout> -        </item> -       </layout> -      </widget> -     </item> -     <item> -      <widget class="QGroupBox" name="PerformanceGroupBox"> -       <property name="title"> -        <string>Add-Ons</string> -       </property> -       <layout class="QHBoxLayout" name="PerformanceHorizontalLayout"> -        <item> -         <widget class="QScrollArea" name="scrollArea"> -          <property name="widgetResizable"> -           <bool>true</bool> -          </property> -          <widget class="QWidget" name="scrollAreaWidgetContents"> -           <property name="geometry"> -            <rect> -             <x>0</x> -             <y>0</y> -             <width>350</width> -             <height>169</height> -            </rect> -           </property> -          </widget> -         </widget> -        </item> -        <item> -         <layout class="QVBoxLayout" name="PerformanceVerticalLayout"/> -        </item> -       </layout> -      </widget> -     </item> -     <item> -      <spacer name="verticalSpacer"> -       <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>40</height> -        </size> -       </property> -      </spacer> -     </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>ConfigurePerGameGeneral</receiver> -   <slot>accept()</slot> -   <hints> -    <hint type="sourcelabel"> -     <x>269</x> -     <y>567</y> -    </hint> -    <hint type="destinationlabel"> -     <x>269</x> -     <y>294</y> -    </hint> -   </hints> -  </connection> -  <connection> -   <sender>buttonBox</sender> -   <signal>rejected()</signal> -   <receiver>ConfigurePerGameGeneral</receiver> -   <slot>reject()</slot> -   <hints> -    <hint type="sourcelabel"> -     <x>269</x> -     <y>567</y> -    </hint> -    <hint type="destinationlabel"> -     <x>269</x> -     <y>294</y> -    </hint> -   </hints> -  </connection> - </connections> -</ui> diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp index f53423440..6334c4c50 100644 --- a/src/yuzu/configuration/configure_profile_manager.cpp +++ b/src/yuzu/configuration/configure_profile_manager.cpp @@ -34,7 +34,7 @@ constexpr std::array<u8, 107> backup_jpeg{  };  QString GetImagePath(Common::UUID uuid) { -    const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + +    const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +                        "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";      return QString::fromStdString(path);  } @@ -282,7 +282,7 @@ void ConfigureProfileManager::SetUserImage() {      }      const auto raw_path = QString::fromStdString( -        FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010"); +        Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + "/system/save/8000000000000010");      const QFileInfo raw_info{raw_path};      if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) {          QMessageBox::warning(this, tr("Error deleting file"), diff --git a/src/yuzu/configuration/configure_service.cpp b/src/yuzu/configuration/configure_service.cpp index 06566e981..0de7a4f0b 100644 --- a/src/yuzu/configuration/configure_service.cpp +++ b/src/yuzu/configuration/configure_service.cpp @@ -68,6 +68,7 @@ void ConfigureService::SetConfiguration() {  }  std::pair<QString, QString> ConfigureService::BCATDownloadEvents() { +#ifdef YUZU_ENABLE_BOXCAT      std::optional<std::string> global;      std::map<std::string, Service::BCAT::EventStatus> map;      const auto res = Service::BCAT::Boxcat::GetStatus(global, map); @@ -105,7 +106,10 @@ std::pair<QString, QString> ConfigureService::BCATDownloadEvents() {                     .arg(QString::fromStdString(key))                     .arg(FormatEventStatusString(value));      } -    return {QStringLiteral("Current Boxcat Events"), std::move(out)}; +    return {tr("Current Boxcat Events"), std::move(out)}; +#else +    return {tr("Current Boxcat Events"), tr("There are currently no events on boxcat.")}; +#endif  }  void ConfigureService::OnBCATImplChanged() { diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index f49cd4c8f..59a58d92c 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -12,8 +12,10 @@  #include "common/assert.h"  #include "common/file_util.h"  #include "core/core.h" +#include "core/hle/service/time/time.h"  #include "core/settings.h"  #include "ui_configure_system.h" +#include "yuzu/configuration/configuration_shared.h"  #include "yuzu/configuration/configure_system.h"  ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) { @@ -21,20 +23,25 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::      connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,              &ConfigureSystem::RefreshConsoleID); -    connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) { -        ui->rng_seed_edit->setEnabled(checked); -        if (!checked) { +    connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](int state) { +        ui->rng_seed_edit->setEnabled(state == Qt::Checked); +        if (state != Qt::Checked) {              ui->rng_seed_edit->setText(QStringLiteral("00000000"));          }      }); -    connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) { -        ui->custom_rtc_edit->setEnabled(checked); -        if (!checked) { +    connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](int state) { +        ui->custom_rtc_edit->setEnabled(state == Qt::Checked); +        if (state != Qt::Checked) {              ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime());          }      }); +    ui->label_console_id->setVisible(Settings::IsConfiguringGlobal()); +    ui->button_regenerate_console_id->setVisible(Settings::IsConfiguringGlobal()); + +    SetupPerGameUI(); +      SetConfiguration();  } @@ -54,49 +61,140 @@ void ConfigureSystem::RetranslateUI() {  void ConfigureSystem::SetConfiguration() {      enabled = !Core::System::GetInstance().IsPoweredOn(); +    const auto rng_seed = +        QStringLiteral("%1") +            .arg(Settings::values.rng_seed.GetValue().value_or(0), 8, 16, QLatin1Char{'0'}) +            .toUpper(); +    const auto rtc_time = Settings::values.custom_rtc.GetValue().value_or( +        std::chrono::seconds(QDateTime::currentSecsSinceEpoch())); -    ui->combo_language->setCurrentIndex(Settings::values.language_index); -    ui->combo_region->setCurrentIndex(Settings::values.region_index); -    ui->combo_sound->setCurrentIndex(Settings::values.sound_index); - -    ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value()); -    ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.has_value()); - -    const auto rng_seed = QStringLiteral("%1") -                              .arg(Settings::values.rng_seed.value_or(0), 8, 16, QLatin1Char{'0'}) -                              .toUpper(); +    ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.GetValue().has_value()); +    ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.GetValue().has_value() && +                                  Settings::values.rng_seed.UsingGlobal());      ui->rng_seed_edit->setText(rng_seed); -    ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value()); -    ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value()); - -    const auto rtc_time = Settings::values.custom_rtc.value_or( -        std::chrono::seconds(QDateTime::currentSecsSinceEpoch())); +    ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.GetValue().has_value()); +    ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.GetValue().has_value() && +                                    Settings::values.rng_seed.UsingGlobal());      ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); + +    if (Settings::IsConfiguringGlobal()) { +        ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue()); +        ui->combo_region->setCurrentIndex(Settings::values.region_index.GetValue()); +        ui->combo_time_zone->setCurrentIndex(Settings::values.time_zone_index.GetValue()); +        ui->combo_sound->setCurrentIndex(Settings::values.sound_index.GetValue()); +    } else { +        ConfigurationShared::SetPerGameSetting(ui->combo_language, +                                               &Settings::values.language_index); +        ConfigurationShared::SetPerGameSetting(ui->combo_region, &Settings::values.region_index); +        ConfigurationShared::SetPerGameSetting(ui->combo_time_zone, +                                               &Settings::values.time_zone_index); +        ConfigurationShared::SetPerGameSetting(ui->combo_sound, &Settings::values.sound_index); + +        ConfigurationShared::SetHighlight(ui->label_language, +                                          !Settings::values.language_index.UsingGlobal()); +        ConfigurationShared::SetHighlight(ui->label_region, +                                          !Settings::values.region_index.UsingGlobal()); +        ConfigurationShared::SetHighlight(ui->label_timezone, +                                          !Settings::values.time_zone_index.UsingGlobal()); +        ConfigurationShared::SetHighlight(ui->label_sound, +                                          !Settings::values.sound_index.UsingGlobal()); +    }  }  void ConfigureSystem::ReadSystemSettings() {}  void ConfigureSystem::ApplyConfiguration() { +    // Allow setting custom RTC even if system is powered on, to allow in-game time to be fast +    // forwared +    if (Settings::values.custom_rtc.UsingGlobal()) { +        if (ui->custom_rtc_checkbox->isChecked()) { +            Settings::values.custom_rtc.SetValue( +                std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch())); +            if (Core::System::GetInstance().IsPoweredOn()) { +                const s64 posix_time{Settings::values.custom_rtc.GetValue()->count() + +                                     Service::Time::TimeManager::GetExternalTimeZoneOffset()}; +                Core::System::GetInstance().GetTimeManager().UpdateLocalSystemClockTime(posix_time); +            } +        } else { +            Settings::values.custom_rtc.SetValue(std::nullopt); +        } +    } +      if (!enabled) {          return;      } -    Settings::values.language_index = ui->combo_language->currentIndex(); -    Settings::values.region_index = ui->combo_region->currentIndex(); -    Settings::values.sound_index = ui->combo_sound->currentIndex(); +    if (Settings::IsConfiguringGlobal()) { +        // Guard if during game and set to game-specific value +        if (Settings::values.language_index.UsingGlobal()) { +            Settings::values.language_index.SetValue(ui->combo_language->currentIndex()); +        } +        if (Settings::values.region_index.UsingGlobal()) { +            Settings::values.region_index.SetValue(ui->combo_region->currentIndex()); +        } +        if (Settings::values.time_zone_index.UsingGlobal()) { +            Settings::values.time_zone_index.SetValue(ui->combo_time_zone->currentIndex()); +        } +        if (Settings::values.sound_index.UsingGlobal()) { +            Settings::values.sound_index.SetValue(ui->combo_sound->currentIndex()); +        } -    if (ui->rng_seed_checkbox->isChecked()) { -        Settings::values.rng_seed = ui->rng_seed_edit->text().toULongLong(nullptr, 16); +        if (Settings::values.rng_seed.UsingGlobal()) { +            if (ui->rng_seed_checkbox->isChecked()) { +                Settings::values.rng_seed.SetValue( +                    ui->rng_seed_edit->text().toULongLong(nullptr, 16)); +            } else { +                Settings::values.rng_seed.SetValue(std::nullopt); +            } +        }      } else { -        Settings::values.rng_seed = std::nullopt; -    } +        ConfigurationShared::ApplyPerGameSetting(&Settings::values.language_index, +                                                 ui->combo_language); +        ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_index, ui->combo_region); +        ConfigurationShared::ApplyPerGameSetting(&Settings::values.time_zone_index, +                                                 ui->combo_time_zone); +        ConfigurationShared::ApplyPerGameSetting(&Settings::values.sound_index, ui->combo_sound); + +        switch (use_rng_seed) { +        case ConfigurationShared::CheckState::On: +        case ConfigurationShared::CheckState::Off: +            Settings::values.rng_seed.SetGlobal(false); +            if (ui->rng_seed_checkbox->isChecked()) { +                Settings::values.rng_seed.SetValue( +                    ui->rng_seed_edit->text().toULongLong(nullptr, 16)); +            } else { +                Settings::values.rng_seed.SetValue(std::nullopt); +            } +            break; +        case ConfigurationShared::CheckState::Global: +            Settings::values.rng_seed.SetGlobal(false); +            Settings::values.rng_seed.SetValue(std::nullopt); +            Settings::values.rng_seed.SetGlobal(true); +            break; +        case ConfigurationShared::CheckState::Count: +            break; +        } -    if (ui->custom_rtc_checkbox->isChecked()) { -        Settings::values.custom_rtc = -            std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()); -    } else { -        Settings::values.custom_rtc = std::nullopt; +        switch (use_custom_rtc) { +        case ConfigurationShared::CheckState::On: +        case ConfigurationShared::CheckState::Off: +            Settings::values.custom_rtc.SetGlobal(false); +            if (ui->custom_rtc_checkbox->isChecked()) { +                Settings::values.custom_rtc.SetValue( +                    std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch())); +            } else { +                Settings::values.custom_rtc.SetValue(std::nullopt); +            } +            break; +        case ConfigurationShared::CheckState::Global: +            Settings::values.custom_rtc.SetGlobal(false); +            Settings::values.custom_rtc.SetValue(std::nullopt); +            Settings::values.custom_rtc.SetGlobal(true); +            break; +        case ConfigurationShared::CheckState::Count: +            break; +        }      }      Settings::Apply(); @@ -118,3 +216,36 @@ void ConfigureSystem::RefreshConsoleID() {      ui->label_console_id->setText(          tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));  } + +void ConfigureSystem::SetupPerGameUI() { +    if (Settings::IsConfiguringGlobal()) { +        ui->combo_language->setEnabled(Settings::values.language_index.UsingGlobal()); +        ui->combo_region->setEnabled(Settings::values.region_index.UsingGlobal()); +        ui->combo_time_zone->setEnabled(Settings::values.time_zone_index.UsingGlobal()); +        ui->combo_sound->setEnabled(Settings::values.sound_index.UsingGlobal()); +        ui->rng_seed_checkbox->setEnabled(Settings::values.rng_seed.UsingGlobal()); +        ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.UsingGlobal()); +        ui->custom_rtc_checkbox->setEnabled(Settings::values.custom_rtc.UsingGlobal()); +        ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.UsingGlobal()); + +        return; +    } + +    ConfigurationShared::SetColoredComboBox(ui->combo_language, ui->label_language, +                                            Settings::values.language_index.GetValue(true)); +    ConfigurationShared::SetColoredComboBox(ui->combo_region, ui->label_region, +                                            Settings::values.region_index.GetValue(true)); +    ConfigurationShared::SetColoredComboBox(ui->combo_time_zone, ui->label_timezone, +                                            Settings::values.time_zone_index.GetValue(true)); +    ConfigurationShared::SetColoredComboBox(ui->combo_sound, ui->label_sound, +                                            Settings::values.sound_index.GetValue(true)); + +    ConfigurationShared::SetColoredTristate( +        ui->rng_seed_checkbox, Settings::values.rng_seed.UsingGlobal(), +        Settings::values.rng_seed.GetValue().has_value(), +        Settings::values.rng_seed.GetValue(true).has_value(), use_rng_seed); +    ConfigurationShared::SetColoredTristate( +        ui->custom_rtc_checkbox, Settings::values.custom_rtc.UsingGlobal(), +        Settings::values.custom_rtc.GetValue().has_value(), +        Settings::values.custom_rtc.GetValue(true).has_value(), use_custom_rtc); +} diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h index d8fa2d2cc..fc5cd2945 100644 --- a/src/yuzu/configuration/configure_system.h +++ b/src/yuzu/configuration/configure_system.h @@ -9,6 +9,10 @@  #include <QList>  #include <QWidget> +namespace ConfigurationShared { +enum class CheckState; +} +  namespace Ui {  class ConfigureSystem;  } @@ -32,10 +36,16 @@ private:      void RefreshConsoleID(); +    void SetupPerGameUI(); +      std::unique_ptr<Ui::ConfigureSystem> ui;      bool enabled = false;      int language_index = 0;      int region_index = 0; +    int time_zone_index = 0;      int sound_index = 0; + +    ConfigurationShared::CheckState use_rng_seed; +    ConfigurationShared::CheckState use_custom_rtc;  }; diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui index 4e2c7e76e..53b95658b 100644 --- a/src/yuzu/configuration/configure_system.ui +++ b/src/yuzu/configuration/configure_system.ui @@ -21,249 +21,494 @@         <property name="title">          <string>System Settings</string>         </property> -       <layout class="QGridLayout" name="gridLayout"> -        <item row="2" column="0"> -         <widget class="QLabel" name="label_sound"> -          <property name="text"> -           <string>Sound output mode</string> -          </property> -         </widget> -        </item> -        <item row="3" column="0"> -         <widget class="QLabel" name="label_console_id"> -          <property name="text"> -           <string>Console ID:</string> -          </property> -         </widget> -        </item> -        <item row="0" column="1"> -         <widget class="QComboBox" name="combo_language"> -          <property name="toolTip"> -           <string>Note: this can be overridden when region setting is auto-select</string> -          </property> -          <item> -           <property name="text"> -            <string>Japanese (日本語)</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>English</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>French (français)</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>German (Deutsch)</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>Italian (italiano)</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>Spanish (español)</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>Chinese</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>Korean (한국어)</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>Dutch (Nederlands)</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>Portuguese (português)</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>Russian (Русский)</string> -           </property> +       <layout class="QVBoxLayout" name="verticalLayout_2"> +        <item> +         <layout class="QGridLayout" name="gridLayout_2"> +          <item row="1" column="0"> +           <widget class="QLabel" name="label_region"> +            <property name="text"> +             <string>Region:</string> +            </property> +           </widget>            </item> -          <item> -           <property name="text"> -            <string>Taiwanese</string> -           </property> +          <item row="2" column="1"> +           <widget class="QComboBox" name="combo_time_zone"> +            <item> +             <property name="text"> +              <string>Auto</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Default</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>CET</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>CST6CDT</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Cuba</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>EET</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Egypt</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Eire</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>EST</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>EST5EDT</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>GB</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>GB-Eire</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>GMT</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>GMT+0</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>GMT-0</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>GMT0</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Greenwich</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Hongkong</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>HST</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Iceland</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Iran</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Israel</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Jamaica</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Japan</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Kwajalein</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Libya</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>MET</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>MST</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>MST7MDT</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Navajo</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>NZ</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>NZ-CHAT</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Poland</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Portugal</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>PRC</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>PST8PDT</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>ROC</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>ROK</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Singapore</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Turkey</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>UCT</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Universal</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>UTC</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>W-SU</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>WET</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Zulu</string> +             </property> +            </item> +           </widget>            </item> -          <item> -           <property name="text"> -            <string>British English</string> -           </property> +          <item row="1" column="1"> +           <widget class="QComboBox" name="combo_region"> +            <item> +             <property name="text"> +              <string>Japan</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>USA</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Europe</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Australia</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>China</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Korea</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Taiwan</string> +             </property> +            </item> +           </widget>            </item> -          <item> -           <property name="text"> -            <string>Canadian French</string> -           </property> +          <item row="2" column="0"> +           <widget class="QLabel" name="label_timezone"> +            <property name="text"> +             <string>Time Zone:</string> +            </property> +           </widget>            </item> -          <item> -           <property name="text"> -            <string>Latin American Spanish</string> -           </property> +          <item row="0" column="1"> +           <widget class="QComboBox" name="combo_language"> +            <property name="toolTip"> +             <string>Note: this can be overridden when region setting is auto-select</string> +            </property> +            <item> +             <property name="text"> +              <string>Japanese (日本語)</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>English</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>French (français)</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>German (Deutsch)</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Italian (italiano)</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Spanish (español)</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Chinese</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Korean (한국어)</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Dutch (Nederlands)</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Portuguese (português)</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Russian (Русский)</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Taiwanese</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>British English</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Canadian French</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Latin American Spanish</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Simplified Chinese</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Traditional Chinese (正體中文)</string> +             </property> +            </item> +           </widget>            </item> -          <item> -           <property name="text"> -            <string>Simplified Chinese</string> -           </property> +          <item row="5" column="0"> +           <widget class="QCheckBox" name="custom_rtc_checkbox"> +            <property name="text"> +             <string>Custom RTC</string> +            </property> +           </widget>            </item> -          <item> -           <property name="text"> -            <string>Traditional Chinese (正體中文)</string> -           </property> +          <item row="0" column="0"> +           <widget class="QLabel" name="label_language"> +            <property name="text"> +             <string>Language</string> +            </property> +           </widget>            </item> -         </widget> -        </item> -        <item row="1" column="0"> -         <widget class="QLabel" name="label_region"> -          <property name="text"> -           <string>Region:</string> -          </property> -         </widget> -        </item> -        <item row="1" column="1"> -         <widget class="QComboBox" name="combo_region"> -          <item> -           <property name="text"> -            <string>Japan</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>USA</string> -           </property> +          <item row="6" column="0"> +           <widget class="QCheckBox" name="rng_seed_checkbox"> +            <property name="text"> +             <string>RNG Seed</string> +            </property> +           </widget>            </item> -          <item> -           <property name="text"> -            <string>Europe</string> -           </property> +          <item row="3" column="1"> +           <widget class="QComboBox" name="combo_sound"> +            <item> +             <property name="text"> +              <string>Mono</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Stereo</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>Surround</string> +             </property> +            </item> +           </widget>            </item> -          <item> -           <property name="text"> -            <string>Australia</string> -           </property> +          <item row="4" column="0"> +           <widget class="QLabel" name="label_console_id"> +            <property name="text"> +             <string>Console ID:</string> +            </property> +           </widget>            </item> -          <item> -           <property name="text"> -            <string>China</string> -           </property> +          <item row="3" column="0"> +           <widget class="QLabel" name="label_sound"> +            <property name="text"> +             <string>Sound output mode</string> +            </property> +           </widget>            </item> -          <item> -           <property name="text"> -            <string>Korea</string> -           </property> -          </item> -          <item> -           <property name="text"> -            <string>Taiwan</string> -           </property> -          </item> -         </widget> -        </item> -        <item row="5" column="0"> -         <widget class="QCheckBox" name="rng_seed_checkbox"> -          <property name="text"> -           <string>RNG Seed</string> -          </property> -         </widget> -        </item> -        <item row="2" column="1"> -         <widget class="QComboBox" name="combo_sound"> -          <item> -           <property name="text"> -            <string>Mono</string> -           </property> +          <item row="5" column="1"> +           <widget class="QDateTimeEdit" name="custom_rtc_edit"> +            <property name="minimumDate"> +             <date> +              <year>1970</year> +              <month>1</month> +              <day>1</day> +             </date> +            </property> +            <property name="displayFormat"> +             <string>d MMM yyyy h:mm:ss AP</string> +            </property> +           </widget>            </item> -          <item> -           <property name="text"> -            <string>Stereo</string> -           </property> +          <item row="6" column="1"> +           <widget class="QLineEdit" name="rng_seed_edit"> +            <property name="sizePolicy"> +             <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> +              <horstretch>0</horstretch> +              <verstretch>0</verstretch> +             </sizepolicy> +            </property> +            <property name="font"> +             <font> +              <family>Lucida Console</family> +             </font> +            </property> +            <property name="inputMask"> +             <string notr="true">HHHHHHHH</string> +            </property> +            <property name="maxLength"> +             <number>8</number> +            </property> +           </widget>            </item> -          <item> -           <property name="text"> -            <string>Surround</string> -           </property> +          <item row="4" column="1"> +           <widget class="QPushButton" name="button_regenerate_console_id"> +            <property name="sizePolicy"> +             <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +              <horstretch>0</horstretch> +              <verstretch>0</verstretch> +             </sizepolicy> +            </property> +            <property name="layoutDirection"> +             <enum>Qt::RightToLeft</enum> +            </property> +            <property name="text"> +             <string>Regenerate</string> +            </property> +           </widget>            </item> -         </widget> -        </item> -        <item row="0" column="0"> -         <widget class="QLabel" name="label_language"> -          <property name="text"> -           <string>Language</string> -          </property> -         </widget> -        </item> -        <item row="3" column="1"> -         <widget class="QPushButton" name="button_regenerate_console_id"> -          <property name="sizePolicy"> -           <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> -            <horstretch>0</horstretch> -            <verstretch>0</verstretch> -           </sizepolicy> -          </property> -          <property name="layoutDirection"> -           <enum>Qt::RightToLeft</enum> -          </property> -          <property name="text"> -           <string>Regenerate</string> -          </property> -         </widget> -        </item> -        <item row="4" column="0"> -         <widget class="QCheckBox" name="custom_rtc_checkbox"> -          <property name="text"> -           <string>Custom RTC</string> -          </property> -         </widget> -        </item> -        <item row="4" column="1"> -         <widget class="QDateTimeEdit" name="custom_rtc_edit"> -          <property name="minimumDate"> -           <date> -            <year>1970</year> -            <month>1</month> -            <day>1</day> -           </date> -          </property> -          <property name="displayFormat"> -           <string>d MMM yyyy h:mm:ss AP</string> -          </property> -         </widget> -        </item> -        <item row="5" column="1"> -         <widget class="QLineEdit" name="rng_seed_edit"> -          <property name="sizePolicy"> -           <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> -            <horstretch>0</horstretch> -            <verstretch>0</verstretch> -           </sizepolicy> -          </property> -          <property name="font"> -           <font> -            <family>Lucida Console</family> -           </font> -          </property> -          <property name="inputMask"> -           <string notr="true">HHHHHHHH</string> -          </property> -          <property name="maxLength"> -           <number>8</number> -          </property> -         </widget> +         </layout>          </item>         </layout>        </widget> diff --git a/src/yuzu/configuration/configure_touch_from_button.cpp b/src/yuzu/configuration/configure_touch_from_button.cpp new file mode 100644 index 000000000..15557e4b8 --- /dev/null +++ b/src/yuzu/configuration/configure_touch_from_button.cpp @@ -0,0 +1,623 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QInputDialog> +#include <QKeyEvent> +#include <QMessageBox> +#include <QMouseEvent> +#include <QResizeEvent> +#include <QStandardItemModel> +#include <QTimer> +#include "common/param_package.h" +#include "core/frontend/framebuffer_layout.h" +#include "core/settings.h" +#include "input_common/main.h" +#include "ui_configure_touch_from_button.h" +#include "yuzu/configuration/configure_touch_from_button.h" +#include "yuzu/configuration/configure_touch_widget.h" + +static 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 QString{}; +    default: +        return QKeySequence(key_code).toString(); +    } +} + +static QString ButtonToText(const Common::ParamPackage& param) { +    if (!param.Has("engine")) { +        return QObject::tr("[not set]"); +    } + +    if (param.Get("engine", "") == "keyboard") { +        return GetKeyName(param.Get("code", 0)); +    } + +    if (param.Get("engine", "") == "sdl") { +        if (param.Has("hat")) { +            const QString hat_str = QString::fromStdString(param.Get("hat", "")); +            const QString direction_str = QString::fromStdString(param.Get("direction", "")); + +            return QObject::tr("Hat %1 %2").arg(hat_str, direction_str); +        } + +        if (param.Has("axis")) { +            const QString axis_str = QString::fromStdString(param.Get("axis", "")); +            const QString direction_str = QString::fromStdString(param.Get("direction", "")); + +            return QObject::tr("Axis %1%2").arg(axis_str, direction_str); +        } + +        if (param.Has("button")) { +            const QString button_str = QString::fromStdString(param.Get("button", "")); + +            return QObject::tr("Button %1").arg(button_str); +        } + +        return {}; +    } + +    return QObject::tr("[unknown]"); +} + +ConfigureTouchFromButton::ConfigureTouchFromButton( +    QWidget* parent, const std::vector<Settings::TouchFromButtonMap>& touch_maps, +    InputCommon::InputSubsystem* input_subsystem_, const int default_index) +    : QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchFromButton>()), +      touch_maps(touch_maps), input_subsystem{input_subsystem_}, selected_index(default_index), +      timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) { +    ui->setupUi(this); +    binding_list_model = new QStandardItemModel(0, 3, this); +    binding_list_model->setHorizontalHeaderLabels( +        {tr("Button"), tr("X", "X axis"), tr("Y", "Y axis")}); +    ui->binding_list->setModel(binding_list_model); +    ui->bottom_screen->SetCoordLabel(ui->coord_label); + +    SetConfiguration(); +    UpdateUiDisplay(); +    ConnectEvents(); +} + +ConfigureTouchFromButton::~ConfigureTouchFromButton() = default; + +void ConfigureTouchFromButton::showEvent(QShowEvent* ev) { +    QWidget::showEvent(ev); + +    // width values are not valid in the constructor +    const int w = +        ui->binding_list->viewport()->contentsRect().width() / binding_list_model->columnCount(); +    if (w <= 0) { +        return; +    } +    ui->binding_list->setColumnWidth(0, w); +    ui->binding_list->setColumnWidth(1, w); +    ui->binding_list->setColumnWidth(2, w); +} + +void ConfigureTouchFromButton::SetConfiguration() { +    for (const auto& touch_map : touch_maps) { +        ui->mapping->addItem(QString::fromStdString(touch_map.name)); +    } + +    ui->mapping->setCurrentIndex(selected_index); +} + +void ConfigureTouchFromButton::UpdateUiDisplay() { +    ui->button_delete->setEnabled(touch_maps.size() > 1); +    ui->button_delete_bind->setEnabled(false); + +    binding_list_model->removeRows(0, binding_list_model->rowCount()); + +    for (const auto& button_str : touch_maps[selected_index].buttons) { +        Common::ParamPackage package{button_str}; +        QStandardItem* button = new QStandardItem(ButtonToText(package)); +        button->setData(QString::fromStdString(button_str)); +        button->setEditable(false); +        QStandardItem* xcoord = new QStandardItem(QString::number(package.Get("x", 0))); +        QStandardItem* ycoord = new QStandardItem(QString::number(package.Get("y", 0))); +        binding_list_model->appendRow({button, xcoord, ycoord}); + +        const int dot = ui->bottom_screen->AddDot(package.Get("x", 0), package.Get("y", 0)); +        button->setData(dot, DataRoleDot); +    } +} + +void ConfigureTouchFromButton::ConnectEvents() { +    connect(ui->mapping, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) { +        SaveCurrentMapping(); +        selected_index = index; +        UpdateUiDisplay(); +    }); +    connect(ui->button_new, &QPushButton::clicked, this, &ConfigureTouchFromButton::NewMapping); +    connect(ui->button_delete, &QPushButton::clicked, this, +            &ConfigureTouchFromButton::DeleteMapping); +    connect(ui->button_rename, &QPushButton::clicked, this, +            &ConfigureTouchFromButton::RenameMapping); +    connect(ui->button_delete_bind, &QPushButton::clicked, this, +            &ConfigureTouchFromButton::DeleteBinding); +    connect(ui->binding_list, &QTreeView::doubleClicked, this, +            &ConfigureTouchFromButton::EditBinding); +    connect(ui->binding_list->selectionModel(), &QItemSelectionModel::selectionChanged, this, +            &ConfigureTouchFromButton::OnBindingSelection); +    connect(binding_list_model, &QStandardItemModel::itemChanged, this, +            &ConfigureTouchFromButton::OnBindingChanged); +    connect(ui->binding_list->model(), &QStandardItemModel::rowsAboutToBeRemoved, this, +            &ConfigureTouchFromButton::OnBindingDeleted); +    connect(ui->bottom_screen, &TouchScreenPreview::DotAdded, this, +            &ConfigureTouchFromButton::NewBinding); +    connect(ui->bottom_screen, &TouchScreenPreview::DotSelected, this, +            &ConfigureTouchFromButton::SetActiveBinding); +    connect(ui->bottom_screen, &TouchScreenPreview::DotMoved, this, +            &ConfigureTouchFromButton::SetCoordinates); +    connect(ui->buttonBox, &QDialogButtonBox::accepted, this, +            &ConfigureTouchFromButton::ApplyConfiguration); + +    connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); }); + +    connect(poll_timer.get(), &QTimer::timeout, [this]() { +        Common::ParamPackage params; +        for (auto& poller : device_pollers) { +            params = poller->GetNextInput(); +            if (params.Has("engine")) { +                SetPollingResult(params, false); +                return; +            } +        } +    }); +} + +void ConfigureTouchFromButton::SaveCurrentMapping() { +    auto& map = touch_maps[selected_index]; +    map.buttons.clear(); +    for (int i = 0, rc = binding_list_model->rowCount(); i < rc; ++i) { +        const auto bind_str = binding_list_model->index(i, 0) +                                  .data(Qt::ItemDataRole::UserRole + 1) +                                  .toString() +                                  .toStdString(); +        if (bind_str.empty()) { +            continue; +        } +        Common::ParamPackage params{bind_str}; +        if (!params.Has("engine")) { +            continue; +        } +        params.Set("x", binding_list_model->index(i, 1).data().toInt()); +        params.Set("y", binding_list_model->index(i, 2).data().toInt()); +        map.buttons.emplace_back(params.Serialize()); +    } +} + +void ConfigureTouchFromButton::NewMapping() { +    const QString name = +        QInputDialog::getText(this, tr("New Profile"), tr("Enter the name for the new profile.")); +    if (name.isEmpty()) { +        return; +    } +    touch_maps.emplace_back(Settings::TouchFromButtonMap{name.toStdString(), {}}); +    ui->mapping->addItem(name); +    ui->mapping->setCurrentIndex(ui->mapping->count() - 1); +} + +void ConfigureTouchFromButton::DeleteMapping() { +    const auto answer = QMessageBox::question( +        this, tr("Delete Profile"), tr("Delete profile %1?").arg(ui->mapping->currentText())); +    if (answer != QMessageBox::Yes) { +        return; +    } +    const bool blocked = ui->mapping->blockSignals(true); +    ui->mapping->removeItem(selected_index); +    ui->mapping->blockSignals(blocked); +    touch_maps.erase(touch_maps.begin() + selected_index); +    selected_index = ui->mapping->currentIndex(); +    UpdateUiDisplay(); +} + +void ConfigureTouchFromButton::RenameMapping() { +    const QString new_name = QInputDialog::getText(this, tr("Rename Profile"), tr("New name:")); +    if (new_name.isEmpty()) { +        return; +    } +    ui->mapping->setItemText(selected_index, new_name); +    touch_maps[selected_index].name = new_name.toStdString(); +} + +void ConfigureTouchFromButton::GetButtonInput(const int row_index, const bool is_new) { +    binding_list_model->item(row_index, 0)->setText(tr("[press key]")); + +    input_setter = [this, row_index, is_new](const Common::ParamPackage& params, +                                             const bool cancel) { +        auto* cell = binding_list_model->item(row_index, 0); +        if (cancel) { +            if (is_new) { +                binding_list_model->removeRow(row_index); +            } else { +                cell->setText( +                    ButtonToText(Common::ParamPackage{cell->data().toString().toStdString()})); +            } +        } else { +            cell->setText(ButtonToText(params)); +            cell->setData(QString::fromStdString(params.Serialize())); +        } +    }; + +    device_pollers = input_subsystem->GetPollers(InputCommon::Polling::DeviceType::Button); + +    for (auto& poller : device_pollers) { +        poller->Start(); +    } + +    grabKeyboard(); +    grabMouse(); +    qApp->setOverrideCursor(QCursor(Qt::CursorShape::ArrowCursor)); +    timeout_timer->start(5000); // Cancel after 5 seconds +    poll_timer->start(200);     // Check for new inputs every 200ms +} + +void ConfigureTouchFromButton::NewBinding(const QPoint& pos) { +    auto* button = new QStandardItem(); +    button->setEditable(false); +    auto* x_coord = new QStandardItem(QString::number(pos.x())); +    auto* y_coord = new QStandardItem(QString::number(pos.y())); + +    const int dot_id = ui->bottom_screen->AddDot(pos.x(), pos.y()); +    button->setData(dot_id, DataRoleDot); + +    binding_list_model->appendRow({button, x_coord, y_coord}); +    ui->binding_list->setFocus(); +    ui->binding_list->setCurrentIndex(button->index()); + +    GetButtonInput(binding_list_model->rowCount() - 1, true); +} + +void ConfigureTouchFromButton::EditBinding(const QModelIndex& qi) { +    if (qi.row() >= 0 && qi.column() == 0) { +        GetButtonInput(qi.row(), false); +    } +} + +void ConfigureTouchFromButton::DeleteBinding() { +    const int row_index = ui->binding_list->currentIndex().row(); +    if (row_index < 0) { +        return; +    } +    ui->bottom_screen->RemoveDot(binding_list_model->index(row_index, 0).data(DataRoleDot).toInt()); +    binding_list_model->removeRow(row_index); +} + +void ConfigureTouchFromButton::OnBindingSelection(const QItemSelection& selected, +                                                  const QItemSelection& deselected) { +    ui->button_delete_bind->setEnabled(!selected.isEmpty()); +    if (!selected.isEmpty()) { +        const auto dot_data = selected.indexes().first().data(DataRoleDot); +        if (dot_data.isValid()) { +            ui->bottom_screen->HighlightDot(dot_data.toInt()); +        } +    } +    if (!deselected.isEmpty()) { +        const auto dot_data = deselected.indexes().first().data(DataRoleDot); +        if (dot_data.isValid()) { +            ui->bottom_screen->HighlightDot(dot_data.toInt(), false); +        } +    } +} + +void ConfigureTouchFromButton::OnBindingChanged(QStandardItem* item) { +    if (item->column() == 0) { +        return; +    } + +    const bool blocked = binding_list_model->blockSignals(true); +    item->setText(QString::number( +        std::clamp(item->text().toInt(), 0, +                   static_cast<int>((item->column() == 1 ? Layout::ScreenUndocked::Width +                                                         : Layout::ScreenUndocked::Height) - +                                    1)))); +    binding_list_model->blockSignals(blocked); + +    const auto dot_data = binding_list_model->index(item->row(), 0).data(DataRoleDot); +    if (dot_data.isValid()) { +        ui->bottom_screen->MoveDot(dot_data.toInt(), +                                   binding_list_model->item(item->row(), 1)->text().toInt(), +                                   binding_list_model->item(item->row(), 2)->text().toInt()); +    } +} + +void ConfigureTouchFromButton::OnBindingDeleted(const QModelIndex& parent, int first, int last) { +    for (int i = first; i <= last; ++i) { +        const auto ix = binding_list_model->index(i, 0); +        if (!ix.isValid()) { +            return; +        } +        const auto dot_data = ix.data(DataRoleDot); +        if (dot_data.isValid()) { +            ui->bottom_screen->RemoveDot(dot_data.toInt()); +        } +    } +} + +void ConfigureTouchFromButton::SetActiveBinding(const int dot_id) { +    for (int i = 0; i < binding_list_model->rowCount(); ++i) { +        if (binding_list_model->index(i, 0).data(DataRoleDot) == dot_id) { +            ui->binding_list->setCurrentIndex(binding_list_model->index(i, 0)); +            ui->binding_list->setFocus(); +            return; +        } +    } +} + +void ConfigureTouchFromButton::SetCoordinates(const int dot_id, const QPoint& pos) { +    for (int i = 0; i < binding_list_model->rowCount(); ++i) { +        if (binding_list_model->item(i, 0)->data(DataRoleDot) == dot_id) { +            binding_list_model->item(i, 1)->setText(QString::number(pos.x())); +            binding_list_model->item(i, 2)->setText(QString::number(pos.y())); +            return; +        } +    } +} + +void ConfigureTouchFromButton::SetPollingResult(const Common::ParamPackage& params, +                                                const bool cancel) { +    releaseKeyboard(); +    releaseMouse(); +    qApp->restoreOverrideCursor(); +    timeout_timer->stop(); +    poll_timer->stop(); +    for (auto& poller : device_pollers) { +        poller->Stop(); +    } +    if (input_setter) { +        (*input_setter)(params, cancel); +        input_setter.reset(); +    } +} + +void ConfigureTouchFromButton::keyPressEvent(QKeyEvent* event) { +    if (!input_setter && event->key() == Qt::Key_Delete) { +        DeleteBinding(); +        return; +    } + +    if (!input_setter) { +        return QDialog::keyPressEvent(event); +    } + +    if (event->key() != Qt::Key_Escape) { +        SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, +                         false); +    } else { +        SetPollingResult({}, true); +    } +} + +void ConfigureTouchFromButton::ApplyConfiguration() { +    SaveCurrentMapping(); +    accept(); +} + +int ConfigureTouchFromButton::GetSelectedIndex() const { +    return selected_index; +} + +std::vector<Settings::TouchFromButtonMap> ConfigureTouchFromButton::GetMaps() const { +    return touch_maps; +} + +TouchScreenPreview::TouchScreenPreview(QWidget* parent) : QFrame(parent) { +    setBackgroundRole(QPalette::ColorRole::Base); +} + +TouchScreenPreview::~TouchScreenPreview() = default; + +void TouchScreenPreview::SetCoordLabel(QLabel* const label) { +    coord_label = label; +} + +int TouchScreenPreview::AddDot(const int device_x, const int device_y) { +    QFont dot_font{QStringLiteral("monospace")}; +    dot_font.setStyleHint(QFont::Monospace); +    dot_font.setPointSize(20); + +    auto* dot = new QLabel(this); +    dot->setAttribute(Qt::WA_TranslucentBackground); +    dot->setFont(dot_font); +    dot->setText(QChar(0xD7)); // U+00D7 Multiplication Sign +    dot->setAlignment(Qt::AlignmentFlag::AlignCenter); +    dot->setProperty(PropId, ++max_dot_id); +    dot->setProperty(PropX, device_x); +    dot->setProperty(PropY, device_y); +    dot->setCursor(Qt::CursorShape::PointingHandCursor); +    dot->setMouseTracking(true); +    dot->installEventFilter(this); +    dot->show(); +    PositionDot(dot, device_x, device_y); +    dots.emplace_back(max_dot_id, dot); +    return max_dot_id; +} + +void TouchScreenPreview::RemoveDot(const int id) { +    const auto iter = std::find_if(dots.begin(), dots.end(), +                                   [id](const auto& entry) { return entry.first == id; }); +    if (iter == dots.cend()) { +        return; +    } + +    iter->second->deleteLater(); +    dots.erase(iter); +} + +void TouchScreenPreview::HighlightDot(const int id, const bool active) const { +    for (const auto& dot : dots) { +        if (dot.first == id) { +            // use color property from the stylesheet, or fall back to the default palette +            if (dot_highlight_color.isValid()) { +                dot.second->setStyleSheet( +                    active ? QStringLiteral("color: %1").arg(dot_highlight_color.name()) +                           : QString{}); +            } else { +                dot.second->setForegroundRole(active ? QPalette::ColorRole::LinkVisited +                                                     : QPalette::ColorRole::NoRole); +            } +            if (active) { +                dot.second->raise(); +            } +            return; +        } +    } +} + +void TouchScreenPreview::MoveDot(const int id, const int device_x, const int device_y) const { +    const auto iter = std::find_if(dots.begin(), dots.end(), +                                   [id](const auto& entry) { return entry.first == id; }); +    if (iter == dots.cend()) { +        return; +    } + +    iter->second->setProperty(PropX, device_x); +    iter->second->setProperty(PropY, device_y); +    PositionDot(iter->second, device_x, device_y); +} + +void TouchScreenPreview::resizeEvent(QResizeEvent* event) { +    if (ignore_resize) { +        return; +    } + +    const int target_width = std::min(width(), height() * 4 / 3); +    const int target_height = std::min(height(), width() * 3 / 4); +    if (target_width == width() && target_height == height()) { +        return; +    } +    ignore_resize = true; +    setGeometry((parentWidget()->contentsRect().width() - target_width) / 2, y(), target_width, +                target_height); +    ignore_resize = false; + +    if (event->oldSize().width() != target_width || event->oldSize().height() != target_height) { +        for (const auto& dot : dots) { +            PositionDot(dot.second); +        } +    } +} + +void TouchScreenPreview::mouseMoveEvent(QMouseEvent* event) { +    if (!coord_label) { +        return; +    } +    const auto pos = MapToDeviceCoords(event->x(), event->y()); +    if (pos) { +        coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y())); +    } else { +        coord_label->clear(); +    } +} + +void TouchScreenPreview::leaveEvent(QEvent* event) { +    if (coord_label) { +        coord_label->clear(); +    } +} + +void TouchScreenPreview::mousePressEvent(QMouseEvent* event) { +    if (event->button() != Qt::MouseButton::LeftButton) { +        return; +    } +    const auto pos = MapToDeviceCoords(event->x(), event->y()); +    if (pos) { +        emit DotAdded(*pos); +    } +} + +bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) { +    switch (event->type()) { +    case QEvent::Type::MouseButtonPress: { +        const auto mouse_event = static_cast<QMouseEvent*>(event); +        if (mouse_event->button() != Qt::MouseButton::LeftButton) { +            break; +        } +        emit DotSelected(obj->property(PropId).toInt()); + +        drag_state.dot = qobject_cast<QLabel*>(obj); +        drag_state.start_pos = mouse_event->globalPos(); +        return true; +    } +    case QEvent::Type::MouseMove: { +        if (!drag_state.dot) { +            break; +        } +        const auto mouse_event = static_cast<QMouseEvent*>(event); +        if (!drag_state.active) { +            drag_state.active = +                (mouse_event->globalPos() - drag_state.start_pos).manhattanLength() >= +                QApplication::startDragDistance(); +            if (!drag_state.active) { +                break; +            } +        } +        auto current_pos = mapFromGlobal(mouse_event->globalPos()); +        current_pos.setX(std::clamp(current_pos.x(), contentsMargins().left(), +                                    contentsMargins().left() + contentsRect().width() - 1)); +        current_pos.setY(std::clamp(current_pos.y(), contentsMargins().top(), +                                    contentsMargins().top() + contentsRect().height() - 1)); +        const auto device_coord = MapToDeviceCoords(current_pos.x(), current_pos.y()); +        if (device_coord) { +            drag_state.dot->setProperty(PropX, device_coord->x()); +            drag_state.dot->setProperty(PropY, device_coord->y()); +            PositionDot(drag_state.dot, device_coord->x(), device_coord->y()); +            emit DotMoved(drag_state.dot->property(PropId).toInt(), *device_coord); +            if (coord_label) { +                coord_label->setText( +                    QStringLiteral("X: %1, Y: %2").arg(device_coord->x()).arg(device_coord->y())); +            } +        } +        return true; +    } +    case QEvent::Type::MouseButtonRelease: { +        drag_state.dot.clear(); +        drag_state.active = false; +        return true; +    } +    default: +        break; +    } +    return obj->eventFilter(obj, event); +} + +std::optional<QPoint> TouchScreenPreview::MapToDeviceCoords(const int screen_x, +                                                            const int screen_y) const { +    const float t_x = 0.5f + static_cast<float>(screen_x - contentsMargins().left()) * +                                 (Layout::ScreenUndocked::Width - 1) / (contentsRect().width() - 1); +    const float t_y = 0.5f + static_cast<float>(screen_y - contentsMargins().top()) * +                                 (Layout::ScreenUndocked::Height - 1) / +                                 (contentsRect().height() - 1); +    if (t_x >= 0.5f && t_x < Layout::ScreenUndocked::Width && t_y >= 0.5f && +        t_y < Layout::ScreenUndocked::Height) { + +        return QPoint{static_cast<int>(t_x), static_cast<int>(t_y)}; +    } +    return std::nullopt; +} + +void TouchScreenPreview::PositionDot(QLabel* const dot, const int device_x, +                                     const int device_y) const { +    const float device_coord_x = +        static_cast<float>(device_x >= 0 ? device_x : dot->property(PropX).toInt()); +    int x_coord = static_cast<int>( +        device_coord_x * (contentsRect().width() - 1) / (Layout::ScreenUndocked::Width - 1) + +        contentsMargins().left() - static_cast<float>(dot->width()) / 2 + 0.5f); + +    const float device_coord_y = +        static_cast<float>(device_y >= 0 ? device_y : dot->property(PropY).toInt()); +    const int y_coord = static_cast<int>( +        device_coord_y * (contentsRect().height() - 1) / (Layout::ScreenUndocked::Height - 1) + +        contentsMargins().top() - static_cast<float>(dot->height()) / 2 + 0.5f); + +    dot->move(x_coord, y_coord); +} diff --git a/src/yuzu/configuration/configure_touch_from_button.h b/src/yuzu/configuration/configure_touch_from_button.h new file mode 100644 index 000000000..d9513e3bc --- /dev/null +++ b/src/yuzu/configuration/configure_touch_from_button.h @@ -0,0 +1,92 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <memory> +#include <optional> +#include <vector> +#include <QDialog> + +class QItemSelection; +class QModelIndex; +class QStandardItemModel; +class QStandardItem; +class QTimer; + +namespace Common { +class ParamPackage; +} + +namespace InputCommon { +class InputSubsystem; +} + +namespace InputCommon::Polling { +class DevicePoller; +} + +namespace Settings { +struct TouchFromButtonMap; +} + +namespace Ui { +class ConfigureTouchFromButton; +} + +class ConfigureTouchFromButton : public QDialog { +    Q_OBJECT + +public: +    explicit ConfigureTouchFromButton(QWidget* parent, +                                      const std::vector<Settings::TouchFromButtonMap>& touch_maps, +                                      InputCommon::InputSubsystem* input_subsystem_, +                                      int default_index = 0); +    ~ConfigureTouchFromButton() override; + +    int GetSelectedIndex() const; +    std::vector<Settings::TouchFromButtonMap> GetMaps() const; + +public slots: +    void ApplyConfiguration(); +    void NewBinding(const QPoint& pos); +    void SetActiveBinding(int dot_id); +    void SetCoordinates(int dot_id, const QPoint& pos); + +protected: +    void showEvent(QShowEvent* ev) override; +    void keyPressEvent(QKeyEvent* event) override; + +private slots: +    void NewMapping(); +    void DeleteMapping(); +    void RenameMapping(); +    void EditBinding(const QModelIndex& qi); +    void DeleteBinding(); +    void OnBindingSelection(const QItemSelection& selected, const QItemSelection& deselected); +    void OnBindingChanged(QStandardItem* item); +    void OnBindingDeleted(const QModelIndex& parent, int first, int last); + +private: +    void SetConfiguration(); +    void UpdateUiDisplay(); +    void ConnectEvents(); +    void GetButtonInput(int row_index, bool is_new); +    void SetPollingResult(const Common::ParamPackage& params, bool cancel); +    void SaveCurrentMapping(); + +    std::unique_ptr<Ui::ConfigureTouchFromButton> ui; +    std::vector<Settings::TouchFromButtonMap> touch_maps; +    QStandardItemModel* binding_list_model; +    InputCommon::InputSubsystem* input_subsystem; +    int selected_index; + +    std::unique_ptr<QTimer> timeout_timer; +    std::unique_ptr<QTimer> poll_timer; +    std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; +    std::optional<std::function<void(const Common::ParamPackage&, bool)>> input_setter; + +    static constexpr int DataRoleDot = Qt::ItemDataRole::UserRole + 2; +}; diff --git a/src/yuzu/configuration/configure_touch_from_button.ui b/src/yuzu/configuration/configure_touch_from_button.ui new file mode 100644 index 000000000..757219d54 --- /dev/null +++ b/src/yuzu/configuration/configure_touch_from_button.ui @@ -0,0 +1,221 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureTouchFromButton</class> + <widget class="QDialog" name="ConfigureTouchFromButton"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>500</width> +    <height>500</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Configure Touchscreen Mappings</string> +  </property> +  <layout class="QVBoxLayout"> +   <item> +    <layout class="QHBoxLayout" name="horizontalLayout"> +     <item> +      <widget class="QLabel" name="label"> +       <property name="text"> +        <string>Mapping:</string> +       </property> +       <property name="textFormat"> +        <enum>Qt::PlainText</enum> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QComboBox" name="mapping"> +       <property name="sizePolicy"> +        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> +         <horstretch>0</horstretch> +         <verstretch>0</verstretch> +        </sizepolicy> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QPushButton" name="button_new"> +       <property name="sizePolicy"> +        <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> +         <horstretch>0</horstretch> +         <verstretch>0</verstretch> +        </sizepolicy> +       </property> +       <property name="text"> +        <string>New</string> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QPushButton" name="button_delete"> +       <property name="sizePolicy"> +        <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> +         <horstretch>0</horstretch> +         <verstretch>0</verstretch> +        </sizepolicy> +       </property> +       <property name="text"> +        <string>Delete</string> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QPushButton" name="button_rename"> +       <property name="sizePolicy"> +        <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> +         <horstretch>0</horstretch> +         <verstretch>0</verstretch> +        </sizepolicy> +       </property> +       <property name="text"> +        <string>Rename</string> +       </property> +      </widget> +     </item> +    </layout> +   </item> +   <item> +    <widget class="Line" name="line"> +     <property name="orientation"> +      <enum>Qt::Horizontal</enum> +     </property> +    </widget> +   </item> +   <item> +    <layout class="QHBoxLayout" name="horizontalLayout_2"> +     <item> +      <widget class="QLabel" name="label_2"> +       <property name="text"> +        <string>Click the bottom area to add a point, then press a button to bind. +Drag points to change position, or double-click table cells to edit values.</string> +       </property> +       <property name="textFormat"> +        <enum>Qt::PlainText</enum> +       </property> +      </widget> +     </item> +     <item> +      <spacer name="horizontalSpacer"> +       <property name="orientation"> +        <enum>Qt::Horizontal</enum> +       </property> +       <property name="sizeHint" stdset="0"> +        <size> +         <width>40</width> +         <height>20</height> +        </size> +       </property> +      </spacer> +     </item> +     <item> +      <widget class="QPushButton" name="button_delete_bind"> +       <property name="text"> +        <string>Delete Point</string> +       </property> +      </widget> +     </item> +    </layout> +   </item> +   <item> +    <widget class="QTreeView" name="binding_list"> +     <property name="sizePolicy"> +      <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> +       <horstretch>0</horstretch> +       <verstretch>0</verstretch> +      </sizepolicy> +     </property> +     <property name="rootIsDecorated"> +      <bool>false</bool> +     </property> +     <property name="uniformRowHeights"> +      <bool>true</bool> +     </property> +     <property name="itemsExpandable"> +      <bool>false</bool> +     </property> +    </widget> +   </item> +   <item> +    <widget class="TouchScreenPreview" name="bottom_screen"> +     <property name="sizePolicy"> +      <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> +       <horstretch>0</horstretch> +       <verstretch>0</verstretch> +      </sizepolicy> +     </property> +     <property name="minimumSize"> +      <size> +       <width>160</width> +       <height>120</height> +      </size> +     </property> +     <property name="baseSize"> +      <size> +       <width>320</width> +       <height>240</height> +      </size> +     </property> +     <property name="cursor"> +      <cursorShape>CrossCursor</cursorShape> +     </property> +     <property name="mouseTracking"> +      <bool>true</bool> +     </property> +     <property name="autoFillBackground"> +      <bool>true</bool> +     </property> +     <property name="frameShape"> +      <enum>QFrame::StyledPanel</enum> +     </property> +     <property name="frameShadow"> +      <enum>QFrame::Sunken</enum> +     </property> +    </widget> +   </item> +   <item> +    <layout class="QHBoxLayout" name="horizontalLayout_3"> +     <item> +      <widget class="QLabel" name="coord_label"> +       <property name="sizePolicy"> +        <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> +         <horstretch>0</horstretch> +         <verstretch>0</verstretch> +        </sizepolicy> +       </property> +       <property name="textFormat"> +        <enum>Qt::PlainText</enum> +       </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> + <customwidgets> +  <customwidget> +   <class>TouchScreenPreview</class> +   <extends>QFrame</extends> +   <header>yuzu/configuration/configure_touch_widget.h</header> +   <container>1</container> +  </customwidget> + </customwidgets> + <resources/> + <connections> +  <connection> +   <sender>buttonBox</sender> +   <signal>rejected()</signal> +   <receiver>ConfigureTouchFromButton</receiver> +   <slot>reject()</slot> +  </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_touch_widget.h b/src/yuzu/configuration/configure_touch_widget.h new file mode 100644 index 000000000..347b46583 --- /dev/null +++ b/src/yuzu/configuration/configure_touch_widget.h @@ -0,0 +1,62 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <optional> +#include <utility> +#include <vector> +#include <QFrame> +#include <QPointer> + +class QLabel; + +// Widget for representing touchscreen coordinates +class TouchScreenPreview : public QFrame { +    Q_OBJECT +    Q_PROPERTY(QColor dotHighlightColor MEMBER dot_highlight_color) + +public: +    explicit TouchScreenPreview(QWidget* parent); +    ~TouchScreenPreview() override; + +    void SetCoordLabel(QLabel*); +    int AddDot(int device_x, int device_y); +    void RemoveDot(int id); +    void HighlightDot(int id, bool active = true) const; +    void MoveDot(int id, int device_x, int device_y) const; + +signals: +    void DotAdded(const QPoint& pos); +    void DotSelected(int dot_id); +    void DotMoved(int dot_id, const QPoint& pos); + +protected: +    void resizeEvent(QResizeEvent*) override; +    void mouseMoveEvent(QMouseEvent*) override; +    void leaveEvent(QEvent*) override; +    void mousePressEvent(QMouseEvent*) override; +    bool eventFilter(QObject*, QEvent*) override; + +private: +    std::optional<QPoint> MapToDeviceCoords(int screen_x, int screen_y) const; +    void PositionDot(QLabel* dot, int device_x = -1, int device_y = -1) const; + +    bool ignore_resize = false; +    QPointer<QLabel> coord_label; + +    std::vector<std::pair<int, QLabel*>> dots; +    int max_dot_id = 0; +    QColor dot_highlight_color; +    static constexpr char PropId[] = "dot_id"; +    static constexpr char PropX[] = "device_x"; +    static constexpr char PropY[] = "device_y"; + +    struct DragState { +        bool active = false; +        QPointer<QLabel> dot; +        QPoint start_pos; +    }; +    DragState drag_state; +}; diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.ui b/src/yuzu/configuration/configure_touchscreen_advanced.ui index 1171c2dd1..30ceccddb 100644 --- a/src/yuzu/configuration/configure_touchscreen_advanced.ui +++ b/src/yuzu/configuration/configure_touchscreen_advanced.ui @@ -168,32 +168,12 @@     <signal>accepted()</signal>     <receiver>ConfigureTouchscreenAdvanced</receiver>     <slot>accept()</slot> -   <hints> -    <hint type="sourcelabel"> -     <x>140</x> -     <y>318</y> -    </hint> -    <hint type="destinationlabel"> -     <x>140</x> -     <y>169</y> -    </hint> -   </hints>    </connection>    <connection>     <sender>buttonBox</sender>     <signal>rejected()</signal>     <receiver>ConfigureTouchscreenAdvanced</receiver>     <slot>reject()</slot> -   <hints> -    <hint type="sourcelabel"> -     <x>140</x> -     <y>318</y> -    </hint> -    <hint type="destinationlabel"> -     <x>140</x> -     <y>169</y> -    </hint> -   </hints> -  </connection> +   </connection>   </connections>  </ui> diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index 94424ee44..dbe3f78c8 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -4,8 +4,11 @@  #include <array>  #include <utility> +#include <QFileDialog> +#include <QDirIterator>  #include "common/common_types.h" +#include "common/file_util.h"  #include "core/settings.h"  #include "ui_configure_ui.h"  #include "yuzu/configuration/configure_ui.h" @@ -29,6 +32,8 @@ constexpr std::array row_text_names{  ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureUi) {      ui->setupUi(this); +    InitializeLanguageComboBox(); +      for (const auto& theme : UISettings::themes) {          ui->theme_combobox->addItem(QString::fromUtf8(theme.first),                                      QString::fromUtf8(theme.second)); @@ -49,9 +54,21 @@ ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::Configur      // Update text ComboBoxes after user interaction.      connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::activated), -            [=]() { ConfigureUi::UpdateSecondRowComboBox(); }); +            [this] { ConfigureUi::UpdateSecondRowComboBox(); });      connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::activated), -            [=]() { ConfigureUi::UpdateFirstRowComboBox(); }); +            [this] { ConfigureUi::UpdateFirstRowComboBox(); }); + +    // Set screenshot path to user specification. +    connect(ui->screenshot_path_button, &QToolButton::pressed, this, [this] { +        const QString& filename = +            QFileDialog::getExistingDirectory(this, tr("Select Screenshots Path..."), +                                              QString::fromStdString(Common::FS::GetUserPath( +                                                  Common::FS::UserPath::ScreenshotsDir))) + +            QDir::separator(); +        if (!filename.isEmpty()) { +            ui->screenshot_path_edit->setText(filename); +        } +    });  }  ConfigureUi::~ConfigureUi() = default; @@ -63,6 +80,10 @@ void ConfigureUi::ApplyConfiguration() {      UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt();      UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();      UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); + +    UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked(); +    Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir, +                            ui->screenshot_path_edit->text().toStdString());      Settings::Apply();  } @@ -72,9 +93,15 @@ void ConfigureUi::RequestGameListUpdate() {  void ConfigureUi::SetConfiguration() {      ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); +    ui->language_combobox->setCurrentIndex( +        ui->language_combobox->findData(UISettings::values.language));      ui->show_add_ons->setChecked(UISettings::values.show_add_ons);      ui->icon_size_combobox->setCurrentIndex(          ui->icon_size_combobox->findData(UISettings::values.icon_size)); + +    ui->enable_screenshot_save_as->setChecked(UISettings::values.enable_screenshot_save_as); +    ui->screenshot_path_edit->setText( +        QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir)));  }  void ConfigureUi::changeEvent(QEvent* event) { @@ -100,6 +127,25 @@ void ConfigureUi::RetranslateUI() {      }  } +void ConfigureUi::InitializeLanguageComboBox() { +    ui->language_combobox->addItem(tr("<System>"), QString{}); +    ui->language_combobox->addItem(tr("English"), QStringLiteral("en")); +    QDirIterator it(QStringLiteral(":/languages"), QDirIterator::NoIteratorFlags); +    while (it.hasNext()) { +        QString locale = it.next(); +        locale.truncate(locale.lastIndexOf(QLatin1Char{'.'})); +        locale.remove(0, locale.lastIndexOf(QLatin1Char{'/'}) + 1); +        const QString lang = QLocale::languageToString(QLocale(locale).language()); +        ui->language_combobox->addItem(lang, locale); +    } + +    // Unlike other configuration changes, interface language changes need to be reflected on the +    // interface immediately. This is done by passing a signal to the main window, and then +    // retranslating when passing back. +    connect(ui->language_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, +            &ConfigureUi::OnLanguageChanged); +} +  void ConfigureUi::InitializeIconSizeComboBox() {      for (const auto& size : default_icon_sizes) {          ui->icon_size_combobox->addItem(QString::fromUtf8(size.second), size.first); @@ -147,3 +193,10 @@ void ConfigureUi::UpdateSecondRowComboBox(bool init) {      ui->row_2_text_combobox->removeItem(          ui->row_2_text_combobox->findData(ui->row_1_text_combobox->currentData()));  } + +void ConfigureUi::OnLanguageChanged(int index) { +    if (index == -1) +        return; + +    emit LanguageChanged(ui->language_combobox->itemData(index).toString()); +} diff --git a/src/yuzu/configuration/configure_ui.h b/src/yuzu/configuration/configure_ui.h index d471afe99..c30bcf6ff 100644 --- a/src/yuzu/configuration/configure_ui.h +++ b/src/yuzu/configuration/configure_ui.h @@ -20,6 +20,12 @@ public:      void ApplyConfiguration(); +private slots: +    void OnLanguageChanged(int index); + +signals: +    void LanguageChanged(const QString& locale); +  private:      void RequestGameListUpdate(); @@ -28,6 +34,7 @@ private:      void changeEvent(QEvent*) override;      void RetranslateUI(); +    void InitializeLanguageComboBox();      void InitializeIconSizeComboBox();      void InitializeRowComboBoxes(); diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui index bd5c5d3c2..d895b799f 100644 --- a/src/yuzu/configuration/configure_ui.ui +++ b/src/yuzu/configuration/configure_ui.ui @@ -6,119 +6,180 @@     <rect>      <x>0</x>      <y>0</y> -    <width>300</width> -    <height>377</height> +    <width>363</width> +    <height>391</height>     </rect>    </property>    <property name="windowTitle">     <string>Form</string>    </property> -  <layout class="QHBoxLayout" name="HorizontalLayout"> +  <layout class="QVBoxLayout" name="verticalLayout">     <item> -    <layout class="QVBoxLayout" name="VerticalLayout"> -     <item> -      <widget class="QGroupBox" name="GeneralGroupBox"> -       <property name="title"> -        <string>General</string> -       </property> -       <layout class="QHBoxLayout" name="horizontalLayout"> +    <widget class="QGroupBox" name="general_groupBox"> +     <property name="title"> +      <string>General</string> +     </property> +     <layout class="QHBoxLayout" name="horizontalLayout"> +      <item> +       <layout class="QVBoxLayout" name="verticalLayout_2">          <item> -         <layout class="QVBoxLayout" name="verticalLayout"> +         <widget class="QLabel" name="label_change_language_info"> +          <property name="text"> +           <string>Note: Changing language will apply your configuration.</string> +          </property> +          <property name="wordWrap"> +           <bool>true</bool> +          </property> +         </widget> +        </item> +        <item> +         <layout class="QHBoxLayout" name="horizontalLayout_2"> +          <item> +           <widget class="QLabel" name="language_label"> +            <property name="text"> +             <string>Interface language:</string> +            </property> +           </widget> +          </item> +          <item> +           <widget class="QComboBox" name="language_combobox"/> +          </item> +         </layout> +        </item> +        <item> +         <layout class="QHBoxLayout" name="horizontalLayout_3"> +          <item> +           <widget class="QLabel" name="theme_label"> +            <property name="text"> +             <string>Theme:</string> +            </property> +           </widget> +          </item>            <item> -           <layout class="QHBoxLayout" name="horizontalLayout_3"> -            <item> -             <widget class="QLabel" name="theme_label"> -              <property name="text"> -               <string>Theme:</string> -              </property> -             </widget> -            </item> -            <item> -             <widget class="QComboBox" name="theme_combobox"/> -            </item> -           </layout> +           <widget class="QComboBox" name="theme_combobox"/>            </item>           </layout>          </item>         </layout> -      </widget> -     </item> -     <item> -      <widget class="QGroupBox" name="GameListGroupBox"> -       <property name="title"> -        <string>Game List</string> -       </property> -       <layout class="QHBoxLayout" name="GameListHorizontalLayout"> +      </item> +     </layout> +    </widget> +   </item> +   <item> +    <widget class="QGroupBox" name="GameListGroupBox"> +     <property name="title"> +      <string>Game List</string> +     </property> +     <layout class="QHBoxLayout" name="GameListHorizontalLayout"> +      <item> +       <layout class="QVBoxLayout" name="GeneralVerticalLayout">          <item> -         <layout class="QVBoxLayout" name="GeneralVerticalLayout"> +         <widget class="QCheckBox" name="show_add_ons"> +          <property name="text"> +           <string>Show Add-Ons Column</string> +          </property> +         </widget> +        </item> +        <item> +         <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2">            <item> -           <widget class="QCheckBox" name="show_add_ons"> +           <widget class="QLabel" name="icon_size_label">              <property name="text"> -             <string>Show Add-Ons Column</string> +             <string>Icon Size:</string>              </property>             </widget>            </item>            <item> -           <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2"> -            <item> -             <widget class="QLabel" name="icon_size_label"> -              <property name="text"> -               <string>Icon Size:</string> -              </property> -             </widget> -            </item> -            <item> -             <widget class="QComboBox" name="icon_size_combobox"/> -            </item> -           </layout> +           <widget class="QComboBox" name="icon_size_combobox"/>            </item> +         </layout> +        </item> +        <item> +         <layout class="QHBoxLayout" name="row_1_qhbox_layout">            <item> -           <layout class="QHBoxLayout" name="row_1_qhbox_layout"> -            <item> -             <widget class="QLabel" name="row_1_label"> -              <property name="text"> -               <string>Row 1 Text:</string> -              </property> -             </widget> -            </item> -            <item> -             <widget class="QComboBox" name="row_1_text_combobox"/> -            </item> -           </layout> +           <widget class="QLabel" name="row_1_label"> +            <property name="text"> +             <string>Row 1 Text:</string> +            </property> +           </widget>            </item>            <item> -           <layout class="QHBoxLayout" name="row_2_qhbox_layout"> -            <item> -             <widget class="QLabel" name="row_2_label"> -              <property name="text"> -               <string>Row 2 Text:</string> -              </property> -             </widget> -            </item> -            <item> -             <widget class="QComboBox" name="row_2_text_combobox"/> -            </item> -           </layout> +           <widget class="QComboBox" name="row_1_text_combobox"/> +          </item> +         </layout> +        </item> +        <item> +         <layout class="QHBoxLayout" name="row_2_qhbox_layout"> +          <item> +           <widget class="QLabel" name="row_2_label"> +            <property name="text"> +             <string>Row 2 Text:</string> +            </property> +           </widget> +          </item> +          <item> +           <widget class="QComboBox" name="row_2_text_combobox"/>            </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> -    </layout> +      </item> +     </layout> +    </widget> +   </item> +   <item> +    <widget class="QGroupBox" name="screenshots_GroupBox"> +     <property name="title"> +      <string>Screenshots</string> +     </property> +     <layout class="QVBoxLayout" name="verticalLayout_4"> +      <item> +       <layout class="QVBoxLayout" name="verticalLayout_3"> +        <item> +         <widget class="QCheckBox" name="enable_screenshot_save_as"> +          <property name="text"> +           <string>Ask Where To Save Screenshots (Windows Only)</string> +          </property> +         </widget> +        </item> +        <item> +         <layout class="QHBoxLayout" name="horizontalLayout_4"> +          <item> +           <widget class="QLabel" name="label"> +            <property name="text"> +             <string>Screenshots Path: </string> +            </property> +           </widget> +          </item> +          <item> +           <widget class="QLineEdit" name="screenshot_path_edit"/> +          </item> +          <item> +           <widget class="QToolButton" name="screenshot_path_button"> +            <property name="text"> +             <string>...</string> +            </property> +           </widget> +          </item> +         </layout> +        </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>    </layout>   </widget> diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp new file mode 100644 index 000000000..7dcb2c5b9 --- /dev/null +++ b/src/yuzu/configuration/configure_vibration.cpp @@ -0,0 +1,146 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <unordered_map> + +#include <fmt/format.h> + +#include "common/param_package.h" +#include "core/settings.h" +#include "ui_configure_vibration.h" +#include "yuzu/configuration/configure_vibration.h" + +ConfigureVibration::ConfigureVibration(QWidget* parent) +    : QDialog(parent), ui(std::make_unique<Ui::ConfigureVibration>()) { +    ui->setupUi(this); + +    vibration_groupboxes = { +        ui->vibrationGroupPlayer1, ui->vibrationGroupPlayer2, ui->vibrationGroupPlayer3, +        ui->vibrationGroupPlayer4, ui->vibrationGroupPlayer5, ui->vibrationGroupPlayer6, +        ui->vibrationGroupPlayer7, ui->vibrationGroupPlayer8, +    }; + +    vibration_spinboxes = { +        ui->vibrationSpinPlayer1, ui->vibrationSpinPlayer2, ui->vibrationSpinPlayer3, +        ui->vibrationSpinPlayer4, ui->vibrationSpinPlayer5, ui->vibrationSpinPlayer6, +        ui->vibrationSpinPlayer7, ui->vibrationSpinPlayer8, +    }; + +    const auto& players = Settings::values.players.GetValue(); + +    for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { +        vibration_groupboxes[i]->setChecked(players[i].vibration_enabled); +        vibration_spinboxes[i]->setValue(players[i].vibration_strength); +    } + +    ui->checkBoxAccurateVibration->setChecked( +        Settings::values.enable_accurate_vibrations.GetValue()); + +    if (!Settings::IsConfiguringGlobal()) { +        ui->checkBoxAccurateVibration->setDisabled(true); +    } + +    RetranslateUI(); +} + +ConfigureVibration::~ConfigureVibration() = default; + +void ConfigureVibration::ApplyConfiguration() { +    auto& players = Settings::values.players.GetValue(); + +    for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { +        players[i].vibration_enabled = vibration_groupboxes[i]->isChecked(); +        players[i].vibration_strength = vibration_spinboxes[i]->value(); +    } + +    Settings::values.enable_accurate_vibrations.SetValue( +        ui->checkBoxAccurateVibration->isChecked()); +} + +void ConfigureVibration::SetVibrationDevices(std::size_t player_index) { +    using namespace Settings::NativeButton; +    static constexpr std::array<std::array<Settings::NativeButton::Values, 6>, 2> buttons{{ +        {DLeft, DUp, DRight, DDown, L, ZL}, // Left Buttons +        {A, B, X, Y, R, ZR},                // Right Buttons +    }}; + +    auto& player = Settings::values.players.GetValue()[player_index]; + +    for (std::size_t device_idx = 0; device_idx < buttons.size(); ++device_idx) { +        std::unordered_map<std::string, int> params_count; + +        for (const auto button_index : buttons[device_idx]) { +            const auto& player_button = player.buttons[button_index]; + +            if (params_count.find(player_button) != params_count.end()) { +                ++params_count[player_button]; +                continue; +            } + +            params_count.insert_or_assign(player_button, 1); +        } + +        const auto it = std::max_element( +            params_count.begin(), params_count.end(), +            [](const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; }); + +        auto& vibration_param_str = player.vibrations[device_idx]; +        vibration_param_str.clear(); + +        if (it->first.empty()) { +            continue; +        } + +        const auto param = Common::ParamPackage(it->first); + +        const auto engine = param.Get("engine", ""); +        const auto guid = param.Get("guid", ""); +        const auto port = param.Get("port", ""); + +        if (engine.empty() || engine == "keyboard" || engine == "mouse") { +            continue; +        } + +        vibration_param_str += fmt::format("engine:{}", engine); + +        if (!port.empty()) { +            vibration_param_str += fmt::format(",port:{}", port); +        } +        if (!guid.empty()) { +            vibration_param_str += fmt::format(",guid:{}", guid); +        } +    } + +    if (player.vibrations[0] != player.vibrations[1]) { +        return; +    } + +    if (!player.vibrations[0].empty() && +        player.controller_type != Settings::ControllerType::RightJoycon) { +        player.vibrations[1].clear(); +    } else if (!player.vibrations[1].empty() && +               player.controller_type == Settings::ControllerType::RightJoycon) { +        player.vibrations[0].clear(); +    } +} + +void ConfigureVibration::SetAllVibrationDevices() { +    // Set vibration devices for all player indices including handheld +    for (std::size_t player_idx = 0; player_idx < NUM_PLAYERS + 1; ++player_idx) { +        SetVibrationDevices(player_idx); +    } +} + +void ConfigureVibration::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QDialog::changeEvent(event); +} + +void ConfigureVibration::RetranslateUI() { +    ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_vibration.h b/src/yuzu/configuration/configure_vibration.h new file mode 100644 index 000000000..07411a86f --- /dev/null +++ b/src/yuzu/configuration/configure_vibration.h @@ -0,0 +1,43 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <memory> +#include <QDialog> + +class QGroupBox; +class QSpinBox; + +namespace Ui { +class ConfigureVibration; +} + +class ConfigureVibration : public QDialog { +    Q_OBJECT + +public: +    explicit ConfigureVibration(QWidget* parent); +    ~ConfigureVibration() override; + +    void ApplyConfiguration(); + +    static void SetVibrationDevices(std::size_t player_index); +    static void SetAllVibrationDevices(); + +private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); + +    std::unique_ptr<Ui::ConfigureVibration> ui; + +    static constexpr std::size_t NUM_PLAYERS = 8; + +    // Groupboxes encapsulating the vibration strength spinbox. +    std::array<QGroupBox*, NUM_PLAYERS> vibration_groupboxes; + +    // Spinboxes representing the vibration strength percentage. +    std::array<QSpinBox*, NUM_PLAYERS> vibration_spinboxes; +}; diff --git a/src/yuzu/configuration/configure_vibration.ui b/src/yuzu/configuration/configure_vibration.ui new file mode 100644 index 000000000..efdf317a9 --- /dev/null +++ b/src/yuzu/configuration/configure_vibration.ui @@ -0,0 +1,546 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureVibration</class> + <widget class="QDialog" name="ConfigureVibration"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>364</width> +    <height>242</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Configure Vibration</string> +  </property> +  <property name="styleSheet"> +   <string notr="true"/> +  </property> +  <layout class="QVBoxLayout"> +   <item> +    <widget class="QGroupBox" name="vibrationStrengthGroup"> +     <property name="title"> +      <string>Vibration</string> +     </property> +     <layout class="QVBoxLayout" name="verticalLayout_3" stretch="0,0"> +      <property name="leftMargin"> +       <number>9</number> +      </property> +      <property name="topMargin"> +       <number>9</number> +      </property> +      <property name="rightMargin"> +       <number>9</number> +      </property> +      <property name="bottomMargin"> +       <number>9</number> +      </property> +      <item> +       <widget class="QWidget" name="player14Widget" native="true"> +        <layout class="QHBoxLayout" name="horizontalLayout_4"> +         <property name="leftMargin"> +          <number>0</number> +         </property> +         <property name="topMargin"> +          <number>0</number> +         </property> +         <property name="rightMargin"> +          <number>0</number> +         </property> +         <property name="bottomMargin"> +          <number>0</number> +         </property> +         <item> +          <widget class="QGroupBox" name="vibrationGroupPlayer1"> +           <property name="title"> +            <string>Player 1</string> +           </property> +           <property name="checkable"> +            <bool>true</bool> +           </property> +           <layout class="QHBoxLayout" name="horizontalLayout_8"> +            <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="QSpinBox" name="vibrationSpinPlayer1"> +              <property name="minimumSize"> +               <size> +                <width>68</width> +                <height>21</height> +               </size> +              </property> +              <property name="maximumSize"> +               <size> +                <width>68</width> +                <height>16777215</height> +               </size> +              </property> +              <property name="suffix"> +               <string>%</string> +              </property> +              <property name="minimum"> +               <number>1</number> +              </property> +              <property name="maximum"> +               <number>150</number> +              </property> +              <property name="value"> +               <number>100</number> +              </property> +             </widget> +            </item> +           </layout> +          </widget> +         </item> +         <item> +          <widget class="QGroupBox" name="vibrationGroupPlayer2"> +           <property name="title"> +            <string>Player 2</string> +           </property> +           <property name="checkable"> +            <bool>true</bool> +           </property> +           <layout class="QHBoxLayout" name="horizontalLayout_9"> +            <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="QSpinBox" name="vibrationSpinPlayer2"> +              <property name="minimumSize"> +               <size> +                <width>68</width> +                <height>21</height> +               </size> +              </property> +              <property name="maximumSize"> +               <size> +                <width>68</width> +                <height>16777215</height> +               </size> +              </property> +              <property name="suffix"> +               <string>%</string> +              </property> +              <property name="minimum"> +               <number>1</number> +              </property> +              <property name="maximum"> +               <number>150</number> +              </property> +              <property name="value"> +               <number>100</number> +              </property> +             </widget> +            </item> +           </layout> +          </widget> +         </item> +         <item> +          <widget class="QGroupBox" name="vibrationGroupPlayer3"> +           <property name="title"> +            <string>Player 3</string> +           </property> +           <property name="checkable"> +            <bool>true</bool> +           </property> +           <layout class="QHBoxLayout" name="horizontalLayout_10"> +            <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="QSpinBox" name="vibrationSpinPlayer3"> +              <property name="minimumSize"> +               <size> +                <width>68</width> +                <height>21</height> +               </size> +              </property> +              <property name="maximumSize"> +               <size> +                <width>68</width> +                <height>16777215</height> +               </size> +              </property> +              <property name="suffix"> +               <string>%</string> +              </property> +              <property name="minimum"> +               <number>1</number> +              </property> +              <property name="maximum"> +               <number>150</number> +              </property> +              <property name="value"> +               <number>100</number> +              </property> +             </widget> +            </item> +           </layout> +          </widget> +         </item> +         <item> +          <widget class="QGroupBox" name="vibrationGroupPlayer4"> +           <property name="title"> +            <string>Player 4</string> +           </property> +           <property name="checkable"> +            <bool>true</bool> +           </property> +           <layout class="QHBoxLayout" name="horizontalLayout_11"> +            <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="QSpinBox" name="vibrationSpinPlayer4"> +              <property name="minimumSize"> +               <size> +                <width>68</width> +                <height>21</height> +               </size> +              </property> +              <property name="maximumSize"> +               <size> +                <width>68</width> +                <height>16777215</height> +               </size> +              </property> +              <property name="suffix"> +               <string>%</string> +              </property> +              <property name="minimum"> +               <number>1</number> +              </property> +              <property name="maximum"> +               <number>150</number> +              </property> +              <property name="value"> +               <number>100</number> +              </property> +             </widget> +            </item> +           </layout> +          </widget> +         </item> +        </layout> +       </widget> +      </item> +      <item> +       <widget class="QWidget" name="player58Widget" native="true"> +        <layout class="QHBoxLayout" name="horizontalLayout_6"> +         <property name="leftMargin"> +          <number>0</number> +         </property> +         <property name="topMargin"> +          <number>0</number> +         </property> +         <property name="rightMargin"> +          <number>0</number> +         </property> +         <property name="bottomMargin"> +          <number>0</number> +         </property> +         <item> +          <widget class="QGroupBox" name="vibrationGroupPlayer7"> +           <property name="title"> +            <string>Player 5</string> +           </property> +           <property name="checkable"> +            <bool>true</bool> +           </property> +           <layout class="QHBoxLayout" name="horizontalLayout_14"> +            <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="QSpinBox" name="vibrationSpinPlayer7"> +              <property name="minimumSize"> +               <size> +                <width>68</width> +                <height>21</height> +               </size> +              </property> +              <property name="maximumSize"> +               <size> +                <width>68</width> +                <height>16777215</height> +               </size> +              </property> +              <property name="suffix"> +               <string>%</string> +              </property> +              <property name="minimum"> +               <number>1</number> +              </property> +              <property name="maximum"> +               <number>150</number> +              </property> +              <property name="value"> +               <number>100</number> +              </property> +             </widget> +            </item> +           </layout> +          </widget> +         </item> +         <item> +          <widget class="QGroupBox" name="vibrationGroupPlayer8"> +           <property name="title"> +            <string>Player 6</string> +           </property> +           <property name="checkable"> +            <bool>true</bool> +           </property> +           <layout class="QHBoxLayout" name="horizontalLayout_15"> +            <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="QSpinBox" name="vibrationSpinPlayer8"> +              <property name="minimumSize"> +               <size> +                <width>68</width> +                <height>21</height> +               </size> +              </property> +              <property name="maximumSize"> +               <size> +                <width>68</width> +                <height>16777215</height> +               </size> +              </property> +              <property name="suffix"> +               <string>%</string> +              </property> +              <property name="minimum"> +               <number>1</number> +              </property> +              <property name="maximum"> +               <number>150</number> +              </property> +              <property name="value"> +               <number>100</number> +              </property> +             </widget> +            </item> +           </layout> +          </widget> +         </item> +         <item> +          <widget class="QGroupBox" name="vibrationGroupPlayer5"> +           <property name="title"> +            <string>Player 7</string> +           </property> +           <property name="checkable"> +            <bool>true</bool> +           </property> +           <layout class="QHBoxLayout" name="horizontalLayout_12"> +            <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="QSpinBox" name="vibrationSpinPlayer5"> +              <property name="minimumSize"> +               <size> +                <width>68</width> +                <height>21</height> +               </size> +              </property> +              <property name="maximumSize"> +               <size> +                <width>68</width> +                <height>16777215</height> +               </size> +              </property> +              <property name="suffix"> +               <string>%</string> +              </property> +              <property name="minimum"> +               <number>1</number> +              </property> +              <property name="maximum"> +               <number>150</number> +              </property> +              <property name="value"> +               <number>100</number> +              </property> +             </widget> +            </item> +           </layout> +          </widget> +         </item> +         <item> +          <widget class="QGroupBox" name="vibrationGroupPlayer6"> +           <property name="title"> +            <string>Player 8</string> +           </property> +           <property name="checkable"> +            <bool>true</bool> +           </property> +           <layout class="QHBoxLayout" name="horizontalLayout_13"> +            <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="QSpinBox" name="vibrationSpinPlayer6"> +              <property name="minimumSize"> +               <size> +                <width>68</width> +                <height>21</height> +               </size> +              </property> +              <property name="maximumSize"> +               <size> +                <width>68</width> +                <height>16777215</height> +               </size> +              </property> +              <property name="suffix"> +               <string>%</string> +              </property> +              <property name="minimum"> +               <number>1</number> +              </property> +              <property name="maximum"> +               <number>150</number> +              </property> +              <property name="value"> +               <number>100</number> +              </property> +             </widget> +            </item> +           </layout> +          </widget> +         </item> +        </layout> +       </widget> +      </item> +     </layout> +    </widget> +   </item> +   <item> +    <widget class="QGroupBox" name="vibrationSettingsGroup"> +     <property name="title"> +      <string>Settings</string> +     </property> +     <layout class="QVBoxLayout" name="verticalLayout"> +      <item> +       <widget class="QCheckBox" name="checkBoxAccurateVibration"> +        <property name="text"> +         <string>Enable Accurate Vibration</string> +        </property> +       </widget> +      </item> +     </layout> +    </widget> +   </item> +   <item> +    <spacer name="spacerVibration"> +     <property name="orientation"> +      <enum>Qt::Vertical</enum> +     </property> +     <property name="sizeHint" stdset="0"> +      <size> +       <width>167</width> +       <height>55</height> +      </size> +     </property> +    </spacer> +   </item> +   <item> +    <widget class="QDialogButtonBox" name="buttonBoxVibration"> +     <property name="standardButtons"> +      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +     </property> +    </widget> +   </item> +  </layout> + </widget> + <resources/> + <connections> +  <connection> +   <sender>buttonBoxVibration</sender> +   <signal>accepted()</signal> +   <receiver>ConfigureVibration</receiver> +   <slot>accept()</slot> +  </connection> +  <connection> +   <sender>buttonBoxVibration</sender> +   <signal>rejected()</signal> +   <receiver>ConfigureVibration</receiver> +   <slot>reject()</slot> +  </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp new file mode 100644 index 000000000..e87aededb --- /dev/null +++ b/src/yuzu/configuration/input_profiles.cpp @@ -0,0 +1,131 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <fmt/format.h> + +#include "common/common_paths.h" +#include "common/file_util.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/input_profiles.h" + +namespace FS = Common::FS; + +namespace { + +bool ProfileExistsInFilesystem(std::string_view profile_name) { +    return FS::Exists(fmt::format("{}input" DIR_SEP "{}.ini", +                                  FS::GetUserPath(FS::UserPath::ConfigDir), profile_name)); +} + +bool IsINI(std::string_view filename) { +    const std::size_t index = filename.rfind('.'); + +    if (index == std::string::npos) { +        return false; +    } + +    return filename.substr(index) == ".ini"; +} + +std::string GetNameWithoutExtension(const std::string& filename) { +    const std::size_t index = filename.rfind('.'); + +    if (index == std::string::npos) { +        return filename; +    } + +    return filename.substr(0, index); +} + +} // namespace + +InputProfiles::InputProfiles() { +    const std::string input_profile_loc = +        fmt::format("{}input", FS::GetUserPath(FS::UserPath::ConfigDir)); + +    FS::ForeachDirectoryEntry( +        nullptr, input_profile_loc, +        [this](u64* entries_out, const std::string& directory, const std::string& filename) { +            if (IsINI(filename) && IsProfileNameValid(GetNameWithoutExtension(filename))) { +                map_profiles.insert_or_assign( +                    GetNameWithoutExtension(filename), +                    std::make_unique<Config>(GetNameWithoutExtension(filename), +                                             Config::ConfigType::InputProfile)); +            } +            return true; +        }); +} + +InputProfiles::~InputProfiles() = default; + +std::vector<std::string> InputProfiles::GetInputProfileNames() { +    std::vector<std::string> profile_names; +    profile_names.reserve(map_profiles.size()); + +    for (const auto& [profile_name, config] : map_profiles) { +        if (!ProfileExistsInFilesystem(profile_name)) { +            DeleteProfile(profile_name); +            continue; +        } + +        profile_names.push_back(profile_name); +    } + +    return profile_names; +} + +bool InputProfiles::IsProfileNameValid(std::string_view profile_name) { +    return profile_name.find_first_of("<>:;\"/\\|,.!?*") == std::string::npos; +} + +bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t player_index) { +    if (ProfileExistsInMap(profile_name)) { +        return false; +    } + +    map_profiles.insert_or_assign( +        profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile)); + +    return SaveProfile(profile_name, player_index); +} + +bool InputProfiles::DeleteProfile(const std::string& profile_name) { +    if (!ProfileExistsInMap(profile_name)) { +        return false; +    } + +    if (!ProfileExistsInFilesystem(profile_name) || +        FS::Delete(map_profiles[profile_name]->GetConfigFilePath())) { +        map_profiles.erase(profile_name); +    } + +    return !ProfileExistsInMap(profile_name) && !ProfileExistsInFilesystem(profile_name); +} + +bool InputProfiles::LoadProfile(const std::string& profile_name, std::size_t player_index) { +    if (!ProfileExistsInMap(profile_name)) { +        return false; +    } + +    if (!ProfileExistsInFilesystem(profile_name)) { +        map_profiles.erase(profile_name); +        return false; +    } + +    map_profiles[profile_name]->ReadControlPlayerValue(player_index); +    return true; +} + +bool InputProfiles::SaveProfile(const std::string& profile_name, std::size_t player_index) { +    if (!ProfileExistsInMap(profile_name)) { +        return false; +    } + +    map_profiles[profile_name]->SaveControlPlayerValue(player_index); +    return true; +} + +bool InputProfiles::ProfileExistsInMap(const std::string& profile_name) const { +    return map_profiles.find(profile_name) != map_profiles.end(); +} diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h new file mode 100644 index 000000000..cb41fd9be --- /dev/null +++ b/src/yuzu/configuration/input_profiles.h @@ -0,0 +1,32 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include <string_view> +#include <unordered_map> + +class Config; + +class InputProfiles { + +public: +    explicit InputProfiles(); +    virtual ~InputProfiles(); + +    std::vector<std::string> GetInputProfileNames(); + +    static bool IsProfileNameValid(std::string_view profile_name); + +    bool CreateProfile(const std::string& profile_name, std::size_t player_index); +    bool DeleteProfile(const std::string& profile_name); +    bool LoadProfile(const std::string& profile_name, std::size_t player_index); +    bool SaveProfile(const std::string& profile_name, std::size_t player_index); + +private: +    bool ProfileExistsInMap(const std::string& profile_name) const; + +    std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles; +};  | 
