From 75e81885b0bd38ece84a94b2d323b05d788f8741 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sat, 28 Jan 2023 18:19:15 -0600
Subject: [PATCH 1/2] input_common: Implement turbo buttons

---
 src/common/input.h                            |  2 +
 src/core/hid/emulated_controller.cpp          | 75 ++++++++++++++++++-
 src/core/hid/emulated_controller.h            |  6 ++
 src/core/hle/service/hid/controllers/npad.cpp |  3 +
 src/input_common/input_poller.cpp             | 30 +++++---
 .../configuration/configure_input_player.cpp  | 17 +++--
 6 files changed, 115 insertions(+), 18 deletions(-)

diff --git a/src/common/input.h b/src/common/input.h
index d61cd7ca88..b5748a6c84 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -130,6 +130,8 @@ struct ButtonStatus {
     bool inverted{};
     // Press once to activate, press again to release
     bool toggle{};
+    // Spams the button when active
+    bool turbo{};
     // Internal lock for the toggle status
     bool locked{};
 };
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 0e06468da1..631aa6ad21 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -12,6 +12,7 @@
 namespace Core::HID {
 constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
 constexpr s32 HID_TRIGGER_MAX = 0x7fff;
+constexpr u32 TURBO_BUTTON_DELAY = 4;
 // Use a common UUID for TAS and Virtual Gamepad
 constexpr Common::UUID TAS_UUID =
     Common::UUID{{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
@@ -447,6 +448,7 @@ void EmulatedController::ReloadInput() {
                 },
         });
     }
+    turbo_button_state = 0;
 }
 
 void EmulatedController::UnloadInput() {
@@ -687,6 +689,7 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
     }
 
     current_status.toggle = new_status.toggle;
+    current_status.turbo = new_status.turbo;
     current_status.uuid = uuid;
 
     // Update button status with current
@@ -1548,7 +1551,7 @@ NpadButtonState EmulatedController::GetNpadButtons() const {
     if (is_configuring) {
         return {};
     }
-    return controller.npad_button_state;
+    return {controller.npad_button_state.raw & GetTurboButtonMask()};
 }
 
 DebugPadButton EmulatedController::GetDebugPadButtons() const {
@@ -1656,4 +1659,74 @@ void EmulatedController::DeleteCallback(int key) {
     }
     callback_list.erase(iterator);
 }
+
+void EmulatedController::TurboButtonUpdate() {
+    turbo_button_state = (turbo_button_state + 1) % (TURBO_BUTTON_DELAY * 2);
+}
+
+NpadButton EmulatedController::GetTurboButtonMask() const {
+    // Apply no mask when disabled
+    if (turbo_button_state < TURBO_BUTTON_DELAY) {
+        return {NpadButton::All};
+    }
+
+    NpadButtonState button_mask{};
+    for (std::size_t index = 0; index < controller.button_values.size(); ++index) {
+        if (!controller.button_values[index].turbo) {
+            continue;
+        }
+
+        switch (index) {
+        case Settings::NativeButton::A:
+            button_mask.a.Assign(1);
+            break;
+        case Settings::NativeButton::B:
+            button_mask.b.Assign(1);
+            break;
+        case Settings::NativeButton::X:
+            button_mask.x.Assign(1);
+            break;
+        case Settings::NativeButton::Y:
+            button_mask.y.Assign(1);
+            break;
+        case Settings::NativeButton::L:
+            button_mask.l.Assign(1);
+            break;
+        case Settings::NativeButton::R:
+            button_mask.r.Assign(1);
+            break;
+        case Settings::NativeButton::ZL:
+            button_mask.zl.Assign(1);
+            break;
+        case Settings::NativeButton::ZR:
+            button_mask.zr.Assign(1);
+            break;
+        case Settings::NativeButton::DLeft:
+            button_mask.left.Assign(1);
+            break;
+        case Settings::NativeButton::DUp:
+            button_mask.up.Assign(1);
+            break;
+        case Settings::NativeButton::DRight:
+            button_mask.right.Assign(1);
+            break;
+        case Settings::NativeButton::DDown:
+            button_mask.down.Assign(1);
+            break;
+        case Settings::NativeButton::SL:
+            button_mask.left_sl.Assign(1);
+            button_mask.right_sl.Assign(1);
+            break;
+        case Settings::NativeButton::SR:
+            button_mask.left_sr.Assign(1);
+            button_mask.right_sr.Assign(1);
+            break;
+        default:
+            break;
+        }
+    }
+
+    return static_cast<NpadButton>(~button_mask.raw);
+}
+
 } // namespace Core::HID
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index 3ac77b2b5e..b02bf35c40 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -411,6 +411,9 @@ public:
      */
     void DeleteCallback(int key);
 
