code:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>ConfigureProfileManager</class>
 <widget class="QWidget" name="ConfigureProfileManager">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>390</width>
    <height>483</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <property name="accessibleName">
   <string>Profiles</string>
  </property>
  <layout class="QHBoxLayout" name="horizontalLayout">
   <item>
    <layout class="QVBoxLayout" name="verticalLayout">
     <item>
      <widget class="QGroupBox" name="gridGroupBox">
       <property name="title">
        <string>Profile Manager</string>
       </property>
       <layout class="QGridLayout" name="gridLayout_2">
        <property name="sizeConstraint">
         <enum>QLayout::SetNoConstraint</enum>
        </property>
        <item row="0" column="0">
         <layout class="QHBoxLayout" name="horizontalLayout_2">
          <item>
           <widget class="QLabel" name="label">
            <property name="sizePolicy">
             <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
              <horstretch>0</horstretch>
              <verstretch>0</verstretch>
             </sizepolicy>
            </property>
            <property name="text">
             <string>Current User</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QGraphicsView" name="current_user_icon">
            <property name="minimumSize">
             <size>
              <width>48</width>
              <height>48</height>
             </size>
            </property>
            <property name="maximumSize">
             <size>
              <width>48</width>
              <height>48</height>
             </size>
            </property>
            <property name="frameShape">
             <enum>QFrame::NoFrame</enum>
            </property>
            <property name="frameShadow">
             <enum>QFrame::Plain</enum>
            </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>
           <widget class="QLabel" name="current_user_username">
            <property name="sizePolicy">
             <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
              <horstretch>0</horstretch>
              <verstretch>0</verstretch>
             </sizepolicy>
            </property>
            <property name="text">
             <string>Username</string>
            </property>
           </widget>
          </item>
         </layout>
        </item>
        <item row="1" column="0">
         <widget class="QScrollArea" name="scrollArea">
          <property name="sizePolicy">
           <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
            <horstretch>0</horstretch>
            <verstretch>0</verstretch>
           </sizepolicy>
          </property>
          <property name="frameShape">
           <enum>QFrame::StyledPanel</enum>
          </property>
          <property name="widgetResizable">
           <bool>false</bool>
          </property>
         </widget>
        </item>
        <item row="2" column="0">
         <layout class="QHBoxLayout" name="horizontalLayout_3">
          <item>
           <widget class="QPushButton" name="pm_set_image">
            <property name="enabled">
             <bool>false</bool>
            </property>
            <property name="text">
             <string>Set Image</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="QPushButton" name="pm_add">
            <property name="text">
             <string>Add</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QPushButton" name="pm_rename">
            <property name="enabled">
             <bool>false</bool>
            </property>
            <property name="text">
             <string>Rename</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QPushButton" name="pm_remove">
            <property name="enabled">
             <bool>false</bool>
            </property>
            <property name="text">
             <string>Remove</string>
            </property>
           </widget>
          </item>
         </layout>
        </item>
       </layout>
      </widget>
     </item>
     <item>
      <widget class="QLabel" name="label_disable_info">
       <property name="text">
        <string>Profile management is available only when game is not running.</string>
       </property>
       <property name="wordWrap">
        <bool>true</bool>
       </property>
      </widget>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <memory>
#include <QKeyEvent>
#include <QMenu>
#include <QMessageBox>
#include <QTimer>
#include <fmt/format.h>

#include "configuration/qt_config.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "input_common/drivers/keyboard.h"
#include "input_common/drivers/mouse.h"
#include "input_common/main.h"
#include "ui_configure_ringcon.h"
#include "yuzu/bootmanager.h"
#include "yuzu/configuration/configure_ringcon.h"

const std::array<std::string, ConfigureRingController::ANALOG_SUB_BUTTONS_NUM>
    ConfigureRingController::analog_sub_buttons{{
        "left",
        "right",
    }};

namespace {

QString GetKeyName(int key_code) {
    switch (key_code) {
    case Qt::Key_Shift:
        return QObject::tr("Shift");
    case Qt::Key_Control:
        return QObject::tr("Ctrl");
    case Qt::Key_Alt:
        return QObject::tr("Alt");
    case Qt::Key_Meta:
        return {};
    default:
        return QKeySequence(key_code).toString();
    }
}

QString GetButtonName(Common::Input::ButtonNames button_name) {
    switch (button_name) {
    case Common::Input::ButtonNames::ButtonLeft:
        return QObject::tr("Left");
    case Common::Input::ButtonNames::ButtonRight:
        return QObject::tr("Right");
    case Common::Input::ButtonNames::ButtonDown:
        return QObject::tr("Down");
    case Common::Input::ButtonNames::ButtonUp:
        return QObject::tr("Up");
    case Common::Input::ButtonNames::TriggerZ:
        return QObject::tr("Z");
    case Common::Input::ButtonNames::TriggerR:
        return QObject::tr("R");
    case Common::Input::ButtonNames::TriggerL:
        return QObject::tr("L");
    case Common::Input::ButtonNames::ButtonA:
        return QObject::tr("A");
    case Common::Input::ButtonNames::ButtonB:
        return QObject::tr("B");
    case Common::Input::ButtonNames::ButtonX:
        return QObject::tr("X");
    case Common::Input::ButtonNames::ButtonY:
        return QObject::tr("Y");
    case Common::Input::ButtonNames::ButtonStart:
        return QObject::tr("Start");
    case Common::Input::ButtonNames::L1:
        return QObject::tr("L1");
    case Common::Input::ButtonNames::L2:
        return QObject::tr("L2");
    case Common::Input::ButtonNames::L3:
        return QObject::tr("L3");
    case Common::Input::ButtonNames::R1:
        return QObject::tr("R1");
    case Common::Input::ButtonNames::R2:
        return QObject::tr("R2");
    case Common::Input::ButtonNames::R3:
        return QObject::tr("R3");
    case Common::Input::ButtonNames::Circle:
        return QObject::tr("Circle");
    case Common::Input::ButtonNames::Cross:
        return QObject::tr("Cross");
    case Common::Input::ButtonNames::Square:
        return QObject::tr("Square");
    case Common::Input::ButtonNames::Triangle:
        return QObject::tr("Triangle");
    case Common::Input::ButtonNames::Share:
        return QObject::tr("Share");
    case Common::Input::ButtonNames::Options:
        return QObject::tr("Options");
    default:
        return QObject::tr("[undefined]");
    }
}

void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param,
                    const std::string& button_name) {
    // The poller returned a complete axis, so set all the buttons
    if (input_param.Has("axis_x") && input_param.Has("axis_y")) {
        analog_param = input_param;
        return;
    }
    // Check if the current configuration has either no engine or an axis binding.
    // Clears out the old binding and adds one with analog_from_button.
    if (!analog_param.Has("engine") || analog_param.Has("axis_x") || analog_param.Has("axis_y")) {
        analog_param = {
            {"engine", "analog_from_button"},
        };
    }
    analog_param.Set(button_name, input_param.Serialize());
}
} // namespace

