diff --git a/src/Ryujinx/UI/Models/Input/StickVisualizer.cs b/src/Ryujinx/UI/Models/Input/StickVisualizer.cs
new file mode 100644
index 0000000000..83cfd4766f
--- /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 adc23e96d2..4111aa1045 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 d1e8b47c51..e0649af572 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 0b530eb094..e6902a2dc1 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 b18eb882fe..082fb3ce40 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}">
diff --git a/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml
index e4566f463d..b7e9f4f00b 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">
+
+
+
+
+
+
+
+
+