Merge pull request 'feat(input): Add Controller Color Customization & Gyro Toggle for Controller Overlay' (#5) from feat/controller-custom-color into main

Reviewed-on: https://git.citron-emu.org/Citron/Emulator/pulls/5
This commit is contained in:
Collecting
2025-10-31 05:44:21 +00:00
10 changed files with 185 additions and 22 deletions

View File

@@ -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 <algorithm>
@@ -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<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>
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);
}

View File

@@ -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 <vector>
#include <QWidget>
#include <QColorDialog>
#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::ConfigureInputPlayer> ui;
std::size_t player_index;

View File

@@ -2137,6 +2137,53 @@
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_Color">
<item>
<spacer name="horizontalSpacer_Color1">
<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="buttonChooseColor">
<property name="text">
<string>Color</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonToggleGyro">
<property name="text">
<string>Toggle Gyro Display</string>
</property>
<property name="toolTip">
<string>Toggles the Gyro Indicator on and off in the Controller Overlay.</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_Color2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="miscButtons">
<property name="spacing">

View File

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

View File

@@ -50,6 +50,9 @@ public:
// Updates input on scheduled interval
void UpdateInput();
// Gyro Visibility
bool gyro_visible = true;
protected:
void paintEvent(QPaintEvent* event) override;

View File

@@ -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]),

View File

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

View File

@@ -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

View File

@@ -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 <algorithm>
@@ -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;

View File

@@ -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();