diff --git a/src/Ryujinx/UI/Models/Input/StickVisualizer.cs b/src/Ryujinx/UI/Models/Input/StickVisualizer.cs index 83cfd4766f..b7e9ec331b 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 4111aa1045..c8472a8258 100644 --- a/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs @@ -1,29 +1,11 @@ using Avalonia.Svg.Skia; -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 class ControllerInputViewModel : BaseModel { - private IGamepad _selectedGamepad; - - private StickVisualizer _stickVisualizer; - public StickVisualizer StickVisualizer - { - get => _stickVisualizer; - set - { - _stickVisualizer = value; - - OnPropertyChanged(); - } - } - private GamepadInputConfig _config; public GamepadInputConfig Config { @@ -31,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(); } @@ -76,17 +69,13 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public readonly InputViewModel ParentModel; - 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() @@ -99,24 +88,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input await RumbleInputView.Show(this); } - 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 e0649af572..c738d2c9ca 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -56,6 +56,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public IGamepadDriver AvaloniaKeyboardDriver { get; } public IGamepad SelectedGamepad { get; private set; } + public StickVisualizer VisualStick { get; private set; } public ObservableCollection PlayerIndexes { get; set; } public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; } @@ -80,6 +81,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input { _configViewModel = value; + VisualStick.UpdateConfig(value); + OnPropertyChanged(); } } @@ -261,6 +264,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; @@ -281,12 +285,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); } } @@ -879,10 +883,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 e6902a2dc1..9096cd845d 100644 --- a/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs @@ -1,27 +1,10 @@ using Avalonia.Svg.Skia; using Ryujinx.Ava.UI.Models.Input; -using Ryujinx.Input; -using System.Threading; -using System.Threading.Tasks; namespace Ryujinx.Ava.UI.ViewModels.Input { public 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 { @@ -29,7 +12,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(); } @@ -74,70 +68,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 082fb3ce40..a888c5a282 100644 --- a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml +++ b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml @@ -333,72 +333,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 b7e9f4f00b..286d5df22b 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}" /> @@ -751,4 +739,4 @@ - \ No newline at end of file +