Stick Visualizer #579

Merged
GreemDev merged 28 commits from xeyes into master 2025-03-03 02:43:31 +00:00
3 changed files with 145 additions and 7 deletions
Showing only changes of commit d2bb580aea - Show all commits

View File

@ -3,8 +3,12 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Views.Input; using Ryujinx.Ava.UI.Views.Input;
using Ryujinx.Input;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.ViewModels.Input namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
@ -12,6 +16,19 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
[ObservableProperty] private GamepadInputConfig _config; [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; private bool _isLeft;
public bool IsLeft public bool IsLeft
{ {
@ -42,12 +59,50 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public InputViewModel ParentModel { get; } 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) public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config)
{ {
ParentModel = model; ParentModel = model;
model.NotifyChangesEvent += OnParentModelChanged; model.NotifyChangesEvent += OnParentModelChanged;
OnParentModelChanged(); OnParentModelChanged();
Config = config; Config = config;
_pollTokenSource = new();
_pollToken = _pollTokenSource.Token;
Task.Run(() => PollSticks(_pollToken));
} }
public async void ShowMotionConfig() public async void ShowMotionConfig()
@ -70,6 +125,24 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
ParentModel.SelectedGamepad.SetLed(Config.LedColor.ToUInt32()); 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() public void OnParentModelChanged()
{ {
IsLeft = ParentModel.IsLeft; IsLeft = ParentModel.IsLeft;

View File

@ -884,6 +884,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;
(ConfigViewModel as ControllerInputViewModel)._pollTokenSource?.Cancel();
_mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates(); _mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates();
SelectedGamepad?.Dispose(); SelectedGamepad?.Dispose();

View File

@ -319,12 +319,75 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
<!-- Controller Picture --> <!-- Controller Picture -->
<Image <Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
Margin="0,10" Margin="0,10"
MaxHeight="300" MinHeight="90">
HorizontalAlignment="Stretch" <StackPanel
VerticalAlignment="Stretch" Margin="10"
Source="{Binding Image}" /> Orientation="Horizontal"
Spacing="20"
HorizontalAlignment="Center">
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
Height="100"
Width="100">
<Canvas
Background="{DynamicResource ThemeBackgroundColor}"
Height="100"
Width="100">
<Ellipse
Fill="Gray"
Stroke="Blue"
StrokeThickness="1"
Width="25"
Height="25"
Canvas.Bottom="{Binding UiStickLeftY}"
Canvas.Left="{Binding UiStickLeftX}" />
<Ellipse
Stroke="Black"
StrokeThickness="1"
StrokeDashOffset="2"
Width="96"
Height="96"
Canvas.Bottom="2"
Canvas.Left="2" />
</Canvas>
</Border>
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
Height="100"
Width="100">
<Canvas
Background="{DynamicResource ThemeBackgroundColor}"
Height="100"
Width="100">
<Ellipse
Fill="Gray"
Stroke="Blue"
StrokeThickness="1"
Width="25"
Height="25"
Canvas.Bottom="{Binding UiStickRightY}"
Canvas.Left="{Binding UiStickRightX}"/>
<Ellipse
Stroke="Black"
StrokeThickness="1"
StrokeDashOffset="2"
Width="96"
Height="96"
Canvas.Bottom="2"
Canvas.Left="2" />
</Canvas>
</Border>
</StackPanel>
</Border>
<Border <Border
BorderBrush="{DynamicResource ThemeControlBorderColor}" BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1" BorderThickness="1"