ConfigureRingController::ConfigureRingController(QWidget* parent,
                                                 InputCommon::InputSubsystem* input_subsystem_,
                                                 Core::HID::HIDCore& hid_core_)
    : QDialog(parent), timeout_timer(std::make_unique<QTimer>()),
      poll_timer(std::make_unique<QTimer>()), input_subsystem{input_subsystem_},

      ui(std::make_unique<Ui::ConfigureRingController>()) {
    ui->setupUi(this);

    analog_map_buttons = {
        ui->buttonRingAnalogPull,
        ui->buttonRingAnalogPush,
    };

    emulated_controller = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1);
    emulated_controller->SaveCurrentConfig();
    emulated_controller->EnableConfiguration();

    Core::HID::ControllerUpdateCallback engine_callback{
        .on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdate(type); },
        .is_npad_service = false,
    };
    callback_key = emulated_controller->SetCallback(engine_callback);
    is_controller_set = true;

    LoadConfiguration();

    for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
        auto* const analog_button = analog_map_buttons[sub_button_id];

        if (analog_button == nullptr) {
            continue;
        }

        connect(analog_button, &QPushButton::clicked, [=, this] {
            HandleClick(
                analog_map_buttons[sub_button_id],
                [=, this](const Common::ParamPackage& params) {
                    Common::ParamPackage param = emulated_controller->GetRingParam();
                    SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]);
                    emulated_controller->SetRingParam(param);
                },
                InputCommon::Polling::InputType::Stick);
        });

        analog_button->setContextMenuPolicy(Qt::CustomContextMenu);

        connect(analog_button, &QPushButton::customContextMenuRequested,
                [=, this](const QPoint& menu_location) {
                    QMenu context_menu;
                    Common::ParamPackage param = emulated_controller->GetRingParam();
                    context_menu.addAction(tr("Clear"), [&] {
                        emulated_controller->SetRingParam(param);
                        analog_map_buttons[sub_button_id]->setText(tr("[not set]"));
                    });
                    context_menu.addAction(tr("Invert axis"), [&] {
                        const bool invert_value = param.Get("invert_x", "+") == "-";
                        const std::string invert_str = invert_value ? "+" : "-";
                        param.Set("invert_x", invert_str);
                        emulated_controller->SetRingParam(param);
                        for (int sub_button_id2 = 0; sub_button_id2 < ANALOG_SUB_BUTTONS_NUM;
                             ++sub_button_id2) {
                            analog_map_buttons[sub_button_id2]->setText(
                                AnalogToText(param, analog_sub_buttons[sub_button_id2]));
                        }
                    });
                    context_menu.exec(
                        analog_map_buttons[sub_button_id]->mapToGlobal(menu_location));
                });
    }

    connect(ui->sliderRingAnalogDeadzone, &QSlider::valueChanged, [=, this] {
        Common::ParamPackage param = emulated_controller->GetRingParam();
        const auto slider_value = ui->sliderRingAnalogDeadzone->value();
        ui->labelRingAnalogDeadzone->setText(tr("Deadzone: %1%").arg(slider_value));
        param.Set("deadzone", slider_value / 100.0f);
        emulated_controller->SetRingParam(param);
    });

    connect(ui->restore_defaults_button, &QPushButton::clicked, this,
            &ConfigureRingController::RestoreDefaults);

    connect(ui->enable_ring_controller_button, &QPushButton::clicked, this,
            &ConfigureRingController::EnableRingController);

    timeout_timer->setSingleShot(true);
    connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });

    connect(poll_timer.get(), &QTimer::timeout, [this] {
        const auto& params = input_subsystem->GetNextInput();
        if (params.Has("engine") && IsInputAcceptable(params)) {
            SetPollingResult(params, false);
            return;
        }
    });

    resize(0, 0);
}

ConfigureRingController::~ConfigureRingController() {
    emulated_controller->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
                                        Common::Input::PollingMode::Active);
    emulated_controller->DisableConfiguration();

    if (is_controller_set) {
        emulated_controller->DeleteCallback(callback_key);
        is_controller_set = false;
    }
};

void ConfigureRingController::changeEvent(QEvent* event) {
    if (event->type() == QEvent::LanguageChange) {
        RetranslateUI();
    }

    QDialog::changeEvent(event);
}

void ConfigureRingController::RetranslateUI() {
    ui->retranslateUi(this);
}

void ConfigureRingController::UpdateUI() {
    RetranslateUI();
    const Common::ParamPackage param = emulated_controller->GetRingParam();

    for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
        auto* const analog_button = analog_map_buttons[sub_button_id];

        if (analog_button == nullptr) {
            continue;
        }

        analog_button->setText(AnalogToText(param, analog_sub_buttons[sub_button_id]));
    }

    const auto deadzone_label = ui->labelRingAnalogDeadzone;
    const auto deadzone_slider = ui->sliderRingAnalogDeadzone;

    int slider_value = static_cast<int>(param.Get("deadzone", 0.15f) * 100);
    deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value));
    deadzone_slider->setValue(slider_value);
}

void ConfigureRingController::ApplyConfiguration() {
    emulated_controller->DisableConfiguration();
    emulated_controller->SaveCurrentConfig();
    emulated_controller->EnableConfiguration();
}

void ConfigureRingController::LoadConfiguration() {
    UpdateUI();
}

void ConfigureRingController::RestoreDefaults() {
    const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys(
        0, 0, QtConfig::default_ringcon_analogs[0], QtConfig::default_ringcon_analogs[1], 0, 0.05f);
    emulated_controller->SetRingParam(Common::ParamPackage(default_ring_string));
    UpdateUI();
}

void ConfigureRingController::EnableRingController() {
    const auto dialog_title = tr("Error enabling ring input");

    is_ring_enabled = false;
    ui->ring_controller_sensor_value->setText(tr("Not connected"));

    if (!Settings::values.enable_joycon_driver) {
        QMessageBox::warning(this, dialog_title, tr("Direct Joycon driver is not enabled"));
        return;
    }

    ui->enable_ring_controller_button->setEnabled(false);
    ui->enable_ring_controller_button->setText(tr("Configuring"));
    // SetPollingMode is blocking. Allow to update the button status before calling the command
    repaint();

    const auto result = emulated_controller->SetPollingMode(
        Core::HID::EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::Ring);
    switch (result) {
    case Common::Input::DriverResult::Success:
        is_ring_enabled = true;
        break;
    case Common::Input::DriverResult::NotSupported:
        QMessageBox::warning(this, dialog_title,
                             tr("The current mapped device doesn't support the ring controller"));
        break;
    case Common::Input::DriverResult::NoDeviceDetected:
        QMessageBox::warning(this, dialog_title,
                             tr("The current mapped device doesn't have a ring attached"));
        break;
    case Common::Input::DriverResult::InvalidHandle:
        QMessageBox::warning(this, dialog_title, tr("The current mapped device is not connected"));
        break;
    default:
        QMessageBox::warning(this, dialog_title,
                             tr("Unexpected driver result %1").arg(static_cast<int>(result)));
        break;
    }
    ui->enable_ring_controller_button->setEnabled(true);
    ui->enable_ring_controller_button->setText(tr("Enable"));
}

