Fix input settings device selection state

This commit is contained in:
Babib3l
2026-03-22 18:15:12 +01:00
parent ca015200f1
commit c8367335d4
3 changed files with 186 additions and 89 deletions

View File

@@ -0,0 +1,28 @@
using Avalonia.Data.Converters;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.UI.Models;
using System;
using System.Globalization;
namespace Ryujinx.Ava.UI.Helpers
{
internal class InputDeviceNameConverter : MarkupExtension, IValueConverter
{
public static readonly InputDeviceNameConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is ValueTuple<DeviceType, string, string> device ? device.Item3 : string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Instance;
}
}
}

View File

@@ -91,7 +91,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; } public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; }
internal ObservableCollection<ControllerModel> Controllers { get; set; } internal ObservableCollection<ControllerModel> Controllers { get; set; }
public AvaloniaList<string> ProfilesList { get; set; } public AvaloniaList<string> ProfilesList { get; set; }
public AvaloniaList<string> DeviceList { get; set; }
public bool UseGlobalConfig; public bool UseGlobalConfig;
@@ -101,7 +100,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool IsKeyboard => !IsController; public bool IsKeyboard => !IsController;
public bool IsRight { get; set; } public bool IsRight { get; set; }
public bool IsLeft { get; set; } public bool IsLeft { get; set; }
public string RevertDeviceId { get; set; }
public bool HasLed => (SelectedGamepad.Features & GamepadFeaturesFlag.Led) != 0; public bool HasLed => (SelectedGamepad.Features & GamepadFeaturesFlag.Led) != 0;
public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense"); public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense");
@@ -165,7 +163,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
LoadDevice(); LoadDevice();
LoadProfiles(); LoadProfiles();
RevertDeviceId = Devices[Device].Id;
_isLoaded = true; _isLoaded = true;
_isChangeTrackingActive = true; _isChangeTrackingActive = true;
OnPropertyChanged(); OnPropertyChanged();
@@ -177,15 +174,22 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
get => _controller; get => _controller;
set set
{ {
MarkAsChanged(); int controllerIndex = value < 0 ? 0 : value;
_controller = value; if (controllerIndex == _controller)
if (_controller == -1)
{ {
_controller = 0; return;
} }
MarkAsChanged();
ApplyControllerSelection(controllerIndex);
}
}
private void ApplyControllerSelection(int controllerIndex)
{
_controller = controllerIndex;
if (Controllers.Count > 0 && _controller < Controllers.Count && _controller > -1) if (Controllers.Count > 0 && _controller < Controllers.Count && _controller > -1)
{ {
ControllerType controller = Controllers[_controller].Type; ControllerType controller = Controllers[_controller].Type;
@@ -218,10 +222,9 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
LoadProfiles(); LoadProfiles();
} }
OnPropertyChanged(); OnPropertyChanged(nameof(Controller));
NotifyChanges(); NotifyChanges();
} }
}
public string ControllerImage public string ControllerImage
{ {
@@ -257,15 +260,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
get => _device; get => _device;
set set
{ {
MarkAsChanged(); if (value < 0 || value >= Devices.Count)
_device = value < 0 ? 0 : value;
if (_device >= Devices.Count)
{ {
return; return;
} }
MarkAsChanged();
_device = value;
DeviceType selected = Devices[_device].Type; DeviceType selected = Devices[_device].Type;
if (selected != DeviceType.None) if (selected != DeviceType.None)
@@ -280,10 +282,39 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
FindPairedDeviceInConfigFile(); FindPairedDeviceInConfigFile();
OnPropertyChanged(); OnPropertyChanged();
OnPropertyChanged(nameof(SelectedDeviceItem));
NotifyChanges(); NotifyChanges();
} }
} }
public object SelectedDeviceItem
{
get => _device >= 0 && _device < Devices.Count ? Devices[_device] : null;
set
{
if (value is not ValueTuple<DeviceType, string, string> selectedDevice)
{
return;
}
int deviceIndex = Devices.ToList().FindIndex(device =>
device.Type == selectedDevice.Item1 &&
device.Id == selectedDevice.Item2);
if (deviceIndex < 0)
{
return;
}
if (deviceIndex == _device)
{
return;
}
Device = deviceIndex;
}
}
public InputConfig Config { get; set; } public InputConfig Config { get; set; }
public InputViewModel(UserControl owner, bool useGlobal = false) : this() public InputViewModel(UserControl owner, bool useGlobal = false) : this()
@@ -328,7 +359,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
Controllers = []; Controllers = [];
Devices = []; Devices = [];
ProfilesList = []; ProfilesList = [];
DeviceList = [];
VisualStick = new StickVisualizer(this); VisualStick = new StickVisualizer(this);
ControllerImage = ProControllerResource; ControllerImage = ProControllerResource;
@@ -409,7 +439,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
//If tracking is active, then allow changing the modifier //If tracking is active, then allow changing the modifier
if (!IsModified && _isChangeTrackingActive) if (!IsModified && _isChangeTrackingActive)
{ {
RevertDeviceId = Devices[Device].Id; // Remember the device to undo changes
IsModified = true; IsModified = true;
} }
} }
@@ -424,12 +453,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public void LoadDevice() public void LoadDevice()
{ {
int deviceIndex = 0;
if (Config == null || Config.Backend == InputBackendType.Invalid) if (Config == null || Config.Backend == InputBackendType.Invalid)
{ {
Device = 0; ApplyLoadedDevice(deviceIndex);
return;
} }
else
{
DeviceType type = DeviceType.None; DeviceType type = DeviceType.None;
if (Config is StandardKeyboardInputConfig) if (Config is StandardKeyboardInputConfig)
@@ -443,15 +474,33 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
} }
(DeviceType Type, string Id, string Name) item = Devices.FirstOrDefault(x => x.Type == type && x.Id == Config.Id); (DeviceType Type, string Id, string Name) item = Devices.FirstOrDefault(x => x.Type == type && x.Id == Config.Id);
if (item != default) if (item != default)
{ {
Device = Devices.ToList().FindIndex(x => x.Id == item.Id); deviceIndex = Devices.ToList().FindIndex(x => x.Id == item.Id);
} }
else
ApplyLoadedDevice(deviceIndex);
}
private void ApplyLoadedDevice(int deviceIndex)
{ {
Device = 0; _device = deviceIndex is >= 0 and < int.MaxValue ? deviceIndex : 0;
if (_device >= Devices.Count)
{
_device = 0;
} }
if (_device > 0 && Devices[_device].Type != DeviceType.None)
{
LoadControllers();
} }
FindPairedDeviceInConfigFile();
OnPropertyChanged(nameof(Device));
OnPropertyChanged(nameof(SelectedDeviceItem));
NotifyChanges();
} }
private void LoadInputDriver() private void LoadInputDriver()
@@ -569,7 +618,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
Controllers.Add(new(ControllerType.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeHandheld])); Controllers.Add(new(ControllerType.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeHandheld]));
Controller = 0; ApplyControllerSelection(0);
} }
else else
{ {
@@ -587,14 +636,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
// Workaround: set the box to 1 and then 0 // Workaround: set the box to 1 and then 0
if (controllerIndex == 0) if (controllerIndex == 0)
{ {
Controller = 1; ApplyControllerSelection(1);
} }
Controller = controllerIndex; ApplyControllerSelection(controllerIndex);
} }
else else
{ {
Controller = 0; ApplyControllerSelection(0);
} }
} }
} }
@@ -622,6 +671,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public void LoadDevices() public void LoadDevices()
{ {
int selectedDeviceIndex = 0;
(DeviceType Type, string Id, string Name) selectedDevice = default;
if (_device >= 0 && _device < Devices.Count)
{
selectedDevice = Devices[_device];
}
string GetGamepadName(IGamepad gamepad, int controllerNumber) string GetGamepadName(IGamepad gamepad, int controllerNumber)
{ {
return $"{GetShortGamepadName(gamepad.Name)} ({controllerNumber})"; return $"{GetShortGamepadName(gamepad.Name)} ({controllerNumber})";
@@ -642,7 +699,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
lock (Devices) lock (Devices)
{ {
Devices.Clear(); Devices.Clear();
DeviceList.Clear();
Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance[LocaleKeys.ControllerSettingsDeviceDisabled])); Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance[LocaleKeys.ControllerSettingsDeviceDisabled]));
@@ -668,9 +724,20 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
} }
} }
DeviceList.AddRange(Devices.Select(x => x.Name)); if (selectedDevice != default)
Device = Math.Min(Device, DeviceList.Count - 1); {
selectedDeviceIndex = Devices.ToList().FindIndex(device =>
device.Type == selectedDevice.Type &&
device.Id == selectedDevice.Id);
} }
if (selectedDeviceIndex < 0)
{
selectedDeviceIndex = Math.Clamp(_device, 0, Devices.Count - 1);
}
}
ApplyLoadedDevice(selectedDeviceIndex);
} }
private string GetProfileBasePath() private string GetProfileBasePath()
@@ -1024,9 +1091,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public void RevertChanges() public void RevertChanges()
{ {
LoadConfiguration(); // configuration preload is required if the paired gamepad was disconnected but was changed to another gamepad
Device = Devices.ToList().FindIndex(d => d.Id == RevertDeviceId);
_isLoaded = false; _isLoaded = false;
LoadConfiguration(); LoadConfiguration();
LoadDevice(); LoadDevice();
@@ -1046,8 +1110,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
IsModified = false; IsModified = false;
RevertDeviceId = Devices[Device].Id; // Remember selected device after saving
List<InputConfig> newConfig = []; List<InputConfig> newConfig = [];
if (UseGlobalConfig && Program.UseExtraConfig) if (UseGlobalConfig && Program.UseExtraConfig)

View File

@@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup" xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
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:models="clr-namespace:Ryujinx.Ava.UI.Models" xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
@@ -161,8 +162,14 @@
Name="DeviceBox" Name="DeviceBox"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center" VerticalAlignment="Center"
ItemsSource="{Binding DeviceList}" ItemsSource="{Binding Devices}"
SelectedIndex="{Binding Device}" /> SelectedItem="{Binding SelectedDeviceItem, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ., Converter={x:Static helpers:InputDeviceNameConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button <Button
Grid.Column="2" Grid.Column="2"
MinWidth="0" MinWidth="0"