Stick Visualizer #579
@ -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;
|
||||||
|
@ -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();
|
||||||
|
@ -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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user