+    /// Swaps the state of the turbo buttons
+    void TurboButtonUpdate();
+
 private:
     /// creates input devices from params
     void LoadDevices();
@@ -511,6 +514,8 @@ private:
      */
     void TriggerOnChange(ControllerTriggerType type, bool is_service_update);
 
+    NpadButton GetTurboButtonMask() const;
+
     const NpadIdType npad_id_type;
     NpadStyleIndex npad_type{NpadStyleIndex::None};
     NpadStyleIndex original_npad_type{NpadStyleIndex::None};
@@ -520,6 +525,7 @@ private:
     bool system_buttons_enabled{true};
     f32 motion_sensitivity{0.01f};
     bool force_update_motion{false};
+    u32 turbo_button_state{0};
 
     // Temporary values to avoid doing changes while the controller is in configuring mode
     NpadStyleIndex tmp_npad_type{NpadStyleIndex::None};
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 5713f12888..3afda9e3f9 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -428,6 +428,9 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
         return;
     }
 
+    // This function is unique to yuzu for the turbo buttons to work properly
+    controller.device->TurboButtonUpdate();
+
     auto& pad_entry = controller.npad_pad_state;
     auto& trigger_entry = controller.npad_trigger_state;
     const auto button_state = controller.device->GetNpadButtons();
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index 15cbf7e5f0..8c6a6521ad 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -16,10 +16,10 @@ public:
 
 class InputFromButton final : public Common::Input::InputDevice {
 public:
-    explicit InputFromButton(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_,
-                             InputEngine* input_engine_)
-        : identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_),
-          input_engine(input_engine_) {
+    explicit InputFromButton(PadIdentifier identifier_, int button_, bool turbo_, bool toggle_,
+                             bool inverted_, InputEngine* input_engine_)
+        : identifier(identifier_), button(button_), turbo(turbo_), toggle(toggle_),
+          inverted(inverted_), input_engine(input_engine_) {
         UpdateCallback engine_callback{[this]() { OnChange(); }};
         const InputIdentifier input_identifier{
             .identifier = identifier,
@@ -40,6 +40,7 @@ public:
             .value = input_engine->GetButton(identifier, button),
             .inverted = inverted,
             .toggle = toggle,
+            .turbo = turbo,
         };
     }
 
@@ -68,6 +69,7 @@ public:
 private:
     const PadIdentifier identifier;
     const int button;
+    const bool turbo;
     const bool toggle;
     const bool inverted;
     int callback_key;
@@ -77,10 +79,10 @@ private:
 
 class InputFromHatButton final : public Common::Input::InputDevice {
 public:
-    explicit InputFromHatButton(PadIdentifier identifier_, int button_, u8 direction_, bool toggle_,
-                                bool inverted_, InputEngine* input_engine_)
-        : identifier(identifier_), button(button_), direction(direction_), toggle(toggle_),
-          inverted(inverted_), input_engine(input_engine_) {
+    explicit InputFromHatButton(PadIdentifier identifier_, int button_, u8 direction_, bool turbo_,
+                                bool toggle_, bool inverted_, InputEngine* input_engine_)
+        : identifier(identifier_), button(button_), direction(direction_), turbo(turbo_),
+          toggle(toggle_), inverted(inverted_), input_engine(input_engine_) {
         UpdateCallback engine_callback{[this]() { OnChange(); }};
         const InputIdentifier input_identifier{
             .identifier = identifier,
@@ -101,6 +103,7 @@ public:
             .value = input_engine->GetHatButton(identifier, button, direction),
             .inverted = inverted,
             .toggle = toggle,
+            .turbo = turbo,
         };
     }
 
@@ -130,6 +133,7 @@ private:
     const PadIdentifier identifier;
     const int button;
     const u8 direction;
+    const bool turbo;
     const bool toggle;
     const bool inverted;
     int callback_key;
@@ -853,14 +857,15 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateButtonDevice(
     const auto keyboard_key = params.Get("code", 0);
     const auto toggle = params.Get("toggle", false) != 0;
     const auto inverted = params.Get("inverted", false) != 0;
+    const auto turbo = params.Get("turbo", false) != 0;
     input_engine->PreSetController(identifier);
     input_engine->PreSetButton(identifier, button_id);
     input_engine->PreSetButton(identifier, keyboard_key);
     if (keyboard_key != 0) {
-        return std::make_unique<InputFromButton>(identifier, keyboard_key, toggle, inverted,
+        return std::make_unique<InputFromButton>(identifier, keyboard_key, turbo, toggle, inverted,
                                                  input_engine.get());
     }
-    return std::make_unique<InputFromButton>(identifier, button_id, toggle, inverted,
+    return std::make_unique<InputFromButton>(identifier, button_id, turbo, toggle, inverted,
                                              input_engine.get());
 }
 
@@ -876,11 +881,12 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateHatButtonDevice(
     const auto direction = input_engine->GetHatButtonId(params.Get("direction", ""));
     const auto toggle = params.Get("toggle", false) != 0;
     const auto inverted = params.Get("inverted", false) != 0;
+    const auto turbo = params.Get("turbo", false) != 0;
 
     input_engine->PreSetController(identifier);
     input_engine->PreSetHatButton(identifier, button_id);
-    return std::make_unique<InputFromHatButton>(identifier, button_id, direction, toggle, inverted,
-                                                input_engine.get());
+    return std::make_unique<InputFromHatButton>(identifier, button_id, direction, turbo, toggle,
+                                                inverted, input_engine.get());
 }
 
 std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateStickDevice(
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 4b7e3b01bf..723690e710 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -182,12 +182,13 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) {
     const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
     const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : "");
     const QString invert = QString::fromStdString(param.Get("invert", "+") == "-" ? "-" : "");
+    const QString turbo = QString::fromStdString(param.Get("turbo", 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%3").arg(toggle, inverted, button_str);
+        return QObject::tr("%1%2%3%4").arg(turbo, toggle, inverted, button_str);
     }
 
     if (common_button_name == Common::Input::ButtonNames::Invalid) {
@@ -201,7 +202,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) {
     if (common_button_name == Common::Input::ButtonNames::Value) {
         if (param.Has("hat")) {
             const QString hat = GetDirectionName(param.Get("direction", ""));
-            return QObject::tr("%1%2Hat %3").arg(toggle, inverted, hat);
+            return QObject::tr("%1%2%3Hat %4").arg(turbo, toggle, inverted, hat);
         }
         if (param.Has("axis")) {
             const QString axis = QString::fromStdString(param.Get("axis", ""));
@@ -219,13 +220,13 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) {
         }
         if (param.Has("button")) {
             const QString button = QString::fromStdString(param.Get("button", ""));
-            return QObject::tr("%1%2Button %3").arg(toggle, inverted, button);
+            return QObject::tr("%1%2%3Button %4").arg(turbo, 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);
+        return QObject::tr("%1%2%3Hat %4").arg(turbo, toggle, inverted, button_name);
     }
     if (param.Has("axis")) {
         return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name);
@@ -234,7 +235,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) {
         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("%1%2%3Button %4").arg(turbo, toggle, inverted, button_name);
     }
 
     return QObject::tr("[unknown]");
@@ -395,6 +396,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
                             button_map[button_id]->setText(ButtonToText(param));
                             emulated_controller->SetButtonParam(button_id, param);
                         });
+                        context_menu.addAction(tr("Turbo button"), [&] {
+                            const bool turbo_value = !param.Get("turbo", false);
+                            param.Set("turbo", turbo_value);
+                            button_map[button_id]->setText(ButtonToText(param));
+                            emulated_controller->SetButtonParam(button_id, param);
+                        });
                     }
                     if (param.Has("axis")) {
                         context_menu.addAction(tr("Invert axis"), [&] {

From ce1895497dd6a1ab87a6ab56d344b6f5e1e36ee7 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sun, 29 Jan 2023 12:23:30 -0600
Subject: [PATCH 2/2] yuzu: config: Draw turbo buttons with a different color

---
 .../configure_input_player_widget.cpp         | 35 +++++++++++--------
 .../configure_input_player_widget.h           |  2 ++
 2 files changed, 23 insertions(+), 14 deletions(-)

diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp
index 68af6c20c9..c287220fcd 100644
--- a/src/yuzu/configuration/configure_input_player_widget.cpp
+++ b/src/yuzu/configuration/configure_input_player_widget.cpp
@@ -81,7 +81,6 @@ void PlayerControlPreview::UpdateColors() {
         colors.outline = QColor(0, 0, 0);
         colors.primary = QColor(225, 225, 225);
         colors.button = QColor(109, 111, 114);
-        colors.button2 = QColor(109, 111, 114);
         colors.button2 = QColor(77, 80, 84);
         colors.slider_arrow = QColor(65, 68, 73);
         colors.font2 = QColor(0, 0, 0);
@@ -100,6 +99,7 @@ void PlayerControlPreview::UpdateColors() {
     colors.led_off = QColor(170, 238, 255);
     colors.indicator2 = QColor(59, 165, 93);
     colors.charging = QColor(250, 168, 26);
+    colors.button_turbo = QColor(217, 158, 4);
 
     colors.left = colors.primary;
     colors.right = colors.primary;
@@ -2469,7 +2469,6 @@ void PlayerControlPreview::DrawJoystickDot(QPainter& p, const QPointF center,
 void PlayerControlPreview::DrawRoundButton(QPainter& p, QPointF center,
                                            const Common::Input::ButtonStatus& pressed, float width,
                                            float height, Direction direction, float radius) {
-    p.setBrush(button_color);
     if (pressed.value) {
         switch (direction) {
         case Direction::Left:
@@ -2487,16 +2486,16 @@ void PlayerControlPreview::DrawRoundButton(QPainter& p, QPointF center,
         case Direction::None:
             break;
         }
-        p.setBrush(colors.highlight);
     }
     QRectF rect = {center.x() - width, center.y() - height, width * 2.0f, height * 2.0f};
+    p.setBrush(GetButtonColor(button_color, pressed.value, pressed.turbo));
     p.drawRoundedRect(rect, radius, radius);
 }
 void PlayerControlPreview::DrawMinusButton(QPainter& p, const QPointF center,
                                            const Common::Input::ButtonStatus& pressed,
                                            int button_size) {
     p.setPen(colors.outline);
-    p.setBrush(pressed.value ? colors.highlight : colors.button);
+    p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo));
     DrawRectangle(p, center, button_size, button_size / 3.0f);
 }
 void PlayerControlPreview::DrawPlusButton(QPainter& p, const QPointF center,
@@ -2504,7 +2503,7 @@ void PlayerControlPreview::DrawPlusButton(QPainter& p, const QPointF center,
                                           int button_size) {
     // Draw outer line
     p.setPen(colors.outline);
-    p.setBrush(pressed.value ? colors.highlight : colors.button);
+    p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo));
     DrawRectangle(p, center, button_size, button_size / 3.0f);
     DrawRectangle(p, center, button_size / 3.0f, button_size);
 
@@ -2526,7 +2525,7 @@ void PlayerControlPreview::DrawGCButtonX(QPainter& p, const QPointF center,
     }
 
     p.setPen(colors.outline);
-    p.setBrush(pressed.value ? colors.highlight : colors.button);
+    p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo));
     DrawPolygon(p, button_x);
 }
 
@@ -2539,7 +2538,7 @@ void PlayerControlPreview::DrawGCButtonY(QPainter& p, const QPointF center,
     }
 
     p.setPen(colors.outline);
-    p.setBrush(pressed.value ? colors.highlight : colors.button);
+    p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo));
     DrawPolygon(p, button_x);
 }
 
@@ -2553,17 +2552,15 @@ void PlayerControlPreview::DrawGCButtonZ(QPainter& p, const QPointF center,
     }
 
     p.setPen(colors.outline);
-    p.setBrush(pressed.value ? colors.highlight : colors.button2);
+    p.setBrush(GetButtonColor(colors.button2, pressed.value, pressed.turbo));
     DrawPolygon(p, button_x);
 }
 
 void PlayerControlPreview::DrawCircleButton(QPainter& p, const QPointF center,
                                             const Common::Input::ButtonStatus& pressed,
                                             float button_size) {
-    p.setBrush(button_color);
-    if (pressed.value) {
-        p.setBrush(colors.highlight);
-    }
+
+    p.setBrush(GetButtonColor(button_color, pressed.value, pressed.turbo));
     p.drawEllipse(center, button_size, button_size);
 }
 
@@ -2620,7 +2617,7 @@ void PlayerControlPreview::DrawArrowButton(QPainter& p, const QPointF center,
 
     // Draw arrow button
     p.setPen(pressed.value ? colors.highlight : colors.button);
-    p.setBrush(pressed.value ? colors.highlight : colors.button);
+    p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo));
     DrawPolygon(p, arrow_button);
 
     switch (direction) {
@@ -2672,10 +2669,20 @@ void PlayerControlPreview::DrawTriggerButton(QPainter& p, const QPointF center,
 
     // Draw arrow button
     p.setPen(colors.outline);
-    p.setBrush(pressed.value ? colors.highlight : colors.button);
+    p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo));
     DrawPolygon(p, qtrigger_button);
 }
 
