From 65fd31f27547b4fe1c118d50b29be5f9e08b3179 Mon Sep 17 00:00:00 2001 From: Collecting Date: Fri, 31 Oct 2025 05:32:24 +0000 Subject: [PATCH 01/10] feat: Gyro Toggle & Choose Color Signed-off-by: Collecting --- .../configuration/configure_input_player.ui | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/citron/configuration/configure_input_player.ui b/src/citron/configuration/configure_input_player.ui index 45d1794c8..c3e0fa564 100644 --- a/src/citron/configuration/configure_input_player.ui +++ b/src/citron/configuration/configure_input_player.ui @@ -2137,6 +2137,53 @@ + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Color + + + + + + + Toggle Gyro Display + + + Toggles the Gyro Indicator on and off in the Controller Overlay. + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + From 141d9add2e4c47603ee00ea2bdd2e89901a588b8 Mon Sep 17 00:00:00 2001 From: Collecting Date: Fri, 31 Oct 2025 05:33:41 +0000 Subject: [PATCH 02/10] Feat: Custom Controller Colors & Toggle Gyro Signed-off-by: Collecting --- .../configuration/configure_input_player.cpp | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/citron/configuration/configure_input_player.cpp b/src/citron/configuration/configure_input_player.cpp index 3a85fbef3..511ac5fe8 100644 --- a/src/citron/configuration/configure_input_player.cpp +++ b/src/citron/configuration/configure_input_player.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-FileCopyrightText: 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -27,6 +28,7 @@ #include "citron/configuration/configure_mouse_panning.h" #include "citron/configuration/input_profiles.h" #include "citron/util/limitable_input_dialog.h" +#include "common/settings_input.h" const std::array ConfigureInputPlayer::analog_sub_buttons{{ @@ -361,6 +363,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i analog_map_range_spinbox = {ui->spinboxLStickRange, ui->spinboxRStickRange}; ui->controllerFrame->SetController(emulated_controller); + ui->controllerFrame->SetRawJoystickVisible(true); + connect(ui->buttonChooseColor, &QPushButton::clicked, this, + &ConfigureInputPlayer::OnButtonChooseColor); + connect(ui->buttonToggleGyro, &QPushButton::clicked, this, + &ConfigureInputPlayer::OnButtonToggleGyro); for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { auto* const button = button_map[button_id]; @@ -1668,3 +1675,24 @@ void ConfigureInputPlayer::UpdateInputProfiles() { LOG_DEBUG(Frontend, "Setting the current input profile to index {}", profile_index); ui->comboProfiles->setCurrentIndex(profile_index); } + +void ConfigureInputPlayer::OnButtonChooseColor() { + const u32 current_color_val = emulated_controller->GetBodyColor(); + const QColor initial_color = + QColor(current_color_val != 0 ? current_color_val : Settings::DEFAULT_CONTROLLER_COLOR); + + const QColor color = QColorDialog::getColor(initial_color, this, tr("Choose Controller Color")); + + if (color.isValid()) { + emulated_controller->SetBodyColor(color.rgb()); + // The update call must be on the UI widget, not the controller object. + ui->controllerFrame->ControllerUpdate(Core::HID::ControllerTriggerType::Color); + } +} + +void ConfigureInputPlayer::OnButtonToggleGyro() { + bool current_visibility = emulated_controller->IsGyroOverlayVisible(); + emulated_controller->SetGyroOverlayVisible(!current_visibility); + // We can just re-draw everything to apply the change + ui->controllerFrame->ControllerUpdate(Core::HID::ControllerTriggerType::All); +} From 1315e238b1fa710f4cfb0d759b06d7a4e23b173b Mon Sep 17 00:00:00 2001 From: Collecting Date: Fri, 31 Oct 2025 05:34:41 +0000 Subject: [PATCH 03/10] feat: Add Choose Color & Toggle Gyro for Controller Overlay Signed-off-by: Collecting --- src/citron/configuration/configure_input_player.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/citron/configuration/configure_input_player.h b/src/citron/configuration/configure_input_player.h index fda09e925..79812ffe5 100644 --- a/src/citron/configuration/configure_input_player.h +++ b/src/citron/configuration/configure_input_player.h @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-FileCopyrightText: 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -11,6 +12,7 @@ #include #include +#include #include "common/param_package.h" #include "common/settings.h" @@ -167,6 +169,12 @@ private: /// Saves the current controller configuration into a selected controller profile. void SaveProfile(); + /// Implements a feature to change the controllers color manually. + void OnButtonChooseColor(); + + /// Toggles the gyro visibility for the overlay + void OnButtonToggleGyro(); + std::unique_ptr ui; std::size_t player_index; From 59bba7848d840833df0269394cd72929321a8fdd Mon Sep 17 00:00:00 2001 From: Collecting Date: Fri, 31 Oct 2025 05:35:35 +0000 Subject: [PATCH 04/10] feat/fix: Choose Color, Toggle Gyro & Deadzone Fix Signed-off-by: Collecting --- .../configure_input_player_widget.cpp | 61 ++++++++++++------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/src/citron/configuration/configure_input_player_widget.cpp b/src/citron/configuration/configure_input_player_widget.cpp index 63bb41a1f..7444f4e0c 100644 --- a/src/citron/configuration/configure_input_player_widget.cpp +++ b/src/citron/configuration/configure_input_player_widget.cpp @@ -106,6 +106,12 @@ void PlayerControlPreview::UpdateColors() { colors.charging = QColor(250, 168, 26); colors.button_turbo = QColor(217, 158, 4); + // Apply custom controller body color if it exists, overriding the theme color. + const auto custom_body_color = controller->GetBodyColor(); + if (custom_body_color != 0) { + colors.primary = QColor(custom_body_color); + } + colors.left = colors.primary; colors.right = colors.primary; @@ -348,10 +354,12 @@ void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center) { // Draw motion cubes using namespace Settings::NativeMotion; - p.setPen(colors.outline); - p.setBrush(colors.transparent); - Draw3dCube(p, center + QPointF(-140, 90), - motion_values[Settings::NativeMotion::MotionLeft].euler, 20.0f); + if (gyro_visible) { + p.setPen(colors.outline); + p.setBrush(colors.transparent); + Draw3dCube(p, center + QPointF(-140, 90), + motion_values[Settings::NativeMotion::MotionLeft].euler, 20.0f); + } } using namespace Settings::NativeButton; @@ -485,10 +493,12 @@ void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center { // Draw motion cubes using namespace Settings::NativeMotion; - p.setPen(colors.outline); - p.setBrush(colors.transparent); - Draw3dCube(p, center + QPointF(140, 90), - motion_values[Settings::NativeMotion::MotionRight].euler, 20.0f); + if (gyro_visible) { + p.setPen(colors.outline); + p.setBrush(colors.transparent); + Draw3dCube(p, center + QPointF(140, 90), + motion_values[Settings::NativeMotion::MotionRight].euler, 20.0f); + } } using namespace Settings::NativeButton; @@ -631,12 +641,14 @@ void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center) { // Draw motion cubes using namespace Settings::NativeMotion; - p.setPen(colors.outline); - p.setBrush(colors.transparent); - Draw3dCube(p, center + QPointF(-180, 90), - motion_values[Settings::NativeMotion::MotionLeft].euler, 20.0f); - Draw3dCube(p, center + QPointF(180, 90), - motion_values[Settings::NativeMotion::MotionRight].euler, 20.0f); + if (gyro_visible) { + p.setPen(colors.outline); + p.setBrush(colors.transparent); + Draw3dCube(p, center + QPointF(-180, 90), + motion_values[Settings::NativeMotion::MotionLeft].euler, 20.0f); + Draw3dCube(p, center + QPointF(180, 90), + motion_values[Settings::NativeMotion::MotionRight].euler, 20.0f); + } } using namespace Settings::NativeButton; @@ -736,10 +748,12 @@ void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF cen { // Draw motion cubes using namespace Settings::NativeMotion; - p.setPen(colors.outline); - p.setBrush(colors.transparent); - Draw3dCube(p, center + QPointF(0, -115), - motion_values[Settings::NativeMotion::MotionLeft].euler, 15.0f); + if (gyro_visible) { + p.setPen(colors.outline); + p.setBrush(colors.transparent); + Draw3dCube(p, center + QPointF(0, -115), + motion_values[Settings::NativeMotion::MotionLeft].euler, 15.0f); + } } using namespace Settings::NativeButton; @@ -850,10 +864,12 @@ void PlayerControlPreview::DrawProController(QPainter& p, const QPointF center) { // Draw motion cubes using namespace Settings::NativeMotion; - p.setPen(colors.button); - p.setBrush(colors.transparent); - Draw3dCube(p, center + QPointF(0, -100), - motion_values[Settings::NativeMotion::MotionLeft].euler, 15.0f); + if (gyro_visible) { + p.setPen(colors.button); + p.setBrush(colors.transparent); + Draw3dCube(p, center + QPointF(0, -100), + motion_values[Settings::NativeMotion::MotionLeft].euler, 15.0f); + } } using namespace Settings::NativeButton; @@ -2544,6 +2560,7 @@ void PlayerControlPreview::DrawJoystickProperties( DrawCircle(p, center, range); // Deadzone circle + pen.setStyle(Qt::SolidLine); pen.setColor(colors.deadzone); p.setPen(pen); DrawCircle(p, center, deadzone); From a6b038df43351d416d0e6b9dea64d2be9ae21486 Mon Sep 17 00:00:00 2001 From: Collecting Date: Fri, 31 Oct 2025 05:36:09 +0000 Subject: [PATCH 05/10] feat: Toggle Gyro for Controller Overlay --- src/citron/configuration/configure_input_player_widget.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/citron/configuration/configure_input_player_widget.h b/src/citron/configuration/configure_input_player_widget.h index 7b4589ef3..22a8fa8f3 100644 --- a/src/citron/configuration/configure_input_player_widget.h +++ b/src/citron/configuration/configure_input_player_widget.h @@ -50,6 +50,9 @@ public: // Updates input on scheduled interval void UpdateInput(); + // Gyro Visibility + bool gyro_visible = true; + protected: void paintEvent(QPaintEvent* event) override; From bb98468168fd68cf731c08b890b263a785f99b7c Mon Sep 17 00:00:00 2001 From: Collecting Date: Fri, 31 Oct 2025 05:37:03 +0000 Subject: [PATCH 06/10] feat: Controller Color & Toggle Overlay Signed-off-by: Collecting --- src/citron/configuration/qt_config.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/citron/configuration/qt_config.cpp b/src/citron/configuration/qt_config.cpp index 37951b9c8..39decf2a0 100644 --- a/src/citron/configuration/qt_config.cpp +++ b/src/citron/configuration/qt_config.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-FileCopyrightText: 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" @@ -95,6 +96,10 @@ void QtConfig::ReadQtPlayerValues(const std::size_t player_index) { } } + const auto body_color_str = ReadStringSetting(std::string(player_prefix).append("body_color"), fmt::format("{:x}", Settings::DEFAULT_CONTROLLER_COLOR)); + player.body_color = std::stoul(body_color_str, nullptr, 16); + player.gyro_overlay_visible = ReadBooleanSetting(std::string(player_prefix).append("gyro_overlay_visible"), true); + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); auto& player_buttons = player.buttons[i]; @@ -351,6 +356,9 @@ void QtConfig::SaveQtPlayerValues(const std::size_t player_index) { return; } + WriteStringSetting(std::string(player_prefix).append("body_color"), fmt::format("{:x}", player.body_color), fmt::format("{:x}", Settings::DEFAULT_CONTROLLER_COLOR)); + WriteBooleanSetting(std::string(player_prefix).append("gyro_overlay_visible"), player.gyro_overlay_visible, true); + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); WriteStringSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]), From e1f4815c0cc56be2c7496f13f0ad6dc930aee605 Mon Sep 17 00:00:00 2001 From: Collecting Date: Fri, 31 Oct 2025 05:38:22 +0000 Subject: [PATCH 07/10] feat: Add Controller Color & Toggle Gyro Signed-off-by: Collecting --- src/common/settings_input.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/common/settings_input.h b/src/common/settings_input.h index a99bb0892..19e249840 100644 --- a/src/common/settings_input.h +++ b/src/common/settings_input.h @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -365,6 +366,7 @@ constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28; constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A; constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6; constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E; +constexpr u32 DEFAULT_CONTROLLER_COLOR = 0xE1E1E1; enum class ControllerType { ProController, @@ -390,10 +392,12 @@ struct PlayerInput { bool vibration_enabled; int vibration_strength; + u32 body_color; u32 body_color_left; u32 body_color_right; u32 button_color_left; u32 button_color_right; + bool gyro_overlay_visible; std::string profile_name; // This is meant to tell the Android frontend whether to use a device's built-in vibration From dc82d22e48ada22c30af3bf03b1a4b6c66320961 Mon Sep 17 00:00:00 2001 From: Collecting Date: Fri, 31 Oct 2025 05:39:33 +0000 Subject: [PATCH 08/10] feat: Toggle Gyro for Controller Overlay Signed-off-by: Collecting --- src/citron/controller_overlay.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/citron/controller_overlay.cpp b/src/citron/controller_overlay.cpp index c843669bb..c2066f43e 100644 --- a/src/citron/controller_overlay.cpp +++ b/src/citron/controller_overlay.cpp @@ -70,6 +70,7 @@ void ControllerOverlay::UpdateControllerState() { Core::HID::EmulatedController* controller = GetPlayer1Controller(system); if (controller_widget && controller) { controller_widget->SetController(controller); + controller_widget->gyro_visible = controller->IsGyroOverlayVisible(); controller_widget->UpdateInput(); } } From 35e27a52c46326e0720b5ef3ee0ca3279ecf238e Mon Sep 17 00:00:00 2001 From: Collecting Date: Fri, 31 Oct 2025 05:40:39 +0000 Subject: [PATCH 09/10] feat: Choose Controller Color & Toggle Gyro Signed-off-by: Collecting --- src/hid_core/frontend/emulated_controller.cpp | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/hid_core/frontend/emulated_controller.cpp b/src/hid_core/frontend/emulated_controller.cpp index 9aa08d5cc..909dd0eef 100644 --- a/src/hid_core/frontend/emulated_controller.cpp +++ b/src/hid_core/frontend/emulated_controller.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -97,6 +98,9 @@ void EmulatedController::ReloadFromSettings() { motion_params[index] = Common::ParamPackage(player.motions[index]); } + controller.body_color = player.body_color; + controller.gyro_overlay_visible = player.gyro_overlay_visible; + controller.color_values = {}; ReloadColorsFromSettings(); @@ -637,6 +641,8 @@ void EmulatedController::SaveCurrentConfig() { auto& player = Settings::values.players.GetValue()[player_index]; player.connected = is_connected; player.controller_type = MapNPadToSettingsType(npad_type); + player.body_color = controller.body_color; + player.gyro_overlay_visible = controller.gyro_overlay_visible; for (std::size_t index = 0; index < player.buttons.size(); ++index) { player.buttons[index] = button_params[index].Serialize(); } @@ -730,6 +736,26 @@ Common::ParamPackage EmulatedController::GetMotionParam(std::size_t index) const return motion_params[index]; } +u32 EmulatedController::GetBodyColor() const { + std::scoped_lock lock{mutex}; + return controller.body_color; +} + +void EmulatedController::SetBodyColor(u32 color) { + std::scoped_lock lock{mutex}; + controller.body_color = color; +} + +bool EmulatedController::IsGyroOverlayVisible() const { + std::scoped_lock lock{mutex}; + return controller.gyro_overlay_visible; +} + +void EmulatedController::SetGyroOverlayVisible(bool visible) { + std::scoped_lock lock{mutex}; + controller.gyro_overlay_visible = visible; +} + void EmulatedController::SetButtonParam(std::size_t index, Common::ParamPackage param) { if (index >= button_params.size()) { return; From de4b93d147b466b343b69d3b52c7243c2fd78c56 Mon Sep 17 00:00:00 2001 From: Collecting Date: Fri, 31 Oct 2025 05:41:29 +0000 Subject: [PATCH 10/10] feat: Toggle Gyro for Controller Overlay Signed-off-by: Collecting --- src/hid_core/frontend/emulated_controller.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/hid_core/frontend/emulated_controller.h b/src/hid_core/frontend/emulated_controller.h index 85370a736..f51593e54 100644 --- a/src/hid_core/frontend/emulated_controller.h +++ b/src/hid_core/frontend/emulated_controller.h @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -129,6 +130,8 @@ struct ControllerStatus { CameraValues camera_values{}; RingAnalogValue ring_analog_value{}; NfcValues nfc_values{}; + u32 body_color{}; + bool gyro_overlay_visible{}; // Data for HID services HomeButtonState home_button_state{}; @@ -274,6 +277,12 @@ public: // Returns the current mapped motion device Common::ParamPackage GetMotionParam(std::size_t index) const; + // Returns the custom body color for the controller + u32 GetBodyColor() const; + + // Returns whether the gyro overlay is visible within Controller Overlay. + bool IsGyroOverlayVisible() const; + /** * Updates the current mapped button device * @param param ParamPackage with controller data to be mapped @@ -292,6 +301,18 @@ public: */ void SetMotionParam(std::size_t index, Common::ParamPackage param); + /** + * Sets the custom body color for the controller + * @param color The RGB color value to set + */ + void SetBodyColor(u32 color); + + /** + * Sets the visibility of the gyro overlay + * @param visible The visibility state to set + */ + void SetGyroOverlayVisible(bool visible); + /// Auto calibrates the current motion devices void StartMotionCalibration();