diff --git a/src/Ryujinx/UI/Helpers/InputDeviceNameConverter.cs b/src/Ryujinx/UI/Helpers/InputDeviceNameConverter.cs new file mode 100644 index 000000000..44abad47c --- /dev/null +++ b/src/Ryujinx/UI/Helpers/InputDeviceNameConverter.cs @@ -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 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; + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index 7892cac08..bec7b8131 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -91,7 +91,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; } internal ObservableCollection Controllers { get; set; } public AvaloniaList ProfilesList { get; set; } - public AvaloniaList DeviceList { get; set; } public bool UseGlobalConfig; @@ -101,7 +100,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public bool IsKeyboard => !IsController; public bool IsRight { get; set; } public bool IsLeft { get; set; } - public string RevertDeviceId { get; set; } public bool HasLed => (SelectedGamepad.Features & GamepadFeaturesFlag.Led) != 0; public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense"); @@ -165,7 +163,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input LoadDevice(); LoadProfiles(); - RevertDeviceId = Devices[Device].Id; _isLoaded = true; _isChangeTrackingActive = true; OnPropertyChanged(); @@ -177,52 +174,58 @@ namespace Ryujinx.Ava.UI.ViewModels.Input get => _controller; set { + int controllerIndex = value < 0 ? 0 : value; + + if (controllerIndex == _controller) + { + return; + } + MarkAsChanged(); - - _controller = value; - - if (_controller == -1) - { - _controller = 0; - } - - if (Controllers.Count > 0 && _controller < Controllers.Count && _controller > -1) - { - ControllerType controller = Controllers[_controller].Type; - - IsLeft = true; - IsRight = true; - - switch (controller) - { - case ControllerType.Handheld: - ControllerImage = JoyConPairResource; - break; - case ControllerType.ProController: - ControllerImage = ProControllerResource; - break; - case ControllerType.JoyconPair: - ControllerImage = JoyConPairResource; - break; - case ControllerType.JoyconLeft: - ControllerImage = JoyConLeftResource; - IsRight = false; - break; - case ControllerType.JoyconRight: - ControllerImage = JoyConRightResource; - IsLeft = false; - break; - } - - LoadInputDriver(); - LoadProfiles(); - } - - OnPropertyChanged(); - NotifyChanges(); + ApplyControllerSelection(controllerIndex); } } + private void ApplyControllerSelection(int controllerIndex) + { + _controller = controllerIndex; + + if (Controllers.Count > 0 && _controller < Controllers.Count && _controller > -1) + { + ControllerType controller = Controllers[_controller].Type; + + IsLeft = true; + IsRight = true; + + switch (controller) + { + case ControllerType.Handheld: + ControllerImage = JoyConPairResource; + break; + case ControllerType.ProController: + ControllerImage = ProControllerResource; + break; + case ControllerType.JoyconPair: + ControllerImage = JoyConPairResource; + break; + case ControllerType.JoyconLeft: + ControllerImage = JoyConLeftResource; + IsRight = false; + break; + case ControllerType.JoyconRight: + ControllerImage = JoyConRightResource; + IsLeft = false; + break; + } + + LoadInputDriver(); + LoadProfiles(); + } + + OnPropertyChanged(nameof(Controller)); + NotifyChanges(); + } + public string ControllerImage { get => _controllerImage; @@ -257,15 +260,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input get => _device; set { - MarkAsChanged(); - - _device = value < 0 ? 0 : value; - - if (_device >= Devices.Count) + if (value < 0 || value >= Devices.Count) { return; } + MarkAsChanged(); + _device = value; + DeviceType selected = Devices[_device].Type; if (selected != DeviceType.None) @@ -280,10 +282,39 @@ namespace Ryujinx.Ava.UI.ViewModels.Input FindPairedDeviceInConfigFile(); OnPropertyChanged(); + OnPropertyChanged(nameof(SelectedDeviceItem)); NotifyChanges(); } } + public object SelectedDeviceItem + { + get => _device >= 0 && _device < Devices.Count ? Devices[_device] : null; + set + { + if (value is not ValueTuple 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 InputViewModel(UserControl owner, bool useGlobal = false) : this() @@ -328,7 +359,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input Controllers = []; Devices = []; ProfilesList = []; - DeviceList = []; VisualStick = new StickVisualizer(this); ControllerImage = ProControllerResource; @@ -409,7 +439,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input //If tracking is active, then allow changing the modifier if (!IsModified && _isChangeTrackingActive) { - RevertDeviceId = Devices[Device].Id; // Remember the device to undo changes IsModified = true; } } @@ -424,34 +453,54 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public void LoadDevice() { + int deviceIndex = 0; + if (Config == null || Config.Backend == InputBackendType.Invalid) { - Device = 0; + ApplyLoadedDevice(deviceIndex); + return; } - else + + DeviceType type = DeviceType.None; + + if (Config is StandardKeyboardInputConfig) { - DeviceType type = DeviceType.None; - - if (Config is StandardKeyboardInputConfig) - { - type = DeviceType.Keyboard; - } - - if (Config is StandardControllerInputConfig) - { - type = DeviceType.Controller; - } - - (DeviceType Type, string Id, string Name) item = Devices.FirstOrDefault(x => x.Type == type && x.Id == Config.Id); - if (item != default) - { - Device = Devices.ToList().FindIndex(x => x.Id == item.Id); - } - else - { - Device = 0; - } + type = DeviceType.Keyboard; } + + if (Config is StandardControllerInputConfig) + { + type = DeviceType.Controller; + } + + (DeviceType Type, string Id, string Name) item = Devices.FirstOrDefault(x => x.Type == type && x.Id == Config.Id); + + if (item != default) + { + deviceIndex = Devices.ToList().FindIndex(x => x.Id == item.Id); + } + + ApplyLoadedDevice(deviceIndex); + } + + private void ApplyLoadedDevice(int deviceIndex) + { + _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() @@ -569,7 +618,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input { Controllers.Add(new(ControllerType.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeHandheld])); - Controller = 0; + ApplyControllerSelection(0); } else { @@ -587,14 +636,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input // Workaround: set the box to 1 and then 0 if (controllerIndex == 0) { - Controller = 1; + ApplyControllerSelection(1); } - Controller = controllerIndex; + ApplyControllerSelection(controllerIndex); } else { - Controller = 0; + ApplyControllerSelection(0); } } } @@ -622,6 +671,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input 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) { return $"{GetShortGamepadName(gamepad.Name)} ({controllerNumber})"; @@ -642,7 +699,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input lock (Devices) { Devices.Clear(); - DeviceList.Clear(); 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)); - Device = Math.Min(Device, DeviceList.Count - 1); + if (selectedDevice != default) + { + 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() @@ -1024,9 +1091,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input 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; LoadConfiguration(); LoadDevice(); @@ -1046,8 +1110,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input IsModified = false; - RevertDeviceId = Devices[Device].Id; // Remember selected device after saving - List newConfig = []; if (UseGlobalConfig && Program.UseExtraConfig) diff --git a/src/Ryujinx/UI/Views/Input/InputView.axaml b/src/Ryujinx/UI/Views/Input/InputView.axaml index e8e19ab6c..b7ae4fc08 100644 --- a/src/Ryujinx/UI/Views/Input/InputView.axaml +++ b/src/Ryujinx/UI/Views/Input/InputView.axaml @@ -3,6 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" 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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" @@ -161,8 +162,14 @@ Name="DeviceBox" HorizontalAlignment="Stretch" VerticalAlignment="Center" - ItemsSource="{Binding DeviceList}" - SelectedIndex="{Binding Device}" /> + ItemsSource="{Binding Devices}" + SelectedItem="{Binding SelectedDeviceItem, Mode=TwoWay}"> + + + + + +