diff --git a/src/Ryujinx/UI/Models/Input/StickVisualizer.cs b/src/Ryujinx/UI/Models/Input/StickVisualizer.cs new file mode 100644 index 000000000..83cfd4766 --- /dev/null +++ b/src/Ryujinx/UI/Models/Input/StickVisualizer.cs @@ -0,0 +1,123 @@ +using Ryujinx.Ava.UI.ViewModels; +using System; +using System.Threading; + +namespace Ryujinx.Ava.UI.Models.Input +{ + public class StickVisualizer : BaseModel + { + public const int DrawStickPollRate = 50; // Milliseconds per poll. + public const int DrawStickCircumference = 5; + public const float DrawStickScaleFactor = DrawStickCanvasCenter; + public const int DrawStickCanvasSize = 100; + public const int DrawStickBorderSize = DrawStickCanvasSize + 5; + public const float DrawStickCanvasCenter = (DrawStickCanvasSize - DrawStickCircumference) / 2; + public const float MaxVectorLength = DrawStickCanvasSize / 2; + + public CancellationTokenSource PollTokenSource = new(); + public CancellationToken PollToken; + + private static float _vectorLength; + private static float _vectorMultiplier; + + private GamepadInputConfig _gamepadConfig; + public GamepadInputConfig GamepadConfig + { + get => _gamepadConfig; + set + { + _gamepadConfig = value; + + OnPropertyChanged(); + } + } + + private KeyboardInputConfig _keyboardConfig; + public KeyboardInputConfig KeyboardConfig + { + get => _keyboardConfig; + set + { + _keyboardConfig = value; + + OnPropertyChanged(); + } + } + + 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 float UiStickLeftX => ClampVector(UiStickLeft).Item1; + public float UiStickLeftY => ClampVector(UiStickLeft).Item2; + public float UiStickRightX => ClampVector(UiStickRight).Item1; + public float UiStickRightY => ClampVector(UiStickRight).Item2; + + public int UiStickCircumference => DrawStickCircumference; + public int UiCanvasSize => DrawStickCanvasSize; + public int UiStickBorderSize => DrawStickBorderSize; + + public float? UiDeadzoneLeft => _gamepadConfig?.DeadzoneLeft * DrawStickCanvasSize - DrawStickCircumference; + public float? UiDeadzoneRight => _gamepadConfig?.DeadzoneRight * DrawStickCanvasSize - DrawStickCircumference; + + public void UpdateConfig(object config) + { + if (config is GamepadInputConfig padConfig) + { + GamepadConfig = padConfig; + + return; + } + else if (config is KeyboardInputConfig keyConfig) + { + KeyboardConfig = keyConfig; + + return; + } + + throw new ArgumentException($"Invalid configuration: {config}"); + } + + public static (float, float) ClampVector((float, float) vect) + { + _vectorMultiplier = 1; + _vectorLength = MathF.Sqrt((vect.Item1 * vect.Item1) + (vect.Item2 * vect.Item2)); + + if (_vectorLength > MaxVectorLength) + { + _vectorMultiplier = MaxVectorLength / _vectorLength; + } + + vect.Item1 = vect.Item1 * _vectorMultiplier + DrawStickCanvasCenter; + vect.Item2 = vect.Item2 * _vectorMultiplier + DrawStickCanvasCenter; + + return vect; + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs index adc23e96d..4111aa104 100644 --- a/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs @@ -3,7 +3,6 @@ using Ryujinx.Ava.Input; using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Views.Input; using Ryujinx.Input; -using System; using System.Threading; using System.Threading.Tasks; @@ -11,23 +10,19 @@ namespace Ryujinx.Ava.UI.ViewModels.Input { public 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 float _vectorLength; - private float _vectorMultiplier; + private StickVisualizer _stickVisualizer; + public StickVisualizer StickVisualizer + { + get => _stickVisualizer; + set + { + _stickVisualizer = value; - internal CancellationTokenSource _pollTokenSource = new(); - private readonly CancellationToken _pollToken; + OnPropertyChanged(); + } + } private GamepadInputConfig _config; public GamepadInputConfig Config @@ -36,6 +31,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input set { _config = value; + StickVisualizer.UpdateConfig(Config); + OnPropertyChanged(); } } @@ -77,48 +74,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input } } - 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 readonly InputViewModel ParentModel; public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config) @@ -126,12 +81,12 @@ namespace Ryujinx.Ava.UI.ViewModels.Input ParentModel = model; model.NotifyChangesEvent += OnParentModelChanged; OnParentModelChanged(); + _stickVisualizer = new(); Config = config; - _pollTokenSource = new(); - _pollToken = _pollTokenSource.Token; + StickVisualizer.PollToken = StickVisualizer.PollTokenSource.Token; - Task.Run(() => PollSticks(_pollToken)); + Task.Run(() => PollSticks(StickVisualizer.PollToken)); } public async void ShowMotionConfig() @@ -152,30 +107,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input if (_selectedGamepad != null && _selectedGamepad is not AvaloniaKeyboard) { - UiStickLeft = _selectedGamepad.GetStick(StickInputId.Left); - UiStickRight = _selectedGamepad.GetStick(StickInputId.Right); + StickVisualizer.UiStickLeft = _selectedGamepad.GetStick(StickInputId.Left); + StickVisualizer.UiStickRight = _selectedGamepad.GetStick(StickInputId.Right); } - await Task.Delay(DrawStickPollRate, token); + await Task.Delay(StickVisualizer.DrawStickPollRate, token); } - _pollTokenSource.Dispose(); - } - - private (float, float) ClampVector((float, float) vect) - { - _vectorMultiplier = 1; - _vectorLength = MathF.Sqrt((vect.Item1 * vect.Item1) + (vect.Item2 * vect.Item2)); - - if (_vectorLength > MaxVectorLength) - { - _vectorMultiplier = MaxVectorLength / _vectorLength; - } - - vect.Item1 = vect.Item1 * _vectorMultiplier + DrawStickCanvasCenter; - vect.Item2 = vect.Item2 * _vectorMultiplier + DrawStickCanvasCenter; - - return vect; + StickVisualizer.PollTokenSource.Dispose(); } public void OnParentModelChanged() diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index d1e8b47c5..e0649af57 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -879,7 +879,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; - (ConfigViewModel as ControllerInputViewModel)?._pollTokenSource.Cancel(); + (ConfigViewModel as ControllerInputViewModel)?.StickVisualizer.PollTokenSource.Cancel(); _mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates(); diff --git a/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs index 0b530eb09..e6902a2dc 100644 --- a/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs @@ -1,10 +1,27 @@ 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 { @@ -12,6 +29,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input set { _config = value; + StickVisualizer.UpdateConfig(_config); + OnPropertyChanged(); } } @@ -60,7 +79,65 @@ namespace Ryujinx.Ava.UI.ViewModels.Input ParentModel = model; 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 b18eb882f..082fb3ce4 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 UiStickBorderSize}" - Width="{Binding UiStickBorderSize}" + Height="{Binding StickVisualizer.UiStickBorderSize}" + Width="{Binding StickVisualizer.UiStickBorderSize}" IsVisible="{Binding IsLeft}"> + Height="{Binding StickVisualizer.UiCanvasSize}" + Width="{Binding StickVisualizer.UiCanvasSize}"> + Width="{Binding StickVisualizer.UiCanvasSize}" + Height="{Binding StickVisualizer.UiCanvasSize}"/> + Height="{Binding StickVisualizer.UiDeadzoneLeft}" + Width="{Binding StickVisualizer.UiDeadzoneLeft}"/> + Width="{Binding StickVisualizer.UiStickCircumference}" + Height="{Binding StickVisualizer.UiStickCircumference}" + Canvas.Bottom="{Binding StickVisualizer.UiStickLeftY}" + Canvas.Left="{Binding StickVisualizer.UiStickLeftX}" /> + Height="{Binding StickVisualizer.UiCanvasSize}" + Width="{Binding StickVisualizer.UiCanvasSize}"> + Width="{Binding StickVisualizer.UiCanvasSize}" + Height="{Binding StickVisualizer.UiCanvasSize}"/> + Height="{Binding StickVisualizer.UiDeadzoneRight}" + Width="{Binding StickVisualizer.UiDeadzoneRight}"/> + Width="{Binding StickVisualizer.UiStickCircumference}" + Height="{Binding StickVisualizer.UiStickCircumference}" + Canvas.Bottom="{Binding StickVisualizer.UiStickRightY}" + Canvas.Left="{Binding StickVisualizer.UiStickRightX}" /> diff --git a/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml index e4566f463..b7e9f4f00 100644 --- a/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml +++ b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml @@ -312,12 +312,91 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> - + MinHeight="90"> + + + + + + + + + + + + + + + + + + + + +