void ConfigureRingController::ControllerUpdate(Core::HID::ControllerTriggerType type) {
    if (!is_ring_enabled) {
        return;
    }
    if (type != Core::HID::ControllerTriggerType::RingController) {
        return;
    }

    const auto value = emulated_controller->GetRingSensorValues();
    const auto tex_value = QString::fromStdString(fmt::format("{:.3f}", value.raw_value));
    ui->ring_controller_sensor_value->setText(tex_value);
}

void ConfigureRingController::HandleClick(
    QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
    InputCommon::Polling::InputType type) {
    button->setText(tr("[waiting]"));
    button->setFocus();

    input_setter = new_input_setter;

    input_subsystem->BeginMapping(type);

    QWidget::grabMouse();
    QWidget::grabKeyboard();

    timeout_timer->start(2500); // Cancel after 2.5 seconds
    poll_timer->start(25);      // Check for new inputs every 25ms
}

void ConfigureRingController::SetPollingResult(const Common::ParamPackage& params, bool abort) {
    timeout_timer->stop();
    poll_timer->stop();
    input_subsystem->StopMapping();

    QWidget::releaseMouse();
    QWidget::releaseKeyboard();

    if (!abort) {
        (*input_setter)(params);
    }

    UpdateUI();

    input_setter = std::nullopt;
}

bool ConfigureRingController::IsInputAcceptable(const Common::ParamPackage& params) const {
    return true;
}

void ConfigureRingController::mousePressEvent(QMouseEvent* event) {
    if (!input_setter || !event) {
        return;
    }

    const auto button = GRenderWindow::QtButtonToMouseButton(event->button());
    input_subsystem->GetMouse()->PressButton(0, 0, button);
}

void ConfigureRingController::keyPressEvent(QKeyEvent* event) {
    if (!input_setter || !event) {
        return;
    }
    event->ignore();
    if (event->key() != Qt::Key_Escape) {
        input_subsystem->GetKeyboard()->PressKey(event->key());
    }
}

QString ConfigureRingController::ButtonToText(const Common::ParamPackage& param) {
    if (!param.Has("engine")) {
        return QObject::tr("[not set]");
    }

    const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
    const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : "");
    const auto common_button_name = input_subsystem->GetButtonName(param);

    // Retrieve the names from Qt
    if (param.Get("engine", "") == "keyboard") {
        const QString button_str = GetKeyName(param.Get("code", 0));
        return QObject::tr("%1%2").arg(toggle, button_str);
    }

    if (common_button_name == Common::Input::ButtonNames::Invalid) {
        return QObject::tr("[invalid]");
    }

    if (common_button_name == Common::Input::ButtonNames::Engine) {
        return QString::fromStdString(param.Get("engine", ""));
    }

    if (common_button_name == Common::Input::ButtonNames::Value) {
        if (param.Has("hat")) {
            const QString hat = QString::fromStdString(param.Get("direction", ""));
            return QObject::tr("%1%2Hat %3").arg(toggle, inverted, hat);
        }
        if (param.Has("axis")) {
            const QString axis = QString::fromStdString(param.Get("axis", ""));
            return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis);
        }
        if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) {
            const QString axis_x = QString::fromStdString(param.Get("axis_x", ""));
            const QString axis_y = QString::fromStdString(param.Get("axis_y", ""));
            const QString axis_z = QString::fromStdString(param.Get("axis_z", ""));
            return QObject::tr("%1%2Axis %3,%4,%5").arg(toggle, inverted, axis_x, axis_y, axis_z);
        }
        if (param.Has("motion")) {
            const QString motion = QString::fromStdString(param.Get("motion", ""));
            return QObject::tr("%1%2Motion %3").arg(toggle, inverted, motion);
        }
        if (param.Has("button")) {
            const QString button = QString::fromStdString(param.Get("button", ""));
            return QObject::tr("%1%2Button %3").arg(toggle, inverted, button);
        }
    }

    QString button_name = GetButtonName(common_button_name);
    if (param.Has("hat")) {
        return QObject::tr("%1%2Hat %3").arg(toggle, inverted, button_name);
    }
    if (param.Has("axis")) {
        return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name);
    }
    if (param.Has("motion")) {
        return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name);
    }
    if (param.Has("button")) {
        return QObject::tr("%1%2Button %3").arg(toggle, inverted, button_name);
    }

    return QObject::tr("[unknown]");
}

QString ConfigureRingController::AnalogToText(const Common::ParamPackage& param,
                                              const std::string& dir) {
    if (!param.Has("engine")) {
        return QObject::tr("[not set]");
    }

    if (param.Get("engine", "") == "analog_from_button") {
        return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
    }

    if (!param.Has("axis_x") || !param.Has("axis_y")) {
        return QObject::tr("[unknown]");
    }

    const auto engine_str = param.Get("engine", "");
    const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
    const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
    const bool invert_x = param.Get("invert_x", "+") == "-";
    const bool invert_y = param.Get("invert_y", "+") == "-";

    if (dir == "modifier") {
        return QObject::tr("[unused]");
    }

    if (dir == "left") {
        const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-");
        return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
    }
    if (dir == "right") {
        const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+");
        return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
    }
    if (dir == "up") {
        const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+");
        return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
    }
    if (dir == "down") {
        const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-");
        return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
    }

    return QObject::tr("[unknown]");
}

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <functional>
#include <memory>
#include <QDialog>

namespace InputCommon {
class InputSubsystem;
} // namespace InputCommon

namespace Core::HID {
class HIDCore;
class EmulatedController;
} // namespace Core::HID

namespace Ui {
class ConfigureRingController;
} // namespace Ui

class ConfigureRingController : public QDialog {
    Q_OBJECT

public:
    explicit ConfigureRingController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_,
                                     Core::HID::HIDCore& hid_core_);
    ~ConfigureRingController() override;

    void ApplyConfiguration();