+QColor PlayerControlPreview::GetButtonColor(QColor default_color, bool is_pressed, bool turbo) {
+    if (is_pressed && turbo) {
+        return colors.button_turbo;
+    }
+    if (is_pressed) {
+        return colors.highlight;
+    }
+    return default_color;
+}
+
 void PlayerControlPreview::DrawBattery(QPainter& p, QPointF center,
                                        Common::Input::BatteryLevel battery) {
     if (battery == Common::Input::BatteryLevel::None) {
diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h
index b258c6d773..0e9e95e85e 100644
--- a/src/yuzu/configuration/configure_input_player_widget.h
+++ b/src/yuzu/configuration/configure_input_player_widget.h
@@ -81,6 +81,7 @@ private:
         QColor right{};
         QColor button{};
         QColor button2{};
+        QColor button_turbo{};
         QColor font{};
         QColor font2{};
         QColor highlight{};
@@ -183,6 +184,7 @@ private:
                          const Common::Input::ButtonStatus& pressed, float size = 1.0f);
     void DrawTriggerButton(QPainter& p, QPointF center, Direction direction,
                            const Common::Input::ButtonStatus& pressed);
+    QColor GetButtonColor(QColor default_color, bool is_pressed, bool turbo);
 
     // Draw battery functions
     void DrawBattery(QPainter& p, QPointF center, Common::Input::BatteryLevel battery);