Move most logic into new StickVisualizer class and add keyboard support.

This commit is contained in:
MutantAura 2024-05-31 01:42:33 +01:00
parent 11a2ea4843
commit de08974f82
6 changed files with 332 additions and 114 deletions

View file

@ -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;
}
}
}

View file

@ -3,7 +3,6 @@ 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 Ryujinx.Input;
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -11,23 +10,19 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
public class ControllerInputViewModel : BaseModel 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 IGamepad _selectedGamepad;
private float _vectorLength; private StickVisualizer _stickVisualizer;
private float _vectorMultiplier; public StickVisualizer StickVisualizer
{
get => _stickVisualizer;
set
{
_stickVisualizer = value;
internal CancellationTokenSource _pollTokenSource = new(); OnPropertyChanged();
private readonly CancellationToken _pollToken; }
}
private GamepadInputConfig _config; private GamepadInputConfig _config;
public GamepadInputConfig Config public GamepadInputConfig Config
@ -36,6 +31,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
set set
{ {
_config = value; _config = value;
StickVisualizer.UpdateConfig(Config);
OnPropertyChanged(); 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 readonly InputViewModel ParentModel;
public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config) public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config)
@ -126,12 +81,12 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
ParentModel = model; ParentModel = model;
model.NotifyChangesEvent += OnParentModelChanged; model.NotifyChangesEvent += OnParentModelChanged;
OnParentModelChanged(); OnParentModelChanged();
_stickVisualizer = new();
Config = config; Config = config;
_pollTokenSource = new(); StickVisualizer.PollToken = StickVisualizer.PollTokenSource.Token;
_pollToken = _pollTokenSource.Token;
Task.Run(() => PollSticks(_pollToken)); Task.Run(() => PollSticks(StickVisualizer.PollToken));
} }
public async void ShowMotionConfig() public async void ShowMotionConfig()
@ -152,30 +107,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
if (_selectedGamepad != null && _selectedGamepad is not AvaloniaKeyboard) if (_selectedGamepad != null && _selectedGamepad is not AvaloniaKeyboard)
{ {
UiStickLeft = _selectedGamepad.GetStick(StickInputId.Left); StickVisualizer.UiStickLeft = _selectedGamepad.GetStick(StickInputId.Left);
UiStickRight = _selectedGamepad.GetStick(StickInputId.Right); StickVisualizer.UiStickRight = _selectedGamepad.GetStick(StickInputId.Right);
} }
await Task.Delay(DrawStickPollRate, token); await Task.Delay(StickVisualizer.DrawStickPollRate, token);
} }
_pollTokenSource.Dispose(); StickVisualizer.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;
} }
public void OnParentModelChanged() public void OnParentModelChanged()

View file

@ -879,7 +879,7 @@ 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(); (ConfigViewModel as ControllerInputViewModel)?.StickVisualizer.PollTokenSource.Cancel();
_mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates(); _mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates();

View file

@ -1,10 +1,27 @@
using Avalonia.Svg.Skia; using Avalonia.Svg.Skia;
using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Input;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.ViewModels.Input namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
public class KeyboardInputViewModel : BaseModel 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; private KeyboardInputConfig _config;
public KeyboardInputConfig Config public KeyboardInputConfig Config
{ {
@ -12,6 +29,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
set set
{ {
_config = value; _config = value;
StickVisualizer.UpdateConfig(_config);
OnPropertyChanged(); OnPropertyChanged();
} }
} }
@ -60,7 +79,65 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
ParentModel = model; ParentModel = model;
model.NotifyChangesEvent += OnParentModelChanged; model.NotifyChangesEvent += OnParentModelChanged;
OnParentModelChanged(); OnParentModelChanged();
_stickVisualizer = new();
Config = config; 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() public void OnParentModelChanged()

View file

@ -333,72 +333,72 @@
BorderBrush="{DynamicResource ThemeControlBorderColor}" BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1" BorderThickness="1"
CornerRadius="5" CornerRadius="5"
Height="{Binding UiStickBorderSize}" Height="{Binding StickVisualizer.UiStickBorderSize}"
Width="{Binding UiStickBorderSize}" Width="{Binding StickVisualizer.UiStickBorderSize}"
IsVisible="{Binding IsLeft}"> IsVisible="{Binding IsLeft}">
<Canvas <Canvas
Background="{DynamicResource ThemeBackgroundColor}" Background="{DynamicResource ThemeBackgroundColor}"
Height="{Binding UiCanvasSize}" Height="{Binding StickVisualizer.UiCanvasSize}"
Width="{Binding UiCanvasSize}"> Width="{Binding StickVisualizer.UiCanvasSize}">
<Grid <Grid
Height="{Binding UiCanvasSize}" Height="{Binding StickVisualizer.UiCanvasSize}"
Width="{Binding UiCanvasSize}" Width="{Binding StickVisualizer.UiCanvasSize}"
Background="{DynamicResource ThemeBackgroundColor}"> Background="{DynamicResource ThemeBackgroundColor}">
<Ellipse <Ellipse
HorizontalAlignment="Center" HorizontalAlignment="Center"
Stroke="Black" Stroke="Black"
StrokeThickness="1" StrokeThickness="1"
Width="{Binding UiCanvasSize}" Width="{Binding StickVisualizer.UiCanvasSize}"
Height="{Binding UiCanvasSize}"/> Height="{Binding StickVisualizer.UiCanvasSize}"/>
<Ellipse <Ellipse
HorizontalAlignment="Center" HorizontalAlignment="Center"
Fill="Gray" Fill="Gray"
Opacity="100" Opacity="100"
Height="{Binding UiDeadzoneLeft}" Height="{Binding StickVisualizer.UiDeadzoneLeft}"
Width="{Binding UiDeadzoneLeft}"/> Width="{Binding StickVisualizer.UiDeadzoneLeft}"/>
</Grid> </Grid>
<Ellipse <Ellipse
Fill="Red" Fill="Red"
Width="{Binding UiStickCircumference}" Width="{Binding StickVisualizer.UiStickCircumference}"
Height="{Binding UiStickCircumference}" Height="{Binding StickVisualizer.UiStickCircumference}"
Canvas.Bottom="{Binding UiStickLeftY}" Canvas.Bottom="{Binding StickVisualizer.UiStickLeftY}"
Canvas.Left="{Binding UiStickLeftX}" /> Canvas.Left="{Binding StickVisualizer.UiStickLeftX}" />
</Canvas> </Canvas>
</Border> </Border>
<Border <Border
BorderBrush="{DynamicResource ThemeControlBorderColor}" BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1" BorderThickness="1"
CornerRadius="5" CornerRadius="5"
Height="{Binding UiStickBorderSize}" Height="{Binding StickVisualizer.UiStickBorderSize}"
Width="{Binding UiStickBorderSize}" Width="{Binding StickVisualizer.UiStickBorderSize}"
IsVisible="{Binding IsRight}"> IsVisible="{Binding IsRight}">
<Canvas <Canvas
Background="{DynamicResource ThemeBackgroundColor}" Background="{DynamicResource ThemeBackgroundColor}"
Height="{Binding UiCanvasSize}" Height="{Binding StickVisualizer.UiCanvasSize}"
Width="{Binding UiCanvasSize}"> Width="{Binding StickVisualizer.UiCanvasSize}">
<Grid <Grid
Height="{Binding UiCanvasSize}" Height="{Binding StickVisualizer.UiCanvasSize}"
Width="{Binding UiCanvasSize}" Width="{Binding StickVisualizer.UiCanvasSize}"
Background="{DynamicResource ThemeBackgroundColor}"> Background="{DynamicResource ThemeBackgroundColor}">
<Ellipse <Ellipse
HorizontalAlignment="Center" HorizontalAlignment="Center"
Stroke="Black" Stroke="Black"
StrokeThickness="1" StrokeThickness="1"
Width="{Binding UiCanvasSize}" Width="{Binding StickVisualizer.UiCanvasSize}"
Height="{Binding UiCanvasSize}"/> Height="{Binding StickVisualizer.UiCanvasSize}"/>
<Ellipse <Ellipse
HorizontalAlignment="Center" HorizontalAlignment="Center"
Fill="Gray" Fill="Gray"
Opacity="100" Opacity="100"
Height="{Binding UiDeadzoneRight}" Height="{Binding StickVisualizer.UiDeadzoneRight}"
Width="{Binding UiDeadzoneRight}"/> Width="{Binding StickVisualizer.UiDeadzoneRight}"/>
</Grid> </Grid>
<Ellipse <Ellipse
Fill="Red" Fill="Red"
Width="{Binding UiStickCircumference}" Width="{Binding StickVisualizer.UiStickCircumference}"
Height="{Binding UiStickCircumference}" Height="{Binding StickVisualizer.UiStickCircumference}"
Canvas.Bottom="{Binding UiStickRightY}" Canvas.Bottom="{Binding StickVisualizer.UiStickRightY}"
Canvas.Left="{Binding UiStickRightX}" /> Canvas.Left="{Binding StickVisualizer.UiStickRightX}" />
</Canvas> </Canvas>
</Border> </Border>
</StackPanel> </StackPanel>

View file

@ -312,12 +312,91 @@
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="{Binding StickVisualizer.UiStickBorderSize}"
Width="{Binding StickVisualizer.UiStickBorderSize}"
IsVisible="{Binding IsLeft}">
<Canvas
Background="{DynamicResource ThemeBackgroundColor}"
Height="{Binding StickVisualizer.UiCanvasSize}"
Width="{Binding StickVisualizer.UiCanvasSize}">
<Grid
Height="{Binding StickVisualizer.UiCanvasSize}"
Width="{Binding StickVisualizer.UiCanvasSize}"
Background="{DynamicResource ThemeBackgroundColor}">
<Ellipse
HorizontalAlignment="Center"
Stroke="Black"
StrokeThickness="1"
Width="{Binding StickVisualizer.UiCanvasSize}"
Height="{Binding StickVisualizer.UiCanvasSize}"/>
<Ellipse
HorizontalAlignment="Center"
Fill="Gray"
Opacity="100"
Height="{Binding StickVisualizer.UiDeadzoneLeft}"
Width="{Binding StickVisualizer.UiDeadzoneLeft}"/>
</Grid>
<Ellipse
Fill="Red"
Width="{Binding StickVisualizer.UiStickCircumference}"
Height="{Binding StickVisualizer.UiStickCircumference}"
Canvas.Bottom="{Binding StickVisualizer.UiStickLeftY}"
Canvas.Left="{Binding StickVisualizer.UiStickLeftX}" />
</Canvas>
</Border>
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
Height="{Binding StickVisualizer.UiStickBorderSize}"
Width="{Binding StickVisualizer.UiStickBorderSize}"
IsVisible="{Binding IsRight}">
<Canvas
Background="{DynamicResource ThemeBackgroundColor}"
Height="{Binding StickVisualizer.UiCanvasSize}"
Width="{Binding StickVisualizer.UiCanvasSize}">
<Grid
Height="{Binding StickVisualizer.UiCanvasSize}"
Width="{Binding StickVisualizer.UiCanvasSize}"
Background="{DynamicResource ThemeBackgroundColor}">
<Ellipse
HorizontalAlignment="Center"
Stroke="Black"
StrokeThickness="1"
Width="{Binding StickVisualizer.UiCanvasSize}"
Height="{Binding StickVisualizer.UiCanvasSize}"/>
<Ellipse
HorizontalAlignment="Center"
Fill="Gray"
Opacity="100"
Height="{Binding StickVisualizer.UiDeadzoneRight}"
Width="{Binding StickVisualizer.UiDeadzoneRight}"/>
</Grid>
<Ellipse
Fill="Red"
Width="{Binding StickVisualizer.UiStickCircumference}"
Height="{Binding StickVisualizer.UiStickCircumference}"
Canvas.Bottom="{Binding StickVisualizer.UiStickRightY}"
Canvas.Left="{Binding StickVisualizer.UiStickRightX}" />
</Canvas>
</Border>
</StackPanel>
</Border>
<Border <Border
BorderBrush="{DynamicResource ThemeControlBorderColor}" BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1" BorderThickness="1"