private:
    void changeEvent(QEvent* event) override;
    void RetranslateUI();

    void UpdateUI();

    /// Load configuration settings.
    void LoadConfiguration();

    /// Restore all buttons to their default values.
    void RestoreDefaults();

    /// Sets current polling mode to ring input
    void EnableRingController();

    // Handles emulated controller events
    void ControllerUpdate(Core::HID::ControllerTriggerType type);

    /// Called when the button was pressed.
    void HandleClick(QPushButton* button,
                     std::function<void(const Common::ParamPackage&)> new_input_setter,
                     InputCommon::Polling::InputType type);

    /// Finish polling and configure input using the input_setter.
    void SetPollingResult(const Common::ParamPackage& params, bool abort);

    /// Checks whether a given input can be accepted.
    bool IsInputAcceptable(const Common::ParamPackage& params) const;

    /// Handle mouse button press events.
    void mousePressEvent(QMouseEvent* event) override;

    /// Handle key press events.
    void keyPressEvent(QKeyEvent* event) override;

    QString ButtonToText(const Common::ParamPackage& param);

    QString AnalogToText(const Common::ParamPackage& param, const std::string& dir);

    static constexpr int ANALOG_SUB_BUTTONS_NUM = 2;

    // A group of four QPushButtons represent one analog input. The buttons each represent left,
    // right, respectively.
    std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM> analog_map_buttons;

    static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;

    std::unique_ptr<QTimer> timeout_timer;
    std::unique_ptr<QTimer> poll_timer;

    /// This will be the the setting function when an input is awaiting configuration.
    std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;

    InputCommon::InputSubsystem* input_subsystem;
    Core::HID::EmulatedController* emulated_controller;

    bool is_ring_enabled{};
    bool is_controller_set{};
    int callback_key;

    std::unique_ptr<Ui::ConfigureRingController> ui;
};


code:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>ConfigureRingController</class>
 <widget class="QDialog" name="ConfigureRingController">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>315</width>
    <height>400</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Configure Ring Controller</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QLabel" name="label_2">
     <property name="minimumSize">
      <size>
       <width>280</width>
       <height>0</height>
      </size>
     </property>
     <property name="text">
      <string>To use Ring-Con, configure player 1 as right Joy-Con (both physical and emulated), and player 2 as left Joy-Con (left physical and dual emulated) before starting the game.</string>
     </property>
     <property name="wordWrap">
      <bool>true</bool>
     </property>
    </widget>
   </item>
   <item>
    <spacer name="verticalSpacer_2">
     <property name="orientation">
      <enum>Qt::Vertical</enum>
     </property>
     <property name="sizeType">
      <enum>QSizePolicy::Fixed</enum>
     </property>
     <property name="sizeHint" stdset="0">
      <size>
       <width>20</width>
       <height>10</height>
      </size>
     </property>
    </spacer>
   </item>
   <item>
    <widget class="QGroupBox" name="RingAnalog">
     <property name="title">
      <string>Virtual Ring Sensor Parameters</string>
     </property>
     <property name="alignment">
      <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
     </property>
     <layout class="QVBoxLayout" name="verticalLayout_1">
      <property name="spacing">
       <number>0</number>
      </property>
      <property name="sizeConstraint">
       <enum>QLayout::SetDefaultConstraint</enum>
      </property>
      <property name="leftMargin">
       <number>3</number>
      </property>
      <property name="topMargin">
       <number>6</number>
      </property>
      <property name="rightMargin">
       <number>3</number>
      </property>
      <property name="bottomMargin">
       <number>0</number>
      </property>
      <item>
       <layout class="QHBoxLayout" name="buttonRingAnalogPullHorizontaLayout">
        <property name="spacing">
         <number>3</number>
        </property>
        <item alignment="Qt::AlignHCenter">
         <widget class="QGroupBox" name="buttonRingAnalogPullGroup">
          <property name="title">
           <string>Pull</string>
          </property>
          <property name="alignment">
           <set>Qt::AlignCenter</set>
          </property>
          <layout class="QVBoxLayout" name="buttonRingAnalogPullVerticalLayout">
           <property name="spacing">
            <number>3</number>
           </property>
           <property name="leftMargin">
            <number>3</number>
           </property>
           <property name="topMargin">
            <number>3</number>
           </property>
           <property name="rightMargin">
            <number>3</number>
           </property>
           <property name="bottomMargin">
            <number>3</number>
           </property>
           <item>
            <widget class="QPushButton" name="buttonRingAnalogPull">
             <property name="minimumSize">
              <size>
               <width>70</width>
               <height>0</height>
              </size>
             </property>
             <property name="maximumSize">
              <size>
               <width>68</width>
               <height>16777215</height>
              </size>
             </property>
             <property name="styleSheet">
              <string notr="true">min-width: 68px;</string>
             </property>
             <property name="text">
              <string>Pull</string>
             </property>
            </widget>
           </item>
          </layout>
         </widget>
        </item>
        <item alignment="Qt::AlignHCenter">
         <widget class="QGroupBox" name="buttonRingAnalogPushGroup">
          <property name="title">
           <string>Push</string>
          </property>
          <property name="alignment">
           <set>Qt::AlignCenter</set>
          </property>
          <layout class="QVBoxLayout" name="buttonRingAnalogPushVerticalLayout">
           <property name="spacing">
            <number>3</number>
           </property>
           <property name="leftMargin">
            <number>3</number>
           </property>
           <property name="topMargin">
            <number>3</number>
           </property>
           <property name="rightMargin">
            <number>3</number>
           </property>
           <property name="bottomMargin">
            <number>3</number>
           </property>
           <item>
            <widget class="QPushButton" name="buttonRingAnalogPush">
             <property name="minimumSize">
              <size>
               <width>70</width>
               <height>0</height>
              </size>
             </property>
             <property name="maximumSize">
              <size>
               <width>68</width>
               <height>16777215</height>
              </size>
             </property>
             <property name="styleSheet">
              <string notr="true">min-width: 68px;</string>
             </property>
             <property name="text">
              <string>Push</string>
             </property>
            </widget>
           </item>
          </layout>
         </widget>
        </item>
       </layout>
      </item>
      <item>
       <layout class="QVBoxLayout" name="sliderRingAnalogDeadzoneVerticalLayout">
        <property name="spacing">
         <number>3</number>
        </property>
        <property name="sizeConstraint">
         <enum>QLayout::SetDefaultConstraint</enum>
        </property>
        <property name="leftMargin">
         <number>0</number>
        </property>
        <property name="topMargin">
         <number>10</number>
        </property>
        <property name="rightMargin">
         <number>0</number>
        </property>
        <property name="bottomMargin">
         <number>3</number>
        </property>
        <item>
         <layout class="QHBoxLayout" name="sliderRingAnalogDeadzoneHorizontalLayout">
          <item>
           <widget class="QLabel" name="labelRingAnalogDeadzone">
            <property name="text">
             <string>Deadzone: 0%</string>
            </property>
            <property name="alignment">
             <set>Qt::AlignHCenter</set>
            </property>
           </widget>
          </item>
         </layout>
        </item>
        <item>
         <widget class="QSlider" name="sliderRingAnalogDeadzone">
          <property name="maximum">
           <number>100</number>
          </property>
          <property name="orientation">
           <enum>Qt::Horizontal</enum>
          </property>
         </widget>
        </item>
       </layout>
      </item>
     </layout>
    </widget>
   </item>
   <item>
    <widget class="QGroupBox" name="RingDriver">
     <property name="title">
      <string>Direct Joycon Driver</string>
     </property>
     <property name="alignment">
      <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
     </property>
     <layout class="QVBoxLayout" name="verticalLayout_2">
      <property name="spacing">
       <number>0</number>
      </property>
      <property name="sizeConstraint">
       <enum>QLayout::SetDefaultConstraint</enum>
      </property>
      <property name="leftMargin">
       <number>3</number>
      </property>
      <property name="topMargin">
       <number>6</number>
      </property>
      <property name="rightMargin">
       <number>3</number>
      </property>
      <property name="bottomMargin">
       <number>10</number>
      </property>
      <item>
       <layout class="QGridLayout" name="gridLayout">
        <property name="leftMargin">
         <number>10</number>
        </property>
        <property name="topMargin">
         <number>6</number>
        </property>
        <property name="rightMargin">
         <number>10</number>
        </property>
        <property name="bottomMargin">
         <number>10</number>
        </property>
        <property name="verticalSpacing">
         <number>10</number>
        </property>
        <item row="0" column="1">
         <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>76</width>
            <height>20</height>
           </size>
          </property>
         </spacer>
        </item>
        <item row="0" column="0">
         <widget class="QLabel" name="enable_ring_controller_label">
          <property name="text">
           <string>Enable Ring Input</string>
          </property>
         </widget>
        </item>
        <item row="0" column="2">
         <widget class="QPushButton" name="enable_ring_controller_button">
          <property name="text">
           <string>Enable</string>
          </property>
         </widget>
        </item>
        <item row="1" column="0">
         <widget class="QLabel" name="ring_controller_sensor_label">
          <property name="text">
           <string>Ring Sensor Value</string>
          </property>
         </widget>
        </item>
        <item row="1" column="2">
         <widget class="QLabel" name="ring_controller_sensor_value">
          <property name="text">
           <string>Not connected</string>
          </property>
          <property name="alignment">
           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
          </property>
         </widget>
        </item>
       </layout>
      </item>
     </layout>
    </widget>
   </item>
   <item>
    <spacer name="verticalSpacer">
     <property name="orientation">
      <enum>Qt::Vertical</enum>
     </property>
     <property name="sizeHint" stdset="0">
      <size>
       <width>20</width>
       <height>40</height>
      </size>
     </property>
    </spacer>
   </item>
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout">
     <item>
      <widget class="QPushButton" name="restore_defaults_button">
       <property name="text">
        <string>Restore Defaults</string>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QDialogButtonBox" name="buttonBox">
       <property name="standardButtons">
        <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
       </property>
      </widget>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>buttonBox</sender>
   <signal>accepted()</signal>
   <receiver>ConfigureRingController</receiver>
   <slot>accept()</slot>
  </connection>
  <connection>
   <sender>buttonBox</sender>
   <signal>rejected()</signal>
   <receiver>ConfigureRingController</receiver>
   <slot>reject()</slot>
  </connection>
 </connections>
