diff --git a/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs
index d291f09a0..863d350fb 100644
--- a/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs
@@ -3,14 +3,31 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
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
{
[ObservableProperty] private GamepadInputConfig _config;
+
+ private const int StickUiPollMs = 50; // Milliseconds per poll.
+ private const float CanvasCenterOffset = 75f/2f;
+ private const int StickScaleFactor = 30;
+
+ private IGamepad _selectedGamepad;
+
+ // Offset from origin for UI stick visualization.
+ private (float, float) _uiStickLeft;
+ private (float, float) _uiStickRight;
+
+ internal CancellationTokenSource _pollTokenSource = new();
+ private CancellationToken _pollToken;
private bool _isLeft;
public bool IsLeft
@@ -39,8 +56,41 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool HasSides => IsLeft ^ IsRight;
[ObservableProperty] private SvgImage _image;
-
+
public InputViewModel ParentModel { get; }
+
+ public (float, float) UiStickLeft
+ {
+ get => (_uiStickLeft.Item1 * StickScaleFactor, _uiStickLeft.Item2 * StickScaleFactor);
+ set
+ {
+ _uiStickLeft = value;
+
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(UiStickRightX));
+ OnPropertyChanged(nameof(UiStickRightY));
+ }
+ }
+
+ public (float, float) UiStickRight
+ {
+ get => (_uiStickRight.Item1 * StickScaleFactor, _uiStickRight.Item2 * StickScaleFactor);
+ set
+ {
+ _uiStickRight = value;
+
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(UiStickLeftX));
+ OnPropertyChanged(nameof(UiStickLeftY));
+ }
+ }
+
+ public float canvasCenter => CanvasCenterOffset;
+
+ public float UiStickLeftX => UiStickLeft.Item1 + CanvasCenterOffset;
+ public float UiStickLeftY => UiStickLeft.Item2 + CanvasCenterOffset;
+ public float UiStickRightX => UiStickRight.Item1 + CanvasCenterOffset;
+ public float UiStickRightY => UiStickRight.Item2 + CanvasCenterOffset;
public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config)
{
@@ -48,6 +98,11 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
model.NotifyChangesEvent += OnParentModelChanged;
OnParentModelChanged();
Config = config;
+
+ _pollTokenSource = new();
+ _pollToken = _pollTokenSource.Token;
+
+ Task.Run(() => PollSticks(_pollToken));
}
public async void ShowMotionConfig()
@@ -59,7 +114,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{
await RumbleInputView.Show(this);
}
-
+
public RelayCommand LedDisabledChanged => Commands.Create(() =>
{
if (!Config.EnableLedChanging) return;
@@ -70,6 +125,24 @@ 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)
+ {
+ UiStickLeft = _selectedGamepad.GetStick(StickInputId.Left);
+ UiStickRight = _selectedGamepad.GetStick(StickInputId.Right);
+ }
+
+ await Task.Delay(StickUiPollMs);
+ }
+
+ _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 c59ec540c..51fbe01fb 100644
--- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs
@@ -884,6 +884,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;
+ (ConfigViewModel as ControllerInputViewModel)._pollTokenSource?.Cancel();
+
_mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates();
SelectedGamepad?.Dispose();
diff --git a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml
index 1662f4a3d..052216397 100644
--- a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml
+++ b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml
@@ -319,12 +319,75 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
-
+ MinHeight="90">
+
+
+
+
+
+
+
+
+