From 85547874c8db21cc741c26afa6e8ec43922f6b46 Mon Sep 17 00:00:00 2001 From: MutantAura Date: Mon, 3 Jun 2024 23:10:04 +0100 Subject: [PATCH] Consolidate most logic into `StickVisualizer`. --- .../UI/Models/Input/StickVisualizer.cs | 151 +++++++++++++++++- .../Input/ControllerInputViewModel.cs | 109 ++----------- .../UI/ViewModels/Input/InputViewModel.cs | 25 ++- .../Input/KeyboardInputViewModel.cs | 91 ++--------- .../UI/Views/Input/ControllerInputView.axaml | 56 +++---- .../UI/Views/Input/KeyboardInputView.axaml | 60 +++---- 6 files changed, 244 insertions(+), 248 deletions(-) diff --git a/src/Ryujinx/UI/Models/Input/StickVisualizer.cs b/src/Ryujinx/UI/Models/Input/StickVisualizer.cs index 83cfd4766..b7e9ec331 100644 --- a/src/Ryujinx/UI/Models/Input/StickVisualizer.cs +++ b/src/Ryujinx/UI/Models/Input/StickVisualizer.cs @@ -1,10 +1,13 @@ using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.ViewModels.Input; +using Ryujinx.Input; using System; using System.Threading; +using System.Threading.Tasks; namespace Ryujinx.Ava.UI.Models.Input { - public class StickVisualizer : BaseModel + public class StickVisualizer : BaseModel, IDisposable { public const int DrawStickPollRate = 50; // Milliseconds per poll. public const int DrawStickCircumference = 5; @@ -14,12 +17,26 @@ namespace Ryujinx.Ava.UI.Models.Input public const float DrawStickCanvasCenter = (DrawStickCanvasSize - DrawStickCircumference) / 2; public const float MaxVectorLength = DrawStickCanvasSize / 2; - public CancellationTokenSource PollTokenSource = new(); + public CancellationTokenSource PollTokenSource; public CancellationToken PollToken; private static float _vectorLength; private static float _vectorMultiplier; + private bool disposedValue; + + private DeviceType _type; + public DeviceType Type + { + get => _type; + set + { + _type = value; + + OnPropertyChanged(); + } + } + private GamepadInputConfig _gamepadConfig; public GamepadInputConfig GamepadConfig { @@ -86,22 +103,119 @@ namespace Ryujinx.Ava.UI.Models.Input public float? UiDeadzoneLeft => _gamepadConfig?.DeadzoneLeft * DrawStickCanvasSize - DrawStickCircumference; public float? UiDeadzoneRight => _gamepadConfig?.DeadzoneRight * DrawStickCanvasSize - DrawStickCircumference; + private InputViewModel Parent; + + public StickVisualizer(InputViewModel parent) + { + Parent = parent; + + PollTokenSource = new CancellationTokenSource(); + PollToken = PollTokenSource.Token; + + Task.Run(Initialize, PollToken); + } + public void UpdateConfig(object config) { - if (config is GamepadInputConfig padConfig) + if (config is ControllerInputViewModel padConfig) { - GamepadConfig = padConfig; + GamepadConfig = padConfig.Config; + Type = DeviceType.Controller; return; } - else if (config is KeyboardInputConfig keyConfig) + else if (config is KeyboardInputViewModel keyConfig) { - KeyboardConfig = keyConfig; + KeyboardConfig = keyConfig.Config; + Type = DeviceType.Keyboard; return; } - throw new ArgumentException($"Invalid configuration: {config}"); + Type = DeviceType.None; + } + + public async Task Initialize() + { + (float, float) leftBuffer; + (float, float) rightBuffer; + + while (!PollToken.IsCancellationRequested) + { + leftBuffer = (0f, 0f); + rightBuffer = (0f, 0f); + + switch (Type) + { + case DeviceType.Keyboard: + IKeyboard keyboard = (IKeyboard)Parent.AvaloniaKeyboardDriver.GetGamepad("0"); + + if (keyboard != null) + { + KeyboardStateSnapshot snapshot = keyboard.GetKeyboardStateSnapshot(); + + if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickRight)) + { + leftBuffer.Item1 += 1; + } + if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickLeft)) + { + leftBuffer.Item1 -= 1; + } + if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickUp)) + { + leftBuffer.Item2 += 1; + } + if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickDown)) + { + leftBuffer.Item2 -= 1; + } + + if (snapshot.IsPressed((Key)KeyboardConfig.RightStickRight)) + { + rightBuffer.Item1 += 1; + } + if (snapshot.IsPressed((Key)KeyboardConfig.RightStickLeft)) + { + rightBuffer.Item1 -= 1; + } + if (snapshot.IsPressed((Key)KeyboardConfig.RightStickUp)) + { + rightBuffer.Item2 += 1; + } + if (snapshot.IsPressed((Key)KeyboardConfig.RightStickDown)) + { + rightBuffer.Item2 -= 1; + } + + UiStickLeft = leftBuffer; + UiStickRight = rightBuffer; + } + break; + + case DeviceType.Controller: + IGamepad controller = Parent.SelectedGamepad; + + if (controller != null) + { + leftBuffer = controller.GetStick((StickInputId)GamepadConfig.LeftJoystick); + rightBuffer = controller.GetStick((StickInputId)GamepadConfig.RightJoystick); + } + break; + + case DeviceType.None: + break; + default: + throw new ArgumentException($"Unable to poll device type \"{Type}\""); + } + + UiStickLeft = leftBuffer; + UiStickRight = rightBuffer; + + await Task.Delay(DrawStickPollRate, PollToken); + } + + PollTokenSource.Dispose(); } public static (float, float) ClampVector((float, float) vect) @@ -119,5 +233,28 @@ namespace Ryujinx.Ava.UI.Models.Input return vect; } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + PollTokenSource.Cancel(); + } + + KeyboardConfig = null; + GamepadConfig = null; + Parent = null; + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } diff --git a/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs index aa2f15e6e..6c683a50a 100644 --- a/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs @@ -6,38 +6,11 @@ using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.Input; using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Views.Input; -using Ryujinx.Input; -using System.Threading; -using System.Threading.Tasks; namespace Ryujinx.Ava.UI.ViewModels.Input { public partial class ControllerInputViewModel : BaseModel { - private const int DrawStickPollRate = 50; // Milliseconds per poll. - private const int DrawStickCircumference = 5; - private const float DrawStickScaleFactor = DrawStickCanvasCenter; - - private const int DrawStickCanvasSize = 100; - private const int DrawStickBorderSize = DrawStickCanvasSize + 5; - private const float DrawStickCanvasCenter = (DrawStickCanvasSize - DrawStickCircumference) / 2; - - private const float MaxVectorLength = DrawStickCanvasSize / 2; - - private IGamepad _selectedGamepad; - - private StickVisualizer _stickVisualizer; - public StickVisualizer StickVisualizer - { - get => _stickVisualizer; - set - { - _stickVisualizer = value; - - OnPropertyChanged(); - } - } - private GamepadInputConfig _config; public GamepadInputConfig Config { @@ -45,7 +18,18 @@ namespace Ryujinx.Ava.UI.ViewModels.Input set { _config = value; - StickVisualizer.UpdateConfig(Config); + + OnPropertyChanged(); + } + } + + private StickVisualizer _visualizer; + public StickVisualizer Visualizer + { + get => _visualizer; + set + { + _visualizer = value; OnPropertyChanged(); } @@ -81,60 +65,13 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public InputViewModel ParentModel { get; } - private (float, float) _uiStickLeft; - - public (float, float) UiStickLeft - { - get => (_uiStickLeft.Item1 * DrawStickScaleFactor, _uiStickLeft.Item2 * DrawStickScaleFactor); - set - { - _uiStickLeft = value; - - OnPropertyChanged(); - OnPropertyChanged(nameof(UiStickRightX)); - OnPropertyChanged(nameof(UiStickRightY)); - OnPropertyChanged(nameof(UiDeadzoneRight)); - } - } - - private (float, float) _uiStickRight; - public (float, float) UiStickRight - { - get => (_uiStickRight.Item1 * DrawStickScaleFactor, _uiStickRight.Item2 * DrawStickScaleFactor); - set - { - _uiStickRight = value; - - OnPropertyChanged(); - OnPropertyChanged(nameof(UiStickLeftX)); - OnPropertyChanged(nameof(UiStickLeftY)); - OnPropertyChanged(nameof(UiDeadzoneLeft)); - } - } - - public int UiStickCircumference => DrawStickCircumference; - public int UiCanvasSize => DrawStickCanvasSize; - public int UiStickBorderSize => DrawStickBorderSize; - - public float UiStickLeftX => ClampVector(UiStickLeft).Item1; - public float UiStickLeftY => ClampVector(UiStickLeft).Item2; - public float UiStickRightX => ClampVector(UiStickRight).Item1; - public float UiStickRightY => ClampVector(UiStickRight).Item2; - - public float UiDeadzoneLeft => Config.DeadzoneLeft * DrawStickCanvasSize - DrawStickCircumference; - public float UiDeadzoneRight => Config.DeadzoneRight * DrawStickCanvasSize - DrawStickCircumference; - - public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config) + public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config, StickVisualizer visualizer) { ParentModel = model; + Visualizer = visualizer; model.NotifyChangesEvent += OnParentModelChanged; OnParentModelChanged(); - _stickVisualizer = new(); Config = config; - - StickVisualizer.PollToken = StickVisualizer.PollTokenSource.Token; - - Task.Run(() => PollSticks(StickVisualizer.PollToken)); } public async void ShowMotionConfig() @@ -157,24 +94,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input ParentModel.SelectedGamepad.SetLed(Config.LedColor.ToUInt32()); }); - private async Task PollSticks(CancellationToken token) - { - while (!token.IsCancellationRequested) - { - _selectedGamepad = ParentModel.SelectedGamepad; - - if (_selectedGamepad != null && _selectedGamepad is not AvaloniaKeyboard) - { - StickVisualizer.UiStickLeft = _selectedGamepad.GetStick(StickInputId.Left); - StickVisualizer.UiStickRight = _selectedGamepad.GetStick(StickInputId.Right); - } - - await Task.Delay(StickVisualizer.DrawStickPollRate, token); - } - - StickVisualizer.PollTokenSource.Dispose(); - } - public void OnParentModelChanged() { IsLeft = ParentModel.IsLeft; diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index 7bb082893..f25effbce 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -48,7 +48,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input private int _controller; private string _controllerImage; private int _device; - [ObservableProperty] private object _configViewModel; + private object _configViewModel; [ObservableProperty] private string _profileName; private bool _isLoaded; @@ -67,6 +67,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input OnPropertiesChanged(nameof(HasLed), nameof(CanClearLed)); } } + public StickVisualizer VisualStick { get; private set; } public ObservableCollection PlayerIndexes { get; set; } public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; } @@ -87,6 +88,19 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public bool IsModified { get; set; } public event Action NotifyChangesEvent; + public object ConfigViewModel + { + get => _configViewModel; + set + { + _configViewModel = value; + + VisualStick.UpdateConfig(value); + + OnPropertyChanged(); + } + } + public PlayerIndex PlayerIdChoose { get => _playerIdChoose; @@ -262,6 +276,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input Devices = new ObservableCollection<(DeviceType Type, string Id, string Name)>(); ProfilesList = new AvaloniaList(); DeviceList = new AvaloniaList(); + VisualStick = new StickVisualizer(this); ControllerImage = ProControllerResource; @@ -282,12 +297,12 @@ namespace Ryujinx.Ava.UI.ViewModels.Input if (Config is StandardKeyboardInputConfig keyboardInputConfig) { - ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig)); + ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig), VisualStick); } if (Config is StandardControllerInputConfig controllerInputConfig) { - ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig)); + ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig), VisualStick); } } @@ -884,10 +899,10 @@ namespace Ryujinx.Ava.UI.ViewModels.Input _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; - (ConfigViewModel as ControllerInputViewModel)?.StickVisualizer.PollTokenSource.Cancel(); - _mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates(); + VisualStick.Dispose(); + SelectedGamepad?.Dispose(); AvaloniaKeyboardDriver.Dispose(); diff --git a/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs index 8f325e67c..bab8db7ce 100644 --- a/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs @@ -1,28 +1,11 @@ using Avalonia.Svg.Skia; using CommunityToolkit.Mvvm.ComponentModel; using Ryujinx.Ava.UI.Models.Input; -using Ryujinx.Input; -using System.Threading; -using System.Threading.Tasks; namespace Ryujinx.Ava.UI.ViewModels.Input { public partial class KeyboardInputViewModel : BaseModel { - private (float, float) _leftBuffer = (0, 0); - private (float, float) _rightBuffer = (0, 0); - private StickVisualizer _stickVisualizer; - public StickVisualizer StickVisualizer - { - get => _stickVisualizer; - set - { - _stickVisualizer = value; - - OnPropertyChanged(); - } - } - private KeyboardInputConfig _config; public KeyboardInputConfig Config { @@ -30,7 +13,18 @@ namespace Ryujinx.Ava.UI.ViewModels.Input set { _config = value; - StickVisualizer.UpdateConfig(_config); + + OnPropertyChanged(); + } + } + + private StickVisualizer _visualizer; + public StickVisualizer Visualizer + { + get => _visualizer; + set + { + _visualizer = value; OnPropertyChanged(); } @@ -66,70 +60,13 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public readonly InputViewModel ParentModel; - public KeyboardInputViewModel(InputViewModel model, KeyboardInputConfig config) + public KeyboardInputViewModel(InputViewModel model, KeyboardInputConfig config, StickVisualizer visualizer) { ParentModel = model; + Visualizer = visualizer; model.NotifyChangesEvent += OnParentModelChanged; OnParentModelChanged(); - _stickVisualizer = new(); Config = config; - - StickVisualizer.PollToken = StickVisualizer.PollTokenSource.Token; - - Task.Run(() => PollKeyboard(StickVisualizer.PollToken)); - } - - private async Task PollKeyboard(CancellationToken token) - { - while (!token.IsCancellationRequested) - { - if (ParentModel.IsKeyboard) - { - IKeyboard keyboard = (IKeyboard)ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); - var snap = keyboard.GetKeyboardStateSnapshot(); - - if (snap.IsPressed((Key)Config.LeftStickRight)) - { - _leftBuffer.Item1 += 1; - } - if (snap.IsPressed((Key)Config.LeftStickLeft)) - { - _leftBuffer.Item1 -= 1; - } - if (snap.IsPressed((Key)Config.LeftStickUp)) - { - _leftBuffer.Item2 += 1; - } - if (snap.IsPressed((Key)Config.LeftStickDown)) - { - _leftBuffer.Item2 -= 1; - } - - if (snap.IsPressed((Key)Config.RightStickRight)) - { - _rightBuffer.Item1 += 1; - } - if (snap.IsPressed((Key)Config.RightStickLeft)) - { - _rightBuffer.Item1 -= 1; - } - if (snap.IsPressed((Key)Config.RightStickUp)) - { - _rightBuffer.Item2 += 1; - } - if (snap.IsPressed((Key)Config.RightStickDown)) - { - _rightBuffer.Item2 -= 1; - } - - StickVisualizer.UiStickLeft = _leftBuffer; - StickVisualizer.UiStickRight = _rightBuffer; - } - - await Task.Delay(StickVisualizer.DrawStickPollRate, token); - } - - StickVisualizer.PollTokenSource.Dispose(); } public void OnParentModelChanged() diff --git a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml index 9740d9dcc..2c96d8cae 100644 --- a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml +++ b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml @@ -334,72 +334,72 @@ BorderBrush="{DynamicResource ThemeControlBorderColor}" BorderThickness="1" CornerRadius="5" - Height="{Binding StickVisualizer.UiStickBorderSize}" - Width="{Binding StickVisualizer.UiStickBorderSize}" + Height="{Binding Visualizer.UiStickBorderSize}" + Width="{Binding Visualizer.UiStickBorderSize}" IsVisible="{Binding IsLeft}"> + Height="{Binding Visualizer.UiCanvasSize}" + Width="{Binding Visualizer.UiCanvasSize}"> + Width="{Binding Visualizer.UiCanvasSize}" + Height="{Binding Visualizer.UiCanvasSize}"/> + Height="{Binding Visualizer.UiDeadzoneLeft}" + Width="{Binding Visualizer.UiDeadzoneLeft}"/> + Width="{Binding Visualizer.UiStickCircumference}" + Height="{Binding Visualizer.UiStickCircumference}" + Canvas.Bottom="{Binding Visualizer.UiStickLeftY}" + Canvas.Left="{Binding Visualizer.UiStickLeftX}" /> + Height="{Binding Visualizer.UiCanvasSize}" + Width="{Binding Visualizer.UiCanvasSize}"> + Width="{Binding Visualizer.UiCanvasSize}" + Height="{Binding Visualizer.UiCanvasSize}"/> + Height="{Binding Visualizer.UiDeadzoneRight}" + Width="{Binding Visualizer.UiDeadzoneRight}"/> + Width="{Binding Visualizer.UiStickCircumference}" + Height="{Binding Visualizer.UiStickCircumference}" + Canvas.Bottom="{Binding Visualizer.UiStickRightY}" + Canvas.Left="{Binding Visualizer.UiStickRightX}" /> diff --git a/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml index 2ffbce07f..afe0e7700 100644 --- a/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml +++ b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml @@ -327,72 +327,60 @@ BorderBrush="{DynamicResource ThemeControlBorderColor}" BorderThickness="1" CornerRadius="5" - Height="{Binding StickVisualizer.UiStickBorderSize}" - Width="{Binding StickVisualizer.UiStickBorderSize}" + Height="{Binding Visualizer.UiStickBorderSize}" + Width="{Binding Visualizer.UiStickBorderSize}" IsVisible="{Binding IsLeft}"> + Height="{Binding Visualizer.UiCanvasSize}" + Width="{Binding Visualizer.UiCanvasSize}"> - + Width="{Binding Visualizer.UiCanvasSize}" + Height="{Binding Visualizer.UiCanvasSize}"/> + Width="{Binding Visualizer.UiStickCircumference}" + Height="{Binding Visualizer.UiStickCircumference}" + Canvas.Bottom="{Binding Visualizer.UiStickLeftY}" + Canvas.Left="{Binding Visualizer.UiStickLeftX}" /> + Height="{Binding Visualizer.UiCanvasSize}" + Width="{Binding Visualizer.UiCanvasSize}"> - + Width="{Binding Visualizer.UiCanvasSize}" + Height="{Binding Visualizer.UiCanvasSize}"/> + Width="{Binding Visualizer.UiStickCircumference}" + Height="{Binding Visualizer.UiStickCircumference}" + Canvas.Bottom="{Binding Visualizer.UiStickRightY}" + Canvas.Left="{Binding Visualizer.UiStickRightX}" />