</ui>


code:
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <chrono>
#include <optional>
#include <vector>

#include <QCheckBox>
#include <QComboBox>
#include <QDateTimeEdit>
#include <QFileDialog>
#include <QGraphicsItem>
#include <QLineEdit>
#include <QMessageBox>
#include <QSpinBox>

#include "common/settings.h"
#include "core/core.h"
#include "ui_configure_system.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_system.h"
#include "yuzu/configuration/shared_widget.h"

constexpr std::array<u32, 7> LOCALE_BLOCKLIST{
    // pzzefezrpnkzeidfej
    // thhsrnhutlohsternp
    // BHH4CG          U
    // Raa1AB          S
    //  nn9
    //  ts
    0b0100011100001100000, // Japan
    0b0000001101001100100, // Americas
    0b0100110100001000010, // Europe
    0b0100110100001000010, // Australia
    0b0000000000000000000, // China
    0b0100111100001000000, // Korea
    0b0100111100001000000, // Taiwan
};

static bool IsValidLocale(u32 region_index, u32 language_index) {
    if (region_index >= LOCALE_BLOCKLIST.size()) {
        return false;
    }
    return ((LOCALE_BLOCKLIST.at(region_index) >> language_index) & 1) == 0;
}

ConfigureSystem::ConfigureSystem(Core::System& system_,
                                 std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
                                 const ConfigurationShared::Builder& builder, QWidget* parent)
    : Tab(group_, parent), ui{std::make_unique<Ui::ConfigureSystem>()}, system{system_} {
    ui->setupUi(this);

    const auto posix_time = std::chrono::system_clock::now().time_since_epoch();
    const auto current_time_s =
        std::chrono::duration_cast<std::chrono::seconds>(posix_time).count();
    previous_time = current_time_s + Settings::values.custom_rtc_offset.GetValue();

    Setup(builder);

    const auto locale_check = [this]() {
        const auto region_index = combo_region->currentIndex();
        const auto language_index = combo_language->currentIndex();
        const bool valid_locale = IsValidLocale(region_index, language_index);
        ui->label_warn_invalid_locale->setVisible(!valid_locale);
        if (!valid_locale) {
            ui->label_warn_invalid_locale->setText(
                tr("Warning: \"%1\" is not a valid language for region \"%2\"")
                    .arg(combo_language->currentText())
                    .arg(combo_region->currentText()));
        }
    };

    const auto update_date_offset = [this]() {
        if (!checkbox_rtc->isChecked()) {
            return;
        }
        auto offset = date_rtc_offset->value();
        offset += date_rtc->dateTime().toSecsSinceEpoch() - previous_time;
        previous_time = date_rtc->dateTime().toSecsSinceEpoch();
        date_rtc_offset->setValue(offset);
    };
    const auto update_rtc_date = [this]() { UpdateRtcTime(); };

    connect(combo_language, qOverload<int>(&QComboBox::currentIndexChanged), this, locale_check);
    connect(combo_region, qOverload<int>(&QComboBox::currentIndexChanged), this, locale_check);
    connect(checkbox_rtc, qOverload<int>(&QCheckBox::stateChanged), this, update_rtc_date);
    connect(date_rtc_offset, qOverload<int>(&QSpinBox::valueChanged), this, update_rtc_date);
    connect(date_rtc, &QDateTimeEdit::dateTimeChanged, this, update_date_offset);

    ui->label_warn_invalid_locale->setVisible(false);
    locale_check();

    SetConfiguration();
    UpdateRtcTime();
}

ConfigureSystem::~ConfigureSystem() = default;

void ConfigureSystem::changeEvent(QEvent* event) {
    if (event->type() == QEvent::LanguageChange) {
        RetranslateUI();
    }

    QWidget::changeEvent(event);
}

void ConfigureSystem::RetranslateUI() {
    ui->retranslateUi(this);
}

void ConfigureSystem::Setup(const ConfigurationShared::Builder& builder) {
    auto& core_layout = *ui->core_widget->layout();
    auto& system_layout = *ui->system_widget->layout();

    std::map<u32, QWidget*> core_hold{};
    std::map<u32, QWidget*> system_hold{};

    std::vector<Settings::BasicSetting*> settings;
    auto push = [&settings](auto& list) {
        for (auto setting : list) {
            settings.push_back(setting);
        }
    };

    push(Settings::values.linkage.by_category[Settings::Category::Core]);
    push(Settings::values.linkage.by_category[Settings::Category::System]);

    for (auto setting : settings) {
        if (setting->Id() == Settings::values.use_docked_mode.Id() &&
            Settings::IsConfiguringGlobal()) {
            continue;
        }

        ConfigurationShared::Widget* widget = builder.BuildWidget(setting, apply_funcs);

        if (widget == nullptr) {
            continue;
        }
        if (!widget->Valid()) {
            widget->deleteLater();
            continue;
        }

        // Keep track of the region_index (and language_index) combobox to validate the selected
        // settings
        if (setting->Id() == Settings::values.region_index.Id()) {
            combo_region = widget->combobox;
        }

        if (setting->Id() == Settings::values.language_index.Id()) {
            combo_language = widget->combobox;
        }

        if (setting->Id() == Settings::values.custom_rtc.Id()) {
            checkbox_rtc = widget->checkbox;
        }

        if (setting->Id() == Settings::values.custom_rtc.Id()) {
            date_rtc = widget->date_time_edit;
        }

        if (setting->Id() == Settings::values.custom_rtc_offset.Id()) {
            date_rtc_offset = widget->spinbox;
        }

        switch (setting->GetCategory()) {
        case Settings::Category::Core:
            core_hold.emplace(setting->Id(), widget);
            break;
        case Settings::Category::System:
            system_hold.emplace(setting->Id(), widget);
            break;
        default:
            widget->deleteLater();
        }
    }
    for (const auto& [label, widget] : core_hold) {
        core_layout.addWidget(widget);
    }
    for (const auto& [id, widget] : system_hold) {
        system_layout.addWidget(widget);
    }
}

void ConfigureSystem::UpdateRtcTime() {
    const auto posix_time = std::chrono::system_clock::now().time_since_epoch();
    previous_time = std::chrono::duration_cast<std::chrono::seconds>(posix_time).count();
    date_rtc_offset->setEnabled(checkbox_rtc->isChecked());

    if (checkbox_rtc->isChecked()) {
        previous_time += date_rtc_offset->value();
    }

    const auto date = QDateTime::fromSecsSinceEpoch(previous_time);
    date_rtc->setDateTime(date);
}

void ConfigureSystem::SetConfiguration() {}

void ConfigureSystem::ApplyConfiguration() {
    const bool powered_on = system.IsPoweredOn();
    for (const auto& func : apply_funcs) {
        func(powered_on);
    }
    UpdateRtcTime();
}


code:
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <functional>
#include <memory>
#include <vector>

#include <QWidget>
#include "yuzu/configuration/configuration_shared.h"

class QCheckBox;
class QLineEdit;
class QComboBox;
class QDateTimeEdit;
namespace Core {
class System;
}

namespace Ui {
class ConfigureSystem;
}

namespace ConfigurationShared {
class Builder;
}

class ConfigureSystem : public ConfigurationShared::Tab {
    Q_OBJECT

public:
    explicit ConfigureSystem(Core::System& system_,
                             std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
                             const ConfigurationShared::Builder& builder,
                             QWidget* parent = nullptr);
    ~ConfigureSystem() override;

    void ApplyConfiguration() override;
    void SetConfiguration() override;

private:
    void changeEvent(QEvent* event) override;
    void RetranslateUI();

    void Setup(const ConfigurationShared::Builder& builder);

    void UpdateRtcTime();

    std::vector<std::function<void(bool)>> apply_funcs{};

    std::unique_ptr<Ui::ConfigureSystem> ui;
    bool enabled = false;

    Core::System& system;

    QComboBox* combo_region;
    QComboBox* combo_language;
    QCheckBox* checkbox_rtc;
    QDateTimeEdit* date_rtc;
    QSpinBox* date_rtc_offset;
    u64 previous_time;
};


code:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>ConfigureSystem</class>
 <widget class="QWidget" name="ConfigureSystem">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>605</width>
    <height>483</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <property name="accessibleName">
   <string>System</string>
  </property>
  <layout class="QHBoxLayout" name="horizontalLayout">
   <item>
    <layout class="QVBoxLayout" name="verticalLayout">
     <item>
      <widget class="QGroupBox" name="group_system_settings">
       <property name="title">
        <string>System</string>
       </property>
       <layout class="QVBoxLayout" name="verticalLayout_2">
        <item>
         <widget class="QWidget" name="system_widget" native="true">
          <layout class="QVBoxLayout" name="verticalLayout_3">
           <property name="leftMargin">
            <number>0</number>
           </property>
           <property name="topMargin">
            <number>0</number>
           </property>
           <property name="rightMargin">
            <number>0</number>
           </property>
           <property name="bottomMargin">
            <number>0</number>
           </property>
          </layout>
         </widget>
        </item>
        <item>
         <widget class="QLabel" name="label_warn_invalid_locale">
          <property name="text">
           <string/>
          </property>
          <property name="wordWrap">
           <bool>true</bool>
          </property>
         </widget>
        </item>
       </layout>
      </widget>
     </item>
     <item>
      <widget class="QGroupBox" name="coreGroup">
       <property name="title">
        <string>Core</string>
       </property>
       <layout class="QVBoxLayout" name="verticalLayout_6">
        <item>
         <widget class="QWidget" name="core_widget" native="true">
          <layout class="QVBoxLayout" name="verticalLayout_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>
          </layout>
         </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>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <QFileDialog>
#include <QMessageBox>
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/settings.h"
#include "ui_configure_tas.h"
#include "yuzu/configuration/configure_tas.h"
#include "yuzu/uisettings.h"

ConfigureTasDialog::ConfigureTasDialog(QWidget* parent)
    : QDialog(parent), ui(std::make_unique<Ui::ConfigureTas>()) {

    ui->setupUi(this);

    setFocusPolicy(Qt::ClickFocus);
    setWindowTitle(tr("TAS Configuration"));

    connect(ui->tas_path_button, &QToolButton::pressed, this,
            [this] { SetDirectory(DirectoryTarget::TAS, ui->tas_path_edit); });

    LoadConfiguration();
}

ConfigureTasDialog::~ConfigureTasDialog() = default;

void ConfigureTasDialog::LoadConfiguration() {
    ui->tas_path_edit->setText(
        QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir)));
    ui->tas_enable->setChecked(Settings::values.tas_enable.GetValue());
    ui->tas_loop_script->setChecked(Settings::values.tas_loop.GetValue());
    ui->tas_pause_on_load->setChecked(Settings::values.pause_tas_on_load.GetValue());
}

void ConfigureTasDialog::ApplyConfiguration() {
    Common::FS::SetYuzuPath(Common::FS::YuzuPath::TASDir, ui->tas_path_edit->text().toStdString());
    Settings::values.tas_enable.SetValue(ui->tas_enable->isChecked());
    Settings::values.tas_loop.SetValue(ui->tas_loop_script->isChecked());
    Settings::values.pause_tas_on_load.SetValue(ui->tas_pause_on_load->isChecked());
}

void ConfigureTasDialog::SetDirectory(DirectoryTarget target, QLineEdit* edit) {
    QString caption;

    switch (target) {
    case DirectoryTarget::TAS:
        caption = tr("Select TAS Load Directory...");
        break;
    }

    QString str = QFileDialog::getExistingDirectory(this, caption, edit->text());

    if (str.isEmpty()) {
        return;
    }

    if (str.back() != QChar::fromLatin1('/')) {
        str.append(QChar::fromLatin1('/'));
    }

    edit->setText(str);
}

void ConfigureTasDialog::changeEvent(QEvent* event) {
    if (event->type() == QEvent::LanguageChange) {
        RetranslateUI();
    }

    QDialog::changeEvent(event);
}

void ConfigureTasDialog::RetranslateUI() {
    ui->retranslateUi(this);
}

void ConfigureTasDialog::HandleApplyButtonClicked() {
    UISettings::values.configuration_applied = true;
    ApplyConfiguration();
}


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <QDialog>

class QLineEdit;

namespace Ui {
class ConfigureTas;
}

class ConfigureTasDialog : public QDialog {
    Q_OBJECT

public:
    explicit ConfigureTasDialog(QWidget* parent);
    ~ConfigureTasDialog() override;

    /// Save all button configurations to settings file
    void ApplyConfiguration();

private:
    enum class DirectoryTarget {
        TAS,
    };

    void LoadConfiguration();

    void SetDirectory(DirectoryTarget target, QLineEdit* edit);

    void changeEvent(QEvent* event) override;
    void RetranslateUI();

    void HandleApplyButtonClicked();

    std::unique_ptr<Ui::ConfigureTas> ui;
};


code:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>ConfigureTas</class>
 <widget class="QDialog" name="ConfigureTas">
  <layout class="QVBoxLayout" name="verticalLayout_1">
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout_1">
     <item>
      <widget class="QGroupBox" name="groupBox_1">
       <property name="title">
        <string>TAS</string>
       </property>
       <layout class="QGridLayout" name="gridLayout_1">
        <item row="0" column="0" colspan="4">
         <widget class="QLabel" name="label_1">
          <property name="text">
           <string>
Reads controller input from scripts in the same format as TAS-nx scripts.
For a more detailed explanation, please consult the help page on the yuzu website.
</string>
          </property>
          <property name="openExternalLinks">
           <bool>true</bool>
          </property>
         </widget>
        </item>
        <item row="1" column="0" colspan="4">
         <widget class="QLabel" name="label_2">
          <property name="text">
           <string>To check which hotkeys control the playback/recording, please refer to the Hotkey settings (Configure -> General -> Hotkeys).</string>
          </property>
          <property name="wordWrap">
           <bool>true</bool>
          </property>
         </widget>
        </item>
        <item row="2" column="0" colspan="4">
         <widget class="QLabel" name="label_3">
          <property name="text">
           <string>WARNING: This is an experimental feature.
It will not play back scripts frame perfectly with the current, imperfect syncing method.</string>
          </property>
          <property name="wordWrap">
           <bool>true</bool>
          </property>
         </widget>
        </item>
       </layout>
      </widget>
     </item>
    </layout>
   </item>
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout_2">
     <item>
      <widget class="QGroupBox" name="groupBox_2">
       <property name="title">
        <string>Settings</string>
       </property>
       <layout class="QGridLayout" name="gridLayout_2">
        <item row="0" column="0" colspan="4">
         <widget class="QCheckBox" name="tas_enable">
          <property name="text">
           <string>Enable TAS features</string>
          </property>
         </widget>
        </item>
        <item row="1" column="0" colspan="4">
         <widget class="QCheckBox" name="tas_loop_script">
          <property name="text">
           <string>Loop script</string>
          </property>
         </widget>
        </item>
        <item row="2" column="0" colspan="4">
         <widget class="QCheckBox" name="tas_pause_on_load">
          <property name="enabled">
           <bool>false</bool>
          </property>
          <property name="text">
           <string>Pause execution during loads</string>
          </property>
         </widget>
        </item>
       </layout>
      </widget>
     </item>
    </layout>
   </item>
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout_3">
     <item>
      <widget class="QGroupBox" name="groupBox_3">
       <property name="title">
        <string>Script Directory</string>
       </property>
       <layout class="QGridLayout" name="gridLayout_3">
        <item row="0" column="0">
         <widget class="QLabel" name="label_4">
          <property name="text">
           <string>Path</string>
          </property>
         </widget>
        </item>
        <item row="0" column="3">
         <widget class="QToolButton" name="tas_path_button">
          <property name="text">
           <string>...</string>
          </property>
         </widget>
        </item>
        <item row="0" column="2">
         <widget class="QLineEdit" name="tas_path_edit"/>
        </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="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>
 <resources/>
 <connections>
  <connection>
   <sender>buttonBox</sender>
   <signal>accepted()</signal>
   <receiver>ConfigureTas</receiver>
   <slot>accept()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>20</x>
     <y>20</y>
    </hint>
    <hint type="destinationlabel">
     <x>20</x>
     <y>20</y>
    </hint>
   </hints>
  </connection>
  <connection>
   <sender>buttonBox</sender>
   <signal>rejected()</signal>
   <receiver>ConfigureTas</receiver>
   <slot>reject()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>20</x>
     <y>20</y>
    </hint>
    <hint type="destinationlabel">
     <x>20</x>
     <y>20</y>
    </hint>
   </hints>
  </connection>
 </connections>
</ui>


code:
// SPDX-FileCopyrightText: 2020 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <QInputDialog>
#include <QKeyEvent>
#include <QMessageBox>
#include <QMouseEvent>
#include <QStandardItemModel>
#include <QTimer>
#include "common/param_package.h"
#include "common/settings.h"
#include "core/frontend/framebuffer_layout.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]() {
        const auto& params = input_subsystem->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) {
    if (timeout_timer->isActive()) {
        return;
    }
    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()));
        }
    };

    input_subsystem->BeginMapping(InputCommon::Polling::InputType::Button);

    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) {
    timeout_timer->stop();
    poll_timer->stop();
    input_subsystem->StopMapping();

    releaseKeyboard();
    releaseMouse();
    qApp->restoreOverrideCursor();

    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);
}


code:
// SPDX-FileCopyrightText: 2020 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#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 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::optional<std::function<void(const Common::ParamPackage&, bool)>> input_setter;

    static constexpr int DataRoleDot = Qt::ItemDataRole::UserRole + 2;
};


code:
<?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>


code:
// SPDX-FileCopyrightText: 2020 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#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;
};


code:
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <memory>
#include "common/settings.h"
#include "ui_configure_touchscreen_advanced.h"
#include "yuzu/configuration/configure_touchscreen_advanced.h"

ConfigureTouchscreenAdvanced::ConfigureTouchscreenAdvanced(QWidget* parent)
    : QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchscreenAdvanced>()) {
    ui->setupUi(this);

    connect(ui->restore_defaults_button, &QPushButton::clicked, this,
            &ConfigureTouchscreenAdvanced::RestoreDefaults);

    LoadConfiguration();
    resize(0, 0);
}

ConfigureTouchscreenAdvanced::~ConfigureTouchscreenAdvanced() = default;

void ConfigureTouchscreenAdvanced::changeEvent(QEvent* event) {
    if (event->type() == QEvent::LanguageChange) {
        RetranslateUI();
    }

    QDialog::changeEvent(event);
}

void ConfigureTouchscreenAdvanced::RetranslateUI() {
    ui->retranslateUi(this);
}

void ConfigureTouchscreenAdvanced::ApplyConfiguration() {
    Settings::values.touchscreen.diameter_x = ui->diameter_x_box->value();
    Settings::values.touchscreen.diameter_y = ui->diameter_y_box->value();
    Settings::values.touchscreen.rotation_angle = ui->angle_box->value();
}

void ConfigureTouchscreenAdvanced::LoadConfiguration() {
    ui->diameter_x_box->setValue(Settings::values.touchscreen.diameter_x);
    ui->diameter_y_box->setValue(Settings::values.touchscreen.diameter_y);
    ui->angle_box->setValue(Settings::values.touchscreen.rotation_angle);
}

void ConfigureTouchscreenAdvanced::RestoreDefaults() {
    ui->diameter_x_box->setValue(15);
    ui->diameter_y_box->setValue(15);
    ui->angle_box->setValue(0);
}


code:
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <memory>
#include <QDialog>

namespace Ui {
class ConfigureTouchscreenAdvanced;
}

class ConfigureTouchscreenAdvanced : public QDialog {
    Q_OBJECT

public:
    explicit ConfigureTouchscreenAdvanced(QWidget* parent);
    ~ConfigureTouchscreenAdvanced() override;

    void ApplyConfiguration();

private:
    void changeEvent(QEvent* event) override;
    void RetranslateUI();

    /// Load configuration settings.
    void LoadConfiguration();
    /// Restore all buttons to their default values.
    void RestoreDefaults();

    std::unique_ptr<Ui::ConfigureTouchscreenAdvanced> ui;
};


code:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>ConfigureTouchscreenAdvanced</class>
 <widget class="QDialog" name="ConfigureTouchscreenAdvanced">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>298</width>
    <height>339</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Configure Touchscreen</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QLabel" name="label_2">
     <property name="minimumSize">
      <size>
       <width>280</width>
       <height>0</height>
      </size>
     </property>
     <property name="text">
      <string>Warning: The settings in this page affect the inner workings of yuzu's emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</string>
     </property>
     <property name="wordWrap">
      <bool>true</bool>
     </property>
    </widget>
   </item>
   <item>
    <spacer name="verticalSpacer_2">
     <property name="orientation">
      <enum>Qt::Vertical</enum>
     </property>
     <property name="sizeType">
      <enum>QSizePolicy::Fixed</enum>
     </property>
     <property name="sizeHint" stdset="0">
      <size>
       <width>20</width>
       <height>20</height>
      </size>
     </property>
    </spacer>
   </item>
   <item>
    <widget class="QGroupBox" name="gridGroupBox">
     <property name="title">
      <string>Touch Parameters</string>
     </property>
     <layout class="QGridLayout" name="gridLayout">
      <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="1" column="1">
       <widget class="QLabel" name="label_4">
        <property name="text">
         <string>Touch Diameter Y</string>
        </property>
       </widget>
      </item>
      <item row="0" 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="0" column="1">
       <widget class="QLabel" name="label_3">
        <property name="text">
         <string>Touch Diameter X</string>
        </property>
       </widget>
      </item>
      <item row="2" column="1">
       <widget class="QLabel" name="label_5">
        <property name="text">
         <string>Rotational Angle</string>
        </property>
       </widget>
      </item>
      <item row="0" column="2">
       <widget class="QSpinBox" name="diameter_x_box"/>
      </item>
      <item row="1" column="2">
       <widget class="QSpinBox" name="diameter_y_box"/>
      </item>
      <item row="2" column="2">
       <widget class="QSpinBox" name="angle_box"/>
      </item>
     </layout>
    </widget>
   </item>
   <item>
    <spacer name="verticalSpacer">
     <property name="orientation">
      <enum>Qt::Vertical</enum>
     </property>
     <property name="sizeHint" stdset="0">
      <size>
       <width>20</width>
       <height>40</height>
      </size>
     </property>
    </spacer>
   </item>
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout">
     <item>
      <widget class="QPushButton" name="restore_defaults_button">
       <property name="text">
        <string>Restore Defaults</string>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QDialogButtonBox" name="buttonBox">
       <property name="standardButtons">
        <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
       </property>
      </widget>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>buttonBox</sender>
   <signal>accepted()</signal>
   <receiver>ConfigureTouchscreenAdvanced</receiver>
   <slot>accept()</slot>
  </connection>
  <connection>
   <sender>buttonBox</sender>
   <signal>rejected()</signal>
   <receiver>ConfigureTouchscreenAdvanced</receiver>
   <slot>reject()</slot>
   </connection>
 </connections>
</ui>