UI: Add the ability to change a DualSense/DualShock 4's LED color.

Not functional yet. This is the UI & persistence side of #572.
This commit is contained in:
Evan Husted 2025-01-22 19:53:39 -06:00
parent 069f630776
commit c03cd50fa3
11 changed files with 125 additions and 14 deletions

View File

@ -78,5 +78,10 @@ namespace Ryujinx.Common.Configuration.Hid.Controller
/// Controller Rumble Settings /// Controller Rumble Settings
/// </summary> /// </summary>
public RumbleConfigController Rumble { get; set; } public RumbleConfigController Rumble { get; set; }
/// <summary>
/// Controller LED Settings
/// </summary>
public LedConfigController Led { get; set; }
} }
} }

View File

@ -0,0 +1,15 @@
namespace Ryujinx.Common.Configuration.Hid.Controller
{
public class LedConfigController
{
/// <summary>
/// Packed RGB int of the color
/// </summary>
public uint LedColor { get; set; }
/// <summary>
/// Enable LED color changing by the emulator
/// </summary>
public bool EnableLed { get; set; }
}
}

View File

@ -1,6 +1,7 @@
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using SDL2;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
@ -118,6 +119,11 @@ namespace Ryujinx.Input.SDL2
result |= GamepadFeaturesFlag.Rumble; result |= GamepadFeaturesFlag.Rumble;
} }
if (SDL_GameControllerHasLED(_gamepadHandle) == SDL_bool.SDL_TRUE)
{
result |= GamepadFeaturesFlag.Led;
}
return result; return result;
} }

View File

@ -24,5 +24,10 @@ namespace Ryujinx.Input
/// <remarks>Also named sixaxis</remarks> /// <remarks>Also named sixaxis</remarks>
/// </summary> /// </summary>
Motion, Motion,
/// <summary>
/// The LED on the back of modern PlayStation controllers (DualSense &amp; DualShock 4).
/// </summary>
Led,
} }
} }

View File

@ -1,3 +1,4 @@
using Avalonia.Media;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
@ -387,6 +388,30 @@ namespace Ryujinx.Ava.UI.Models.Input
} }
} }
private bool _enableLedChanging;
public bool EnableLedChanging
{
get => _enableLedChanging;
set
{
_enableLedChanging = value;
OnPropertyChanged();
}
}
private Color _ledColor;
public Color LedColor
{
get => _ledColor;
set
{
_ledColor = value;
OnPropertyChanged();
}
}
private bool _enableMotion; private bool _enableMotion;
public bool EnableMotion public bool EnableMotion
{ {
@ -483,12 +508,23 @@ namespace Ryujinx.Ava.UI.Models.Input
WeakRumble = controllerInput.Rumble.WeakRumble; WeakRumble = controllerInput.Rumble.WeakRumble;
StrongRumble = controllerInput.Rumble.StrongRumble; StrongRumble = controllerInput.Rumble.StrongRumble;
} }
if (controllerInput.Led != null)
{
EnableLedChanging = controllerInput.Led.EnableLed;
uint rawColor = controllerInput.Led.LedColor;
byte alpha = (byte)(rawColor >> 24);
byte red = (byte)(rawColor >> 16);
byte green = (byte)(rawColor >> 8);
byte blue = (byte)(rawColor % 256);
LedColor = new Color(alpha, red, green, blue);
}
} }
} }
public InputConfig GetConfig() public InputConfig GetConfig()
{ {
var config = new StandardControllerInputConfig StandardControllerInputConfig config = new()
{ {
Id = Id, Id = Id,
Backend = InputBackendType.GamepadSDL2, Backend = InputBackendType.GamepadSDL2,
@ -540,6 +576,11 @@ namespace Ryujinx.Ava.UI.Models.Input
WeakRumble = WeakRumble, WeakRumble = WeakRumble,
StrongRumble = StrongRumble, StrongRumble = StrongRumble,
}, },
Led = new LedConfigController
{
EnableLed = EnableLedChanging,
LedColor = LedColor.ToUInt32()
},
Version = InputConfig.CurrentVersion, Version = InputConfig.CurrentVersion,
DeadzoneLeft = DeadzoneLeft, DeadzoneLeft = DeadzoneLeft,
DeadzoneRight = DeadzoneRight, DeadzoneRight = DeadzoneRight,

View File

@ -37,7 +37,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
[ObservableProperty] private SvgImage _image; [ObservableProperty] private SvgImage _image;
public readonly InputViewModel ParentModel; public InputViewModel ParentModel { get; }
public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config) public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config)
{ {

View File

@ -69,6 +69,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool IsRight { get; set; } public bool IsRight { get; set; }
public bool IsLeft { get; set; } public bool IsLeft { get; set; }
public bool HasLed => SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led);
public bool IsModified { get; set; } public bool IsModified { get; set; }
public event Action NotifyChangesEvent; public event Action NotifyChangesEvent;

View File

@ -4,6 +4,7 @@
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup" xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
@ -486,6 +487,37 @@
</Button> </Button>
</Grid> </Grid>
</Border> </Border>
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
HorizontalAlignment="Stretch"
Margin="0,-1,0,0">
<Grid IsVisible="{Binding ParentModel.HasLed}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox
Margin="10"
MinWidth="0"
Grid.Column="0"
IsChecked="{Binding Config.EnableLedChanging, Mode=TwoWay}">
<TextBlock Text="Custom LED color" />
</CheckBox>
<ui:ColorPickerButton
Grid.Column="1"
Margin="10"
IsMoreButtonVisible="False"
UseColorPalette="False"
UseColorTriangle="False"
UseColorWheel="False"
ShowAcceptDismissButtons="False"
IsAlphaEnabled="False"
Color="{Binding Config.LedColor, Mode=TwoWay}">
</ui:ColorPickerButton>
</Grid>
</Border>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
<!-- Right Controls --> <!-- Right Controls -->

View File

@ -4,14 +4,11 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using DiscordRPC;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels.Input; using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Logging;
using Ryujinx.Input; using Ryujinx.Input;
using Ryujinx.Input.Assigner; using Ryujinx.Input.Assigner;
using System;
using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
namespace Ryujinx.Ava.UI.Views.Input namespace Ryujinx.Ava.UI.Views.Input

View File

@ -17,7 +17,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// <summary> /// <summary>
/// The current version of the file format /// The current version of the file format
/// </summary> /// </summary>
public const int CurrentVersion = 60; public const int CurrentVersion = 61;
/// <summary> /// <summary>
/// Version of the configuration file format /// Version of the configuration file format

View File

@ -263,15 +263,12 @@ namespace Ryujinx.Ava.Utilities.Configuration
}), }),
(30, static cff => (30, static cff =>
{ {
foreach (InputConfig config in cff.InputConfig) foreach (StandardControllerInputConfig config in cff.InputConfig.OfType<StandardControllerInputConfig>())
{ {
if (config is StandardControllerInputConfig controllerConfig) config.Rumble = new RumbleConfigController
{ {
controllerConfig.Rumble = new RumbleConfigController EnableRumble = false, StrongRumble = 1f, WeakRumble = 1f,
{ };
EnableRumble = false, StrongRumble = 1f, WeakRumble = 1f,
};
}
} }
}), }),
(31, static cff => cff.BackendThreading = BackendThreading.Auto), (31, static cff => cff.BackendThreading = BackendThreading.Auto),
@ -416,7 +413,18 @@ namespace Ryujinx.Ava.Utilities.Configuration
// so as a compromise users who want to use it will simply need to re-enable it once after updating. // so as a compromise users who want to use it will simply need to re-enable it once after updating.
cff.IgnoreApplet = false; cff.IgnoreApplet = false;
}), }),
(60, static cff => cff.StartNoUI = false) (60, static cff => cff.StartNoUI = false),
(61, static cff =>
{
foreach (StandardControllerInputConfig config in cff.InputConfig.OfType<StandardControllerInputConfig>())
{
config.Led = new LedConfigController
{
EnableLed = false,
LedColor = 328189
};
}
})
); );
} }
} }