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"> + + + + + + + + + + + + + + +