From 139a4b1fe784ab4f01b45a6e3e18d286281d452d Mon Sep 17 00:00:00 2001 From: Isaac Marovitz Date: Wed, 17 May 2023 10:08:55 -0400 Subject: [PATCH] Slow and steady --- .../UI/Models/InputConfiguration.cs | 456 --------- .../UI/ViewModels/ControllerInputViewModel.cs | 864 +---------------- .../UI/ViewModels/InputViewModel.cs | 890 ++++++++++++++++++ .../UI/ViewModels/KeyboardInputViewModel.cs | 64 ++ .../UI/Views/Input/ControllerInputView.axaml | 469 +-------- .../Views/Input/ControllerInputView.axaml.cs | 172 +--- .../UI/Views/Input/InputView.axaml | 217 +++++ .../UI/Views/Input/InputView.axaml.cs | 180 ++++ .../UI/Views/Input/KeyboardInputView.axaml | 678 +++++++++++++ .../UI/Views/Input/KeyboardInputView.axaml.cs | 19 + .../UI/Views/Settings/SettingsInputView.axaml | 2 +- 11 files changed, 2125 insertions(+), 1886 deletions(-) delete mode 100644 src/Ryujinx.Ava/UI/Models/InputConfiguration.cs create mode 100644 src/Ryujinx.Ava/UI/ViewModels/InputViewModel.cs create mode 100644 src/Ryujinx.Ava/UI/ViewModels/KeyboardInputViewModel.cs create mode 100644 src/Ryujinx.Ava/UI/Views/Input/InputView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/Input/InputView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/Input/KeyboardInputView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/Input/KeyboardInputView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Models/InputConfiguration.cs b/src/Ryujinx.Ava/UI/Models/InputConfiguration.cs deleted file mode 100644 index f1352c6d8..000000000 --- a/src/Ryujinx.Ava/UI/Models/InputConfiguration.cs +++ /dev/null @@ -1,456 +0,0 @@ -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Common.Configuration.Hid; -using Ryujinx.Common.Configuration.Hid.Controller; -using Ryujinx.Common.Configuration.Hid.Controller.Motion; -using Ryujinx.Common.Configuration.Hid.Keyboard; -using System; - -namespace Ryujinx.Ava.UI.Models -{ - internal class InputConfiguration : BaseModel - { - private float _deadzoneRight; - private float _triggerThreshold; - private float _deadzoneLeft; - private double _gyroDeadzone; - private int _sensitivity; - private bool _enableMotion; - private float _weakRumble; - private float _strongRumble; - private float _rangeLeft; - private float _rangeRight; - - public InputBackendType Backend { get; set; } - - /// - /// Controller id - /// - public string Id { get; set; } - - /// - /// Controller's Type - /// - public ControllerType ControllerType { get; set; } - - /// - /// Player's Index for the controller - /// - public PlayerIndex PlayerIndex { get; set; } - - public TStick LeftJoystick { get; set; } - public bool LeftInvertStickX { get; set; } - public bool LeftInvertStickY { get; set; } - public bool RightRotate90 { get; set; } - public TKey LeftControllerStickButton { get; set; } - - public TStick RightJoystick { get; set; } - public bool RightInvertStickX { get; set; } - public bool RightInvertStickY { get; set; } - public bool LeftRotate90 { get; set; } - public TKey RightControllerStickButton { get; set; } - - public float DeadzoneLeft - { - get => _deadzoneLeft; - set - { - _deadzoneLeft = MathF.Round(value, 3); - - OnPropertyChanged(); - } - } - - public float RangeLeft - { - get => _rangeLeft; - set - { - _rangeLeft = MathF.Round(value, 3); - - OnPropertyChanged(); - } - } - - public float DeadzoneRight - { - get => _deadzoneRight; - set - { - _deadzoneRight = MathF.Round(value, 3); - - OnPropertyChanged(); - } - } - - public float RangeRight - { - get => _rangeRight; - set - { - _rangeRight = MathF.Round(value, 3); - - OnPropertyChanged(); - } - } - - public float TriggerThreshold - { - get => _triggerThreshold; - set - { - _triggerThreshold = MathF.Round(value, 3); - - OnPropertyChanged(); - } - } - - public MotionInputBackendType MotionBackend { get; set; } - - public TKey ButtonMinus { get; set; } - public TKey ButtonL { get; set; } - public TKey ButtonZl { get; set; } - public TKey LeftButtonSl { get; set; } - public TKey LeftButtonSr { get; set; } - public TKey DpadUp { get; set; } - public TKey DpadDown { get; set; } - public TKey DpadLeft { get; set; } - public TKey DpadRight { get; set; } - - public TKey ButtonPlus { get; set; } - public TKey ButtonR { get; set; } - public TKey ButtonZr { get; set; } - public TKey RightButtonSl { get; set; } - public TKey RightButtonSr { get; set; } - public TKey ButtonX { get; set; } - public TKey ButtonB { get; set; } - public TKey ButtonY { get; set; } - public TKey ButtonA { get; set; } - - public TKey LeftStickUp { get; set; } - public TKey LeftStickDown { get; set; } - public TKey LeftStickLeft { get; set; } - public TKey LeftStickRight { get; set; } - public TKey LeftKeyboardStickButton { get; set; } - - public TKey RightStickUp { get; set; } - public TKey RightStickDown { get; set; } - public TKey RightStickLeft { get; set; } - public TKey RightStickRight { get; set; } - public TKey RightKeyboardStickButton { get; set; } - - public int Sensitivity - { - get => _sensitivity; - set - { - _sensitivity = value; - - OnPropertyChanged(); - } - } - - public double GyroDeadzone - { - get => _gyroDeadzone; - set - { - _gyroDeadzone = Math.Round(value, 3); - - OnPropertyChanged(); - } - } - - public bool EnableMotion - { - get => _enableMotion; set - { - _enableMotion = value; - - OnPropertyChanged(); - } - } - - public bool EnableCemuHookMotion { get; set; } - public int Slot { get; set; } - public int AltSlot { get; set; } - public bool MirrorInput { get; set; } - public string DsuServerHost { get; set; } - public int DsuServerPort { get; set; } - - public bool EnableRumble { get; set; } - public float WeakRumble - { - get => _weakRumble; set - { - _weakRumble = value; - - OnPropertyChanged(); - } - } - public float StrongRumble - { - get => _strongRumble; set - { - _strongRumble = value; - - OnPropertyChanged(); - } - } - - public InputConfiguration(InputConfig config) - { - if (config != null) - { - Backend = config.Backend; - Id = config.Id; - ControllerType = config.ControllerType; - PlayerIndex = config.PlayerIndex; - - if (config is StandardKeyboardInputConfig keyboardConfig) - { - LeftStickUp = (TKey)(object)keyboardConfig.LeftJoyconStick.StickUp; - LeftStickDown = (TKey)(object)keyboardConfig.LeftJoyconStick.StickDown; - LeftStickLeft = (TKey)(object)keyboardConfig.LeftJoyconStick.StickLeft; - LeftStickRight = (TKey)(object)keyboardConfig.LeftJoyconStick.StickRight; - LeftKeyboardStickButton = (TKey)(object)keyboardConfig.LeftJoyconStick.StickButton; - - RightStickUp = (TKey)(object)keyboardConfig.RightJoyconStick.StickUp; - RightStickDown = (TKey)(object)keyboardConfig.RightJoyconStick.StickDown; - RightStickLeft = (TKey)(object)keyboardConfig.RightJoyconStick.StickLeft; - RightStickRight = (TKey)(object)keyboardConfig.RightJoyconStick.StickRight; - RightKeyboardStickButton = (TKey)(object)keyboardConfig.RightJoyconStick.StickButton; - - ButtonA = (TKey)(object)keyboardConfig.RightJoycon.ButtonA; - ButtonB = (TKey)(object)keyboardConfig.RightJoycon.ButtonB; - ButtonX = (TKey)(object)keyboardConfig.RightJoycon.ButtonX; - ButtonY = (TKey)(object)keyboardConfig.RightJoycon.ButtonY; - ButtonR = (TKey)(object)keyboardConfig.RightJoycon.ButtonR; - RightButtonSl = (TKey)(object)keyboardConfig.RightJoycon.ButtonSl; - RightButtonSr = (TKey)(object)keyboardConfig.RightJoycon.ButtonSr; - ButtonZr = (TKey)(object)keyboardConfig.RightJoycon.ButtonZr; - ButtonPlus = (TKey)(object)keyboardConfig.RightJoycon.ButtonPlus; - - DpadUp = (TKey)(object)keyboardConfig.LeftJoycon.DpadUp; - DpadDown = (TKey)(object)keyboardConfig.LeftJoycon.DpadDown; - DpadLeft = (TKey)(object)keyboardConfig.LeftJoycon.DpadLeft; - DpadRight = (TKey)(object)keyboardConfig.LeftJoycon.DpadRight; - ButtonMinus = (TKey)(object)keyboardConfig.LeftJoycon.ButtonMinus; - LeftButtonSl = (TKey)(object)keyboardConfig.LeftJoycon.ButtonSl; - LeftButtonSr = (TKey)(object)keyboardConfig.LeftJoycon.ButtonSr; - ButtonZl = (TKey)(object)keyboardConfig.LeftJoycon.ButtonZl; - ButtonL = (TKey)(object)keyboardConfig.LeftJoycon.ButtonL; - } - else if (config is StandardControllerInputConfig controllerConfig) - { - LeftJoystick = (TStick)(object)controllerConfig.LeftJoyconStick.Joystick; - LeftInvertStickX = controllerConfig.LeftJoyconStick.InvertStickX; - LeftInvertStickY = controllerConfig.LeftJoyconStick.InvertStickY; - LeftRotate90 = controllerConfig.LeftJoyconStick.Rotate90CW; - LeftControllerStickButton = (TKey)(object)controllerConfig.LeftJoyconStick.StickButton; - - RightJoystick = (TStick)(object)controllerConfig.RightJoyconStick.Joystick; - RightInvertStickX = controllerConfig.RightJoyconStick.InvertStickX; - RightInvertStickY = controllerConfig.RightJoyconStick.InvertStickY; - RightRotate90 = controllerConfig.RightJoyconStick.Rotate90CW; - RightControllerStickButton = (TKey)(object)controllerConfig.RightJoyconStick.StickButton; - - ButtonA = (TKey)(object)controllerConfig.RightJoycon.ButtonA; - ButtonB = (TKey)(object)controllerConfig.RightJoycon.ButtonB; - ButtonX = (TKey)(object)controllerConfig.RightJoycon.ButtonX; - ButtonY = (TKey)(object)controllerConfig.RightJoycon.ButtonY; - ButtonR = (TKey)(object)controllerConfig.RightJoycon.ButtonR; - RightButtonSl = (TKey)(object)controllerConfig.RightJoycon.ButtonSl; - RightButtonSr = (TKey)(object)controllerConfig.RightJoycon.ButtonSr; - ButtonZr = (TKey)(object)controllerConfig.RightJoycon.ButtonZr; - ButtonPlus = (TKey)(object)controllerConfig.RightJoycon.ButtonPlus; - - DpadUp = (TKey)(object)controllerConfig.LeftJoycon.DpadUp; - DpadDown = (TKey)(object)controllerConfig.LeftJoycon.DpadDown; - DpadLeft = (TKey)(object)controllerConfig.LeftJoycon.DpadLeft; - DpadRight = (TKey)(object)controllerConfig.LeftJoycon.DpadRight; - ButtonMinus = (TKey)(object)controllerConfig.LeftJoycon.ButtonMinus; - LeftButtonSl = (TKey)(object)controllerConfig.LeftJoycon.ButtonSl; - LeftButtonSr = (TKey)(object)controllerConfig.LeftJoycon.ButtonSr; - ButtonZl = (TKey)(object)controllerConfig.LeftJoycon.ButtonZl; - ButtonL = (TKey)(object)controllerConfig.LeftJoycon.ButtonL; - - DeadzoneLeft = controllerConfig.DeadzoneLeft; - DeadzoneRight = controllerConfig.DeadzoneRight; - RangeLeft = controllerConfig.RangeLeft; - RangeRight = controllerConfig.RangeRight; - TriggerThreshold = controllerConfig.TriggerThreshold; - - if (controllerConfig.Motion != null) - { - EnableMotion = controllerConfig.Motion.EnableMotion; - MotionBackend = controllerConfig.Motion.MotionBackend; - GyroDeadzone = controllerConfig.Motion.GyroDeadzone; - Sensitivity = controllerConfig.Motion.Sensitivity; - - if (controllerConfig.Motion is CemuHookMotionConfigController cemuHook) - { - EnableCemuHookMotion = true; - DsuServerHost = cemuHook.DsuServerHost; - DsuServerPort = cemuHook.DsuServerPort; - Slot = cemuHook.Slot; - AltSlot = cemuHook.AltSlot; - MirrorInput = cemuHook.MirrorInput; - } - - if (controllerConfig.Rumble != null) - { - EnableRumble = controllerConfig.Rumble.EnableRumble; - WeakRumble = controllerConfig.Rumble.WeakRumble; - StrongRumble = controllerConfig.Rumble.StrongRumble; - } - } - } - } - } - - public InputConfiguration() - { - } - - public InputConfig GetConfig() - { - if (Backend == InputBackendType.WindowKeyboard) - { - return new StandardKeyboardInputConfig - { - Id = Id, - Backend = Backend, - PlayerIndex = PlayerIndex, - ControllerType = ControllerType, - LeftJoycon = new LeftJoyconCommonConfig - { - DpadUp = (Key)(object)DpadUp, - DpadDown = (Key)(object)DpadDown, - DpadLeft = (Key)(object)DpadLeft, - DpadRight = (Key)(object)DpadRight, - ButtonL = (Key)(object)ButtonL, - ButtonZl = (Key)(object)ButtonZl, - ButtonSl = (Key)(object)LeftButtonSl, - ButtonSr = (Key)(object)LeftButtonSr, - ButtonMinus = (Key)(object)ButtonMinus, - }, - RightJoycon = new RightJoyconCommonConfig - { - ButtonA = (Key)(object)ButtonA, - ButtonB = (Key)(object)ButtonB, - ButtonX = (Key)(object)ButtonX, - ButtonY = (Key)(object)ButtonY, - ButtonPlus = (Key)(object)ButtonPlus, - ButtonSl = (Key)(object)RightButtonSl, - ButtonSr = (Key)(object)RightButtonSr, - ButtonR = (Key)(object)ButtonR, - ButtonZr = (Key)(object)ButtonZr, - }, - LeftJoyconStick = new JoyconConfigKeyboardStick - { - StickUp = (Key)(object)LeftStickUp, - StickDown = (Key)(object)LeftStickDown, - StickRight = (Key)(object)LeftStickRight, - StickLeft = (Key)(object)LeftStickLeft, - StickButton = (Key)(object)LeftKeyboardStickButton, - }, - RightJoyconStick = new JoyconConfigKeyboardStick - { - StickUp = (Key)(object)RightStickUp, - StickDown = (Key)(object)RightStickDown, - StickLeft = (Key)(object)RightStickLeft, - StickRight = (Key)(object)RightStickRight, - StickButton = (Key)(object)RightKeyboardStickButton, - }, - Version = InputConfig.CurrentVersion, - }; - - } - - if (Backend == InputBackendType.GamepadSDL2) - { - var config = new StandardControllerInputConfig - { - Id = Id, - Backend = Backend, - PlayerIndex = PlayerIndex, - ControllerType = ControllerType, - LeftJoycon = new LeftJoyconCommonConfig - { - DpadUp = (GamepadInputId)(object)DpadUp, - DpadDown = (GamepadInputId)(object)DpadDown, - DpadLeft = (GamepadInputId)(object)DpadLeft, - DpadRight = (GamepadInputId)(object)DpadRight, - ButtonL = (GamepadInputId)(object)ButtonL, - ButtonZl = (GamepadInputId)(object)ButtonZl, - ButtonSl = (GamepadInputId)(object)LeftButtonSl, - ButtonSr = (GamepadInputId)(object)LeftButtonSr, - ButtonMinus = (GamepadInputId)(object)ButtonMinus, - }, - RightJoycon = new RightJoyconCommonConfig - { - ButtonA = (GamepadInputId)(object)ButtonA, - ButtonB = (GamepadInputId)(object)ButtonB, - ButtonX = (GamepadInputId)(object)ButtonX, - ButtonY = (GamepadInputId)(object)ButtonY, - ButtonPlus = (GamepadInputId)(object)ButtonPlus, - ButtonSl = (GamepadInputId)(object)RightButtonSl, - ButtonSr = (GamepadInputId)(object)RightButtonSr, - ButtonR = (GamepadInputId)(object)ButtonR, - ButtonZr = (GamepadInputId)(object)ButtonZr, - }, - LeftJoyconStick = new JoyconConfigControllerStick - { - Joystick = (StickInputId)(object)LeftJoystick, - InvertStickX = LeftInvertStickX, - InvertStickY = LeftInvertStickY, - Rotate90CW = LeftRotate90, - StickButton = (GamepadInputId)(object)LeftControllerStickButton, - }, - RightJoyconStick = new JoyconConfigControllerStick - { - Joystick = (StickInputId)(object)RightJoystick, - InvertStickX = RightInvertStickX, - InvertStickY = RightInvertStickY, - Rotate90CW = RightRotate90, - StickButton = (GamepadInputId)(object)RightControllerStickButton, - }, - Rumble = new RumbleConfigController - { - EnableRumble = EnableRumble, - WeakRumble = WeakRumble, - StrongRumble = StrongRumble, - }, - Version = InputConfig.CurrentVersion, - DeadzoneLeft = DeadzoneLeft, - DeadzoneRight = DeadzoneRight, - RangeLeft = RangeLeft, - RangeRight = RangeRight, - TriggerThreshold = TriggerThreshold, - Motion = EnableCemuHookMotion - ? new CemuHookMotionConfigController - { - DsuServerHost = DsuServerHost, - DsuServerPort = DsuServerPort, - Slot = Slot, - AltSlot = AltSlot, - MirrorInput = MirrorInput, - MotionBackend = MotionInputBackendType.CemuHook, - } - : new StandardMotionConfigController - { - MotionBackend = MotionInputBackendType.GamepadDriver, - }, - }; - - config.Motion.Sensitivity = Sensitivity; - config.Motion.EnableMotion = EnableMotion; - config.Motion.GyroDeadzone = GyroDeadzone; - - return config; - } - - return null; - } - } -} diff --git a/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs index 71ad2c127..11b555db6 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs @@ -1,325 +1,64 @@ -using Avalonia; -using Avalonia.Collections; -using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Svg.Skia; -using Avalonia.Threading; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.Input; -using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Views.Input; -using Ryujinx.Ava.UI.Windows; -using Ryujinx.Common; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Configuration.Hid; -using Ryujinx.Common.Configuration.Hid.Controller; -using Ryujinx.Common.Configuration.Hid.Controller.Motion; -using Ryujinx.Common.Configuration.Hid.Keyboard; -using Ryujinx.Common.Logging; -using Ryujinx.Common.Utilities; -using Ryujinx.Input; -using Ryujinx.UI.Common.Configuration; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Text.Json; -using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; -using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; -using Key = Ryujinx.Common.Configuration.Hid.Key; namespace Ryujinx.Ava.UI.ViewModels { - public class ControllerInputViewModel : BaseModel, IDisposable + public class ControllerInputViewModel : BaseModel { - private const string Disabled = "disabled"; - private const string ProControllerResource = "Ryujinx.UI.Common/Resources/Controller_ProCon.svg"; - private const string JoyConPairResource = "Ryujinx.UI.Common/Resources/Controller_JoyConPair.svg"; - private const string JoyConLeftResource = "Ryujinx.UI.Common/Resources/Controller_JoyConLeft.svg"; - private const string JoyConRightResource = "Ryujinx.UI.Common/Resources/Controller_JoyConRight.svg"; - private const string KeyboardString = "keyboard"; - private const string ControllerString = "controller"; - private readonly MainWindow _mainWindow; + private ControllerInputConfig _config; + private bool _isLeft; + private bool _isRight; + private bool _showSettings; + private SvgImage _image; - private PlayerIndex _playerId; - private int _controller; - private int _controllerNumber; - private string _controllerImage; - private int _device; - private object _configuration; - private string _profileName; - private bool _isLoaded; - - private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - - public IGamepadDriver AvaloniaKeyboardDriver { get; } - public IGamepad SelectedGamepad { get; private set; } - - public ObservableCollection PlayerIndexes { get; set; } - 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; } - - // XAML Flags - public bool ShowSettings => _device > 0; - public bool IsController => _device > 1; - public bool IsKeyboard => !IsController; - public bool IsRight { get; set; } - public bool IsLeft { get; set; } - - public bool IsModified { get; set; } - - public object Configuration + public ControllerInputConfig Config { - get => _configuration; + get => _config; set { - _configuration = value; - + _config = value; OnPropertyChanged(); } } - public PlayerIndex PlayerId + public bool IsLeft { - get => _playerId; + get => _isLeft; set { - if (IsModified) - { - return; - } - - IsModified = false; - _playerId = value; - - if (!Enum.IsDefined(typeof(PlayerIndex), _playerId)) - { - _playerId = PlayerIndex.Player1; - } - - LoadConfiguration(); - LoadDevice(); - LoadProfiles(); - - _isLoaded = true; - + _isLeft = value; OnPropertyChanged(); } } - public int Controller + public bool IsRight { - get => _controller; + get => _isRight; set { - _controller = value; - - if (_controller == -1) - { - _controller = 0; - } - - if (Controllers.Count > 0 && value < 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(); - } - + _isRight = value; OnPropertyChanged(); - NotifyChanges(); } } - public string ControllerImage + public bool ShowSettings { - get => _controllerImage; + get => _showSettings; set { - _controllerImage = value; - + _showSettings = value; OnPropertyChanged(); - OnPropertyChanged(nameof(Image)); } } public SvgImage Image { - get - { - SvgImage image = new(); - - if (!string.IsNullOrWhiteSpace(_controllerImage)) - { - SvgSource source = new(default(Uri)); - - source.Load(EmbeddedResources.GetStream(_controllerImage)); - - image.Source = source; - } - - return image; - } - } - - public string ProfileName - { - get => _profileName; set - { - _profileName = value; - - OnPropertyChanged(); - } - } - - public int Device - { - get => _device; + get => _image; set { - _device = value < 0 ? 0 : value; - - if (_device >= Devices.Count) - { - return; - } - - var selected = Devices[_device].Type; - - if (selected != DeviceType.None) - { - LoadControllers(); - - if (_isLoaded) - { - LoadConfiguration(LoadDefaultConfiguration()); - } - } - + _image = value; OnPropertyChanged(); - NotifyChanges(); - } - } - - public InputConfig Config { get; set; } - - public ControllerInputViewModel(UserControl owner) : this() - { - if (Program.PreviewerDetached) - { - _mainWindow = - (MainWindow)((IClassicDesktopStyleApplicationLifetime)Application.Current - .ApplicationLifetime).MainWindow; - - AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner); - - _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; - _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; - - _mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates(); - - _isLoaded = false; - - LoadDevices(); - - PlayerId = PlayerIndex.Player1; - } - } - - public ControllerInputViewModel() - { - PlayerIndexes = new ObservableCollection(); - Controllers = new ObservableCollection(); - Devices = new ObservableCollection<(DeviceType Type, string Id, string Name)>(); - ProfilesList = new AvaloniaList(); - DeviceList = new AvaloniaList(); - - ControllerImage = ProControllerResource; - - PlayerIndexes.Add(new(PlayerIndex.Player1, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer1])); - PlayerIndexes.Add(new(PlayerIndex.Player2, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer2])); - PlayerIndexes.Add(new(PlayerIndex.Player3, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer3])); - PlayerIndexes.Add(new(PlayerIndex.Player4, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer4])); - PlayerIndexes.Add(new(PlayerIndex.Player5, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer5])); - PlayerIndexes.Add(new(PlayerIndex.Player6, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer6])); - PlayerIndexes.Add(new(PlayerIndex.Player7, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer7])); - PlayerIndexes.Add(new(PlayerIndex.Player8, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer8])); - PlayerIndexes.Add(new(PlayerIndex.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsHandheld])); - } - - private void LoadConfiguration(InputConfig inputConfig = null) - { - Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerId); - - if (Config is StandardKeyboardInputConfig keyboardInputConfig) - { - Configuration = new InputConfiguration(keyboardInputConfig); - } - - if (Config is StandardControllerInputConfig controllerInputConfig) - { - Configuration = new InputConfiguration(controllerInputConfig); - } - } - - public void LoadDevice() - { - if (Config == null || Config.Backend == InputBackendType.Invalid) - { - Device = 0; - } - else - { - var type = DeviceType.None; - - if (Config is StandardKeyboardInputConfig) - { - type = DeviceType.Keyboard; - } - - if (Config is StandardControllerInputConfig) - { - type = DeviceType.Controller; - } - - var 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; - } } } @@ -332,566 +71,5 @@ namespace Ryujinx.Ava.UI.ViewModels { await RumbleInputView.Show(this); } - - private void LoadInputDriver() - { - if (_device < 0) - { - return; - } - - string id = GetCurrentGamepadId(); - var type = Devices[Device].Type; - - if (type == DeviceType.None) - { - return; - } - - if (type == DeviceType.Keyboard) - { - if (_mainWindow.InputManager.KeyboardDriver is AvaloniaKeyboardDriver) - { - // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused... - SelectedGamepad = AvaloniaKeyboardDriver.GetGamepad(id); - } - else - { - SelectedGamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); - } - } - else - { - SelectedGamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); - } - } - - private void HandleOnGamepadDisconnected(string id) - { - Dispatcher.UIThread.Post(() => - { - LoadDevices(); - }); - } - - private void HandleOnGamepadConnected(string id) - { - Dispatcher.UIThread.Post(() => - { - LoadDevices(); - }); - } - - private string GetCurrentGamepadId() - { - if (_device < 0) - { - return string.Empty; - } - - var device = Devices[Device]; - - if (device.Type == DeviceType.None) - { - return null; - } - - return device.Id.Split(" ")[0]; - } - - public void LoadControllers() - { - Controllers.Clear(); - - if (_playerId == PlayerIndex.Handheld) - { - Controllers.Add(new(ControllerType.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeHandheld])); - - Controller = 0; - } - else - { - Controllers.Add(new(ControllerType.ProController, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeProController])); - Controllers.Add(new(ControllerType.JoyconPair, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConPair])); - Controllers.Add(new(ControllerType.JoyconLeft, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConLeft])); - Controllers.Add(new(ControllerType.JoyconRight, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConRight])); - - if (Config != null && Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType) != -1) - { - Controller = Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType); - } - else - { - Controller = 0; - } - } - } - - private static string GetShortGamepadName(string str) - { - const string Ellipsis = "..."; - const int MaxSize = 50; - - if (str.Length > MaxSize) - { - return $"{str.AsSpan(0, MaxSize - Ellipsis.Length)}{Ellipsis}"; - } - - return str; - } - - private static string GetShortGamepadId(string str) - { - const string Hyphen = "-"; - const int Offset = 1; - - return str[(str.IndexOf(Hyphen) + Offset)..]; - } - - public void LoadDevices() - { - lock (Devices) - { - Devices.Clear(); - DeviceList.Clear(); - Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance[LocaleKeys.ControllerSettingsDeviceDisabled])); - - foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds) - { - using IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); - - if (gamepad != null) - { - Devices.Add((DeviceType.Keyboard, id, $"{GetShortGamepadName(gamepad.Name)}")); - } - } - - foreach (string id in _mainWindow.InputManager.GamepadDriver.GamepadsIds) - { - using IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); - - if (gamepad != null) - { - if (Devices.Any(controller => GetShortGamepadId(controller.Id) == GetShortGamepadId(gamepad.Id))) - { - _controllerNumber++; - } - - Devices.Add((DeviceType.Controller, id, $"{GetShortGamepadName(gamepad.Name)} ({_controllerNumber})")); - } - } - - _controllerNumber = 0; - - DeviceList.AddRange(Devices.Select(x => x.Name)); - Device = Math.Min(Device, DeviceList.Count); - } - } - - private string GetProfileBasePath() - { - string path = AppDataManager.ProfilesDirPath; - var type = Devices[Device == -1 ? 0 : Device].Type; - - if (type == DeviceType.Keyboard) - { - path = Path.Combine(path, KeyboardString); - } - else if (type == DeviceType.Controller) - { - path = Path.Combine(path, ControllerString); - } - - return path; - } - - private void LoadProfiles() - { - ProfilesList.Clear(); - - string basePath = GetProfileBasePath(); - - if (!Directory.Exists(basePath)) - { - Directory.CreateDirectory(basePath); - } - - ProfilesList.Add((LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault])); - - foreach (string profile in Directory.GetFiles(basePath, "*.json", SearchOption.AllDirectories)) - { - ProfilesList.Add(Path.GetFileNameWithoutExtension(profile)); - } - - if (string.IsNullOrWhiteSpace(ProfileName)) - { - ProfileName = LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]; - } - } - - public InputConfig LoadDefaultConfiguration() - { - var activeDevice = Devices.FirstOrDefault(); - - if (Devices.Count > 0 && Device < Devices.Count && Device >= 0) - { - activeDevice = Devices[Device]; - } - - InputConfig config; - if (activeDevice.Type == DeviceType.Keyboard) - { - string id = activeDevice.Id; - - config = new StandardKeyboardInputConfig - { - Version = InputConfig.CurrentVersion, - Backend = InputBackendType.WindowKeyboard, - Id = id, - ControllerType = ControllerType.ProController, - LeftJoycon = new LeftJoyconCommonConfig - { - DpadUp = Key.Up, - DpadDown = Key.Down, - DpadLeft = Key.Left, - DpadRight = Key.Right, - ButtonMinus = Key.Minus, - ButtonL = Key.E, - ButtonZl = Key.Q, - ButtonSl = Key.Unbound, - ButtonSr = Key.Unbound, - }, - LeftJoyconStick = - new JoyconConfigKeyboardStick - { - StickUp = Key.W, - StickDown = Key.S, - StickLeft = Key.A, - StickRight = Key.D, - StickButton = Key.F, - }, - RightJoycon = new RightJoyconCommonConfig - { - ButtonA = Key.Z, - ButtonB = Key.X, - ButtonX = Key.C, - ButtonY = Key.V, - ButtonPlus = Key.Plus, - ButtonR = Key.U, - ButtonZr = Key.O, - ButtonSl = Key.Unbound, - ButtonSr = Key.Unbound, - }, - RightJoyconStick = new JoyconConfigKeyboardStick - { - StickUp = Key.I, - StickDown = Key.K, - StickLeft = Key.J, - StickRight = Key.L, - StickButton = Key.H, - }, - }; - } - else if (activeDevice.Type == DeviceType.Controller) - { - bool isNintendoStyle = Devices.ToList().Find(x => x.Id == activeDevice.Id).Name.Contains("Nintendo"); - - string id = activeDevice.Id.Split(" ")[0]; - - config = new StandardControllerInputConfig - { - Version = InputConfig.CurrentVersion, - Backend = InputBackendType.GamepadSDL2, - Id = id, - ControllerType = ControllerType.ProController, - DeadzoneLeft = 0.1f, - DeadzoneRight = 0.1f, - RangeLeft = 1.0f, - RangeRight = 1.0f, - TriggerThreshold = 0.5f, - LeftJoycon = new LeftJoyconCommonConfig - { - DpadUp = ConfigGamepadInputId.DpadUp, - DpadDown = ConfigGamepadInputId.DpadDown, - DpadLeft = ConfigGamepadInputId.DpadLeft, - DpadRight = ConfigGamepadInputId.DpadRight, - ButtonMinus = ConfigGamepadInputId.Minus, - ButtonL = ConfigGamepadInputId.LeftShoulder, - ButtonZl = ConfigGamepadInputId.LeftTrigger, - ButtonSl = ConfigGamepadInputId.Unbound, - ButtonSr = ConfigGamepadInputId.Unbound, - }, - LeftJoyconStick = new JoyconConfigControllerStick - { - Joystick = ConfigStickInputId.Left, - StickButton = ConfigGamepadInputId.LeftStick, - InvertStickX = false, - InvertStickY = false, - }, - RightJoycon = new RightJoyconCommonConfig - { - ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B, - ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A, - ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y, - ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X, - ButtonPlus = ConfigGamepadInputId.Plus, - ButtonR = ConfigGamepadInputId.RightShoulder, - ButtonZr = ConfigGamepadInputId.RightTrigger, - ButtonSl = ConfigGamepadInputId.Unbound, - ButtonSr = ConfigGamepadInputId.Unbound, - }, - RightJoyconStick = new JoyconConfigControllerStick - { - Joystick = ConfigStickInputId.Right, - StickButton = ConfigGamepadInputId.RightStick, - InvertStickX = false, - InvertStickY = false, - }, - Motion = new StandardMotionConfigController - { - MotionBackend = MotionInputBackendType.GamepadDriver, - EnableMotion = true, - Sensitivity = 100, - GyroDeadzone = 1, - }, - Rumble = new RumbleConfigController - { - StrongRumble = 1f, - WeakRumble = 1f, - EnableRumble = false, - }, - }; - } - else - { - config = new InputConfig(); - } - - config.PlayerIndex = _playerId; - - return config; - } - - public async void LoadProfile() - { - if (Device == 0) - { - return; - } - - InputConfig config = null; - - if (string.IsNullOrWhiteSpace(ProfileName)) - { - return; - } - - if (ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]) - { - config = LoadDefaultConfiguration(); - } - else - { - string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); - - if (!File.Exists(path)) - { - var index = ProfilesList.IndexOf(ProfileName); - if (index != -1) - { - ProfilesList.RemoveAt(index); - } - return; - } - - try - { - config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig); - } - catch (JsonException) { } - catch (InvalidOperationException) - { - Logger.Error?.Print(LogClass.Configuration, $"Profile {ProfileName} is incompatible with the current input configuration system."); - - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogProfileInvalidProfileErrorMessage, ProfileName)); - - return; - } - } - - if (config != null) - { - _isLoaded = false; - - LoadConfiguration(config); - - LoadDevice(); - - _isLoaded = true; - - NotifyChanges(); - } - } - - public async void SaveProfile() - { - if (Device == 0) - { - return; - } - - if (Configuration == null) - { - return; - } - - if (ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]) - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileDefaultProfileOverwriteErrorMessage]); - - return; - } - - bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1; - - if (validFileName) - { - string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); - - InputConfig config = null; - - if (IsKeyboard) - { - config = (Configuration as InputConfiguration).GetConfig(); - } - else if (IsController) - { - config = (Configuration as InputConfiguration).GetConfig(); - } - - config.ControllerType = Controllers[_controller].Type; - - string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig); - - await File.WriteAllTextAsync(path, jsonString); - - LoadProfiles(); - } - else - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]); - } - } - - public async void RemoveProfile() - { - if (Device == 0 || ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault] || ProfilesList.IndexOf(ProfileName) == -1) - { - return; - } - - UserResult result = await ContentDialogHelper.CreateConfirmationDialog( - LocaleManager.Instance[LocaleKeys.DialogProfileDeleteProfileTitle], - LocaleManager.Instance[LocaleKeys.DialogProfileDeleteProfileMessage], - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); - - if (result == UserResult.Yes) - { - string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); - - if (File.Exists(path)) - { - File.Delete(path); - } - - LoadProfiles(); - } - } - - public void Save() - { - IsModified = false; - - List newConfig = new(); - - newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value); - - newConfig.Remove(newConfig.Find(x => x == null)); - - if (Device == 0) - { - newConfig.Remove(newConfig.Find(x => x.PlayerIndex == this.PlayerId)); - } - else - { - var device = Devices[Device]; - - if (device.Type == DeviceType.Keyboard) - { - var inputConfig = Configuration as InputConfiguration; - inputConfig.Id = device.Id; - } - else - { - var inputConfig = Configuration as InputConfiguration; - inputConfig.Id = device.Id.Split(" ")[0]; - } - - var config = !IsController - ? (Configuration as InputConfiguration).GetConfig() - : (Configuration as InputConfiguration).GetConfig(); - config.ControllerType = Controllers[_controller].Type; - config.PlayerIndex = _playerId; - - int i = newConfig.FindIndex(x => x.PlayerIndex == PlayerId); - if (i == -1) - { - newConfig.Add(config); - } - else - { - newConfig[i] = config; - } - } - - _mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); - - // Atomically replace and signal input change. - // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event. - ConfigurationState.Instance.Hid.InputConfig.Value = newConfig; - - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - } - - public void NotifyChange(string property) - { - OnPropertyChanged(property); - } - - public void NotifyChanges() - { - OnPropertyChanged(nameof(Configuration)); - OnPropertyChanged(nameof(IsController)); - OnPropertyChanged(nameof(ShowSettings)); - OnPropertyChanged(nameof(IsKeyboard)); - OnPropertyChanged(nameof(IsRight)); - OnPropertyChanged(nameof(IsLeft)); - } - - public void Dispose() - { - GC.SuppressFinalize(this); - - _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; - _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; - - _mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates(); - - SelectedGamepad?.Dispose(); - - AvaloniaKeyboardDriver.Dispose(); - } } -} +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/UI/ViewModels/InputViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/InputViewModel.cs new file mode 100644 index 000000000..6f213e31f --- /dev/null +++ b/src/Ryujinx.Ava/UI/ViewModels/InputViewModel.cs @@ -0,0 +1,890 @@ +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Svg.Skia; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.Views.Input; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using Ryujinx.Common.Configuration.Hid.Keyboard; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.Input; +using Ryujinx.Ui.Common.Configuration; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text.Json; +using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; +using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; +using Key = Ryujinx.Common.Configuration.Hid.Key; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class InputViewModel : BaseModel, IDisposable + { + private const string Disabled = "disabled"; + private const string ProControllerResource = "Ryujinx.Ui.Common/Resources/Controller_ProCon.svg"; + private const string JoyConPairResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConPair.svg"; + private const string JoyConLeftResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConLeft.svg"; + private const string JoyConRightResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConRight.svg"; + private const string KeyboardString = "keyboard"; + private const string ControllerString = "controller"; + private readonly MainWindow _mainWindow; + + private PlayerIndex _playerId; + private int _controller; + private int _controllerNumber = 0; + private string _controllerImage; + private int _device; + private object _configuration; + private string _profileName; + private bool _isLoaded; + private readonly UserControl _owner; + + private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public IGamepadDriver AvaloniaKeyboardDriver { get; } + public IGamepad SelectedGamepad { get; private set; } + + public ObservableCollection PlayerIndexes { get; set; } + 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; } + + // XAML Flags + public bool ShowSettings => _device > 0; + public bool IsController => _device > 1; + public bool IsKeyboard => !IsController; + public bool IsRight { get; set; } + public bool IsLeft { get; set; } + + public bool IsModified { get; set; } + + public object Configuration + { + get => _configuration; + set + { + _configuration = value; + + OnPropertyChanged(); + } + } + + public PlayerIndex PlayerId + { + get => _playerId; + set + { + if (IsModified) + { + return; + } + + IsModified = false; + _playerId = value; + + if (!Enum.IsDefined(typeof(PlayerIndex), _playerId)) + { + _playerId = PlayerIndex.Player1; + } + + LoadConfiguration(); + LoadDevice(); + LoadProfiles(); + + _isLoaded = true; + + OnPropertyChanged(); + } + } + + public int Controller + { + get => _controller; + set + { + _controller = value; + + if (_controller == -1) + { + _controller = 0; + } + + if (Controllers.Count > 0 && value < 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(); + } + } + + public string ControllerImage + { + get => _controllerImage; + set + { + _controllerImage = value; + + OnPropertyChanged(); + OnPropertyChanged(nameof(Image)); + } + } + + public SvgImage Image + { + get + { + SvgImage image = new SvgImage(); + + if (!string.IsNullOrWhiteSpace(_controllerImage)) + { + SvgSource source = new SvgSource(); + + source.Load(EmbeddedResources.GetStream(_controllerImage)); + + image.Source = source; + } + + return image; + } + } + + public string ProfileName + { + get => _profileName; set + { + _profileName = value; + + OnPropertyChanged(); + } + } + + public int Device + { + get => _device; + set + { + _device = value < 0 ? 0 : value; + + if (_device >= Devices.Count) + { + return; + } + + var selected = Devices[_device].Type; + + if (selected != DeviceType.None) + { + LoadControllers(); + + if (_isLoaded) + { + LoadConfiguration(LoadDefaultConfiguration()); + } + } + + OnPropertyChanged(); + NotifyChanges(); + } + } + + public InputConfig Config { get; set; } + + public InputViewModel(UserControl owner) : this() + { + _owner = owner; + + if (Program.PreviewerDetached) + { + _mainWindow = + (MainWindow)((IClassicDesktopStyleApplicationLifetime)Avalonia.Application.Current + .ApplicationLifetime).MainWindow; + + AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner); + + _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; + _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; + if (_mainWindow.ViewModel.AppHost != null) + { + _mainWindow.ViewModel.AppHost.NpadManager.BlockInputUpdates(); + } + + _isLoaded = false; + + LoadDevices(); + + PlayerId = PlayerIndex.Player1; + } + } + + public InputViewModel() + { + PlayerIndexes = new ObservableCollection(); + Controllers = new ObservableCollection(); + Devices = new ObservableCollection<(DeviceType Type, string Id, string Name)>(); + ProfilesList = new AvaloniaList(); + DeviceList = new AvaloniaList(); + + ControllerImage = ProControllerResource; + + PlayerIndexes.Add(new(PlayerIndex.Player1, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer1])); + PlayerIndexes.Add(new(PlayerIndex.Player2, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer2])); + PlayerIndexes.Add(new(PlayerIndex.Player3, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer3])); + PlayerIndexes.Add(new(PlayerIndex.Player4, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer4])); + PlayerIndexes.Add(new(PlayerIndex.Player5, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer5])); + PlayerIndexes.Add(new(PlayerIndex.Player6, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer6])); + PlayerIndexes.Add(new(PlayerIndex.Player7, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer7])); + PlayerIndexes.Add(new(PlayerIndex.Player8, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer8])); + PlayerIndexes.Add(new(PlayerIndex.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsHandheld])); + } + + private void LoadConfiguration(InputConfig inputConfig = null) + { + Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerId); + + if (Config is StandardKeyboardInputConfig keyboardInputConfig) + { + Configuration = new KeyboardInputConfig(keyboardInputConfig); + } + + if (Config is StandardControllerInputConfig controllerInputConfig) + { + Configuration = new ControllerInputConfig(controllerInputConfig); + } + } + + public void LoadDevice() + { + if (Config == null || Config.Backend == InputBackendType.Invalid) + { + Device = 0; + } + else + { + var type = DeviceType.None; + + if (Config is StandardKeyboardInputConfig) + { + type = DeviceType.Keyboard; + } + + if (Config is StandardControllerInputConfig) + { + type = DeviceType.Controller; + } + + var 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; + } + } + } + + private void LoadInputDriver() + { + if (_device < 0) + { + return; + } + + string id = GetCurrentGamepadId(); + var type = Devices[Device].Type; + + if (type == DeviceType.None) + { + return; + } + else if (type == DeviceType.Keyboard) + { + if (_mainWindow.InputManager.KeyboardDriver is AvaloniaKeyboardDriver) + { + // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused... + SelectedGamepad = AvaloniaKeyboardDriver.GetGamepad(id); + } + else + { + SelectedGamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); + } + } + else + { + SelectedGamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); + } + } + + private void HandleOnGamepadDisconnected(string id) + { + Dispatcher.UIThread.Post(() => + { + LoadDevices(); + }); + } + + private void HandleOnGamepadConnected(string id) + { + Dispatcher.UIThread.Post(() => + { + LoadDevices(); + }); + } + + private string GetCurrentGamepadId() + { + if (_device < 0) + { + return string.Empty; + } + + var device = Devices[Device]; + + if (device.Type == DeviceType.None) + { + return null; + } + + return device.Id.Split(" ")[0]; + } + + public void LoadControllers() + { + Controllers.Clear(); + + if (_playerId == PlayerIndex.Handheld) + { + Controllers.Add(new(ControllerType.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeHandheld])); + + Controller = 0; + } + else + { + Controllers.Add(new(ControllerType.ProController, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeProController])); + Controllers.Add(new(ControllerType.JoyconPair, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConPair])); + Controllers.Add(new(ControllerType.JoyconLeft, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConLeft])); + Controllers.Add(new(ControllerType.JoyconRight, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConRight])); + + if (Config != null && Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType) != -1) + { + Controller = Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType); + } + else + { + Controller = 0; + } + } + } + + private static string GetShortGamepadName(string str) + { + const string Ellipsis = "..."; + const int MaxSize = 50; + + if (str.Length > MaxSize) + { + return $"{str.AsSpan(0, MaxSize - Ellipsis.Length)}{Ellipsis}"; + } + + return str; + } + + private static string GetShortGamepadId(string str) + { + const string Hyphen = "-"; + const int Offset = 1; + + return str.Substring(str.IndexOf(Hyphen) + Offset); + } + + public void LoadDevices() + { + lock (Devices) + { + Devices.Clear(); + DeviceList.Clear(); + Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance[LocaleKeys.ControllerSettingsDeviceDisabled])); + + foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds) + { + using IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); + + if (gamepad != null) + { + Devices.Add((DeviceType.Keyboard, id, $"{GetShortGamepadName(gamepad.Name)}")); + } + } + + foreach (string id in _mainWindow.InputManager.GamepadDriver.GamepadsIds) + { + using IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); + + if (gamepad != null) + { + if (Devices.Any(controller => GetShortGamepadId(controller.Id) == GetShortGamepadId(gamepad.Id))) + { + _controllerNumber++; + } + + Devices.Add((DeviceType.Controller, id, $"{GetShortGamepadName(gamepad.Name)} ({_controllerNumber})")); + } + } + + _controllerNumber = 0; + + DeviceList.AddRange(Devices.Select(x => x.Name)); + Device = Math.Min(Device, DeviceList.Count); + } + } + + private string GetProfileBasePath() + { + string path = AppDataManager.ProfilesDirPath; + var type = Devices[Device == -1 ? 0 : Device].Type; + + if (type == DeviceType.Keyboard) + { + path = Path.Combine(path, KeyboardString); + } + else if (type == DeviceType.Controller) + { + path = Path.Combine(path, ControllerString); + } + + return path; + } + + private void LoadProfiles() + { + ProfilesList.Clear(); + + string basePath = GetProfileBasePath(); + + if (!Directory.Exists(basePath)) + { + Directory.CreateDirectory(basePath); + } + + ProfilesList.Add((LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault])); + + foreach (string profile in Directory.GetFiles(basePath, "*.json", SearchOption.AllDirectories)) + { + ProfilesList.Add(Path.GetFileNameWithoutExtension(profile)); + } + + if (string.IsNullOrWhiteSpace(ProfileName)) + { + ProfileName = LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]; + } + } + + public InputConfig LoadDefaultConfiguration() + { + var activeDevice = Devices.FirstOrDefault(); + + if (Devices.Count > 0 && Device < Devices.Count && Device >= 0) + { + activeDevice = Devices[Device]; + } + + InputConfig config; + if (activeDevice.Type == DeviceType.Keyboard) + { + string id = activeDevice.Id; + + config = new StandardKeyboardInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.WindowKeyboard, + Id = id, + ControllerType = ControllerType.ProController, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = Key.Up, + DpadDown = Key.Down, + DpadLeft = Key.Left, + DpadRight = Key.Right, + ButtonMinus = Key.Minus, + ButtonL = Key.E, + ButtonZl = Key.Q, + ButtonSl = Key.Unbound, + ButtonSr = Key.Unbound + }, + LeftJoyconStick = + new JoyconConfigKeyboardStick + { + StickUp = Key.W, + StickDown = Key.S, + StickLeft = Key.A, + StickRight = Key.D, + StickButton = Key.F + }, + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = Key.Z, + ButtonB = Key.X, + ButtonX = Key.C, + ButtonY = Key.V, + ButtonPlus = Key.Plus, + ButtonR = Key.U, + ButtonZr = Key.O, + ButtonSl = Key.Unbound, + ButtonSr = Key.Unbound + }, + RightJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = Key.I, + StickDown = Key.K, + StickLeft = Key.J, + StickRight = Key.L, + StickButton = Key.H + } + }; + } + else if (activeDevice.Type == DeviceType.Controller) + { + bool isNintendoStyle = Devices.ToList().Find(x => x.Id == activeDevice.Id).Name.Contains("Nintendo"); + + string id = activeDevice.Id.Split(" ")[0]; + + config = new StandardControllerInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.GamepadSDL2, + Id = id, + ControllerType = ControllerType.ProController, + DeadzoneLeft = 0.1f, + DeadzoneRight = 0.1f, + RangeLeft = 1.0f, + RangeRight = 1.0f, + TriggerThreshold = 0.5f, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = ConfigGamepadInputId.DpadUp, + DpadDown = ConfigGamepadInputId.DpadDown, + DpadLeft = ConfigGamepadInputId.DpadLeft, + DpadRight = ConfigGamepadInputId.DpadRight, + ButtonMinus = ConfigGamepadInputId.Minus, + ButtonL = ConfigGamepadInputId.LeftShoulder, + ButtonZl = ConfigGamepadInputId.LeftTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound + }, + LeftJoyconStick = new JoyconConfigControllerStick + { + Joystick = ConfigStickInputId.Left, + StickButton = ConfigGamepadInputId.LeftStick, + InvertStickX = false, + InvertStickY = false + }, + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B, + ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A, + ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y, + ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X, + ButtonPlus = ConfigGamepadInputId.Plus, + ButtonR = ConfigGamepadInputId.RightShoulder, + ButtonZr = ConfigGamepadInputId.RightTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound + }, + RightJoyconStick = new JoyconConfigControllerStick + { + Joystick = ConfigStickInputId.Right, + StickButton = ConfigGamepadInputId.RightStick, + InvertStickX = false, + InvertStickY = false + }, + Motion = new StandardMotionConfigController + { + MotionBackend = MotionInputBackendType.GamepadDriver, + EnableMotion = true, + Sensitivity = 100, + GyroDeadzone = 1 + }, + Rumble = new RumbleConfigController + { + StrongRumble = 1f, + WeakRumble = 1f, + EnableRumble = false + } + }; + } + else + { + config = new InputConfig(); + } + + config.PlayerIndex = _playerId; + + return config; + } + + public async void LoadProfile() + { + if (Device == 0) + { + return; + } + + InputConfig config = null; + + if (string.IsNullOrWhiteSpace(ProfileName)) + { + return; + } + + if (ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]) + { + config = LoadDefaultConfiguration(); + } + else + { + string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); + + if (!File.Exists(path)) + { + var index = ProfilesList.IndexOf(ProfileName); + if (index != -1) + { + ProfilesList.RemoveAt(index); + } + return; + } + + try + { + config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig); + } + catch (JsonException) { } + catch (InvalidOperationException) + { + Logger.Error?.Print(LogClass.Configuration, $"Profile {ProfileName} is incompatible with the current input configuration system."); + + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogProfileInvalidProfileErrorMessage, ProfileName)); + + return; + } + } + + if (config != null) + { + _isLoaded = false; + + LoadConfiguration(config); + + LoadDevice(); + + _isLoaded = true; + + NotifyChanges(); + } + } + + public async void SaveProfile() + { + if (Device == 0) + { + return; + } + + if (Configuration == null) + { + return; + } + + if (ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileDefaultProfileOverwriteErrorMessage]); + + return; + } + else + { + bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1; + + if (validFileName) + { + string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); + + InputConfig config = null; + + if (IsKeyboard) + { + config = (Configuration as KeyboardInputConfig).GetConfig(); + } + else if (IsController) + { + config = (Configuration as ControllerInputConfig).GetConfig(); + } + + config.ControllerType = Controllers[_controller].Type; + + string jsonString = JsonHelper.Serialize(config, SerializerContext.InputConfig); + + await File.WriteAllTextAsync(path, jsonString); + + LoadProfiles(); + } + else + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]); + } + } + } + + public async void RemoveProfile() + { + if (Device == 0 || ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault] || ProfilesList.IndexOf(ProfileName) == -1) + { + return; + } + + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogProfileDeleteProfileTitle], + LocaleManager.Instance[LocaleKeys.DialogProfileDeleteProfileMessage], + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); + + if (File.Exists(path)) + { + File.Delete(path); + } + + LoadProfiles(); + } + } + + public void Save() + { + IsModified = false; + + List newConfig = new(); + + newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value); + + newConfig.Remove(newConfig.Find(x => x == null)); + + if (Device == 0) + { + newConfig.Remove(newConfig.Find(x => x.PlayerIndex == this.PlayerId)); + } + else + { + var device = Devices[Device]; + + if (device.Type == DeviceType.Keyboard) + { + var inputConfig = Configuration as KeyboardInputConfig; + inputConfig.Id = device.Id; + } + else + { + var inputConfig = Configuration as ControllerInputConfig; + inputConfig.Id = device.Id.Split(" ")[0]; + } + + var config = !IsController + ? (Configuration as KeyboardInputConfig).GetConfig() + : (Configuration as ControllerInputConfig).GetConfig(); + config.ControllerType = Controllers[_controller].Type; + config.PlayerIndex = _playerId; + + int i = newConfig.FindIndex(x => x.PlayerIndex == PlayerId); + if (i == -1) + { + newConfig.Add(config); + } + else + { + newConfig[i] = config; + } + } + + _mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); + + // Atomically replace and signal input change. + // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event. + ConfigurationState.Instance.Hid.InputConfig.Value = newConfig; + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + + public void NotifyChange(string property) + { + OnPropertyChanged(property); + } + + public void NotifyChanges() + { + OnPropertyChanged(nameof(Configuration)); + OnPropertyChanged(nameof(IsController)); + OnPropertyChanged(nameof(ShowSettings)); + OnPropertyChanged(nameof(IsKeyboard)); + OnPropertyChanged(nameof(IsRight)); + OnPropertyChanged(nameof(IsLeft)); + } + + public void Dispose() + { + _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; + _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; + + _mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates(); + + SelectedGamepad?.Dispose(); + + AvaloniaKeyboardDriver.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/UI/ViewModels/KeyboardInputViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/KeyboardInputViewModel.cs new file mode 100644 index 000000000..db8329073 --- /dev/null +++ b/src/Ryujinx.Ava/UI/ViewModels/KeyboardInputViewModel.cs @@ -0,0 +1,64 @@ +using Avalonia.Svg.Skia; +using Ryujinx.Ava.UI.Models; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class KeyboardInputViewModel : BaseModel + { + private KeyboardInputConfig _config; + private bool _isLeft; + private bool _isRight; + private bool _showSettings; + private SvgImage _image; + + public KeyboardInputConfig Config + { + get => _config; + set + { + _config = value; + OnPropertyChanged(); + } + } + + public bool IsLeft + { + get => _isLeft; + set + { + _isLeft = value; + OnPropertyChanged(); + } + } + + public bool IsRight + { + get => _isRight; + set + { + _isRight = value; + OnPropertyChanged(); + } + } + + public bool ShowSettings + { + get => _showSettings; + set + { + _showSettings = value; + OnPropertyChanged(); + } + } + + public SvgImage Image + { + get => _image; + set + { + _image = value; + OnPropertyChanged(); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml b/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml index 99f2b6b69..8945fc27b 100644 --- a/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml +++ b/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml @@ -1,12 +1,9 @@ @@ -34,191 +32,10 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Orientation="Vertical"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -260,7 +77,7 @@ TextAlignment="Center" /> @@ -276,7 +93,7 @@ TextAlignment="Center" /> @@ -292,7 +109,7 @@ TextAlignment="Center" /> @@ -312,100 +129,8 @@ Margin="0,0,0,10" HorizontalAlignment="Center" Text="{locale:Locale ControllerSettingsLStick}" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -418,7 +143,7 @@ TextAlignment="Center" /> @@ -435,20 +160,20 @@ TextAlignment="Center" /> - + - + - + - + Value="{Binding Config.DeadzoneLeft, Mode=TwoWay}" /> + Text="{Binding Config.DeadzoneLeft, StringFormat=\{0:0.00\}}" /> - + Value="{Binding Config.RangeLeft, Mode=TwoWay}" /> + Text="{Binding Config.RangeLeft, StringFormat=\{0:0.00\}}" /> @@ -528,7 +253,7 @@ TextAlignment="Center" /> @@ -545,7 +270,7 @@ TextAlignment="Center" /> @@ -562,7 +287,7 @@ TextAlignment="Center" /> @@ -579,7 +304,7 @@ TextAlignment="Center" /> @@ -606,17 +331,17 @@ - + Value="{Binding Config.TriggerThreshold, Mode=TwoWay}" /> + Text="{Binding Config.TriggerThreshold, StringFormat=\{0:0.00\}}" /> @@ -650,7 +375,7 @@ TextAlignment="Center" /> @@ -668,7 +393,7 @@ TextAlignment="Center" /> @@ -686,7 +411,7 @@ TextAlignment="Center" /> @@ -710,8 +435,7 @@ BorderThickness="1" CornerRadius="5" VerticalAlignment="Bottom" - HorizontalAlignment="Stretch" - IsVisible="{Binding IsController}"> + HorizontalAlignment="Stretch"> @@ -721,7 +445,7 @@ Margin="10" MinWidth="0" Grid.Column="0" - IsChecked="{ReflectionBinding Configuration.EnableMotion, Mode=TwoWay}"> + IsChecked="{Binding Config.EnableMotion, Mode=TwoWay}"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Ryujinx.Ava/UI/Views/Input/InputView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Input/InputView.axaml.cs new file mode 100644 index 000000000..91715b859 --- /dev/null +++ b/src/Ryujinx.Ava/UI/Views/Input/InputView.axaml.cs @@ -0,0 +1,180 @@ +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.LogicalTree; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Input; +using Ryujinx.Input.Assigner; +using System; + +namespace Ryujinx.Ava.UI.Views.Input +{ + public partial class InputView : UserControl + { + private bool _dialogOpen; + + private ButtonKeyAssigner _currentAssigner; + internal InputViewModel ViewModel { get; set; } + + public InputView() + { + DataContext = ViewModel = new InputViewModel(this); + + InitializeComponent(); + + foreach (ILogical visual in SettingButtons.GetLogicalDescendants()) + { + if (visual is ToggleButton button && !(visual is CheckBox)) + { + button.Checked += Button_Checked; + button.Unchecked += Button_Unchecked; + } + } + } + + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + base.OnPointerReleased(e); + + if (_currentAssigner != null && _currentAssigner.ToggledButton != null && !_currentAssigner.ToggledButton.IsPointerOver) + { + _currentAssigner.Cancel(); + } + } + + private void Button_Checked(object sender, RoutedEventArgs e) + { + if (sender is ToggleButton button) + { + if (_currentAssigner != null && button == _currentAssigner.ToggledButton) + { + return; + } + + bool isStick = button.Tag != null && button.Tag.ToString() == "stick"; + + if (_currentAssigner == null && (bool)button.IsChecked) + { + _currentAssigner = new ButtonKeyAssigner(button); + + FocusManager.Instance.Focus(this, NavigationMethod.Pointer); + + PointerPressed += MouseClick; + + IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations. + IButtonAssigner assigner = CreateButtonAssigner(isStick); + + _currentAssigner.ButtonAssigned += (sender, e) => + { + if (e.IsAssigned) + { + ViewModel.IsModified = true; + } + }; + + _currentAssigner.GetInputAndAssign(assigner, keyboard); + } + else + { + if (_currentAssigner != null) + { + ToggleButton oldButton = _currentAssigner.ToggledButton; + + _currentAssigner.Cancel(); + _currentAssigner = null; + button.IsChecked = false; + } + } + } + } + + public void SaveCurrentProfile() + { + ViewModel.Save(); + } + + private IButtonAssigner CreateButtonAssigner(bool forStick) + { + IButtonAssigner assigner; + + var device = ViewModel.Devices[ViewModel.Device]; + + if (device.Type == DeviceType.Keyboard) + { + assigner = new KeyboardKeyAssigner((IKeyboard)ViewModel.SelectedGamepad); + } + else if (device.Type == DeviceType.Controller) + { + assigner = new GamepadButtonAssigner(ViewModel.SelectedGamepad, (ViewModel.Config as StandardControllerInputConfig).TriggerThreshold, forStick); + } + else + { + throw new Exception("Controller not supported"); + } + + return assigner; + } + + private void Button_Unchecked(object sender, RoutedEventArgs e) + { + _currentAssigner?.Cancel(); + _currentAssigner = null; + } + + private void MouseClick(object sender, PointerPressedEventArgs e) + { + bool shouldUnbind = false; + + if (e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed) + { + shouldUnbind = true; + } + + _currentAssigner?.Cancel(shouldUnbind); + + PointerPressed -= MouseClick; + } + + private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (ViewModel.IsModified && !_dialogOpen) + { + _dialogOpen = true; + + var result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmMessage], + LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmSubMessage], + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + ViewModel.Save(); + } + + _dialogOpen = false; + + ViewModel.IsModified = false; + + if (e.AddedItems.Count > 0) + { + var player = (PlayerModel)e.AddedItems[0]; + ViewModel.PlayerId = player.Id; + } + } + } + + public void Dispose() + { + _currentAssigner?.Cancel(); + _currentAssigner = null; + ViewModel.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/UI/Views/Input/KeyboardInputView.axaml b/src/Ryujinx.Ava/UI/Views/Input/KeyboardInputView.axaml new file mode 100644 index 000000000..a2ba522c7 --- /dev/null +++ b/src/Ryujinx.Ava/UI/Views/Input/KeyboardInputView.axaml @@ -0,0 +1,678 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Ryujinx.Ava/UI/Views/Input/KeyboardInputView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Input/KeyboardInputView.axaml.cs new file mode 100644 index 000000000..d544660c2 --- /dev/null +++ b/src/Ryujinx.Ava/UI/Views/Input/KeyboardInputView.axaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Ryujinx.Ava.UI.Views.Input +{ + public partial class KeyboardInputView : UserControl + { + public KeyboardInputView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml b/src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml index 81f4b68b7..08b45f206 100644 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml +++ b/src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml @@ -27,7 +27,7 @@ -