aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Ava/Input
diff options
context:
space:
mode:
authorEmmanuel Hansen <emmausssss@gmail.com>2022-05-15 11:30:15 +0000
committerGitHub <noreply@github.com>2022-05-15 13:30:15 +0200
commitdeb99d2cae3e80bdf70cb52c6c160094dc7c9292 (patch)
treee60f44d1b4bd45bbf36fcfa750fb99787febfdbe /Ryujinx.Ava/Input
parent9ba73ffbe5f78c0403cf102b95768f388da05122 (diff)
Avalonia UI - Part 1 (#3270)
* avalonia part 1 * remove vulkan ui backend * move ui common files to ui common project * get name for oading screen from device * rebase. * review 1 * review 1.1 * review * cleanup * addressed review * use cancellation token * review * review * rebased * cancel library loading when closing window * remove star image, use fonticon instead * delete render control frame buffer when game ends. change position of fav star * addressed @Thog review * ensure the right ui is downloaded in updates * fix crash when showing not supported dialog during controller request * add prefix to artifact names * Auto-format Avalonia project * Fix input * Fix build, simplify app disposal * remove nv stutter thread * addressed review * add missing change * maintain window size if new size is zero length * add game, handheld, docked to local * reverse scale main window * Update de_DE.json * Update de_DE.json * Update de_DE.json * Update italian json * Update it_IT.json * let render timer poll with no wait * remove unused code * more unused code * enabled tiered compilation and trimming * check if window event is not closed before signaling * fix atmospher case * locale fix * locale fix * remove explicit tiered compilation declarations * Remove ) it_IT.json * Remove ) de_DE.json * Update it_IT.json * Update pt_BR locale with latest strings * Remove ')' * add more strings to locale * update locale * remove extra slash * remove extra slash * set firmware version to 0 if key's not found * fix * revert timer changes * lock on object instead * Update it_IT.json * remove unused method * add load screen text to locale * drop swap event * Update de_DE.json * Update de_DE.json * do null check when stopping emulator * Update de_DE.json * Create tr_TR.json * Add tr_TR * Add tr_TR + Turkish * Update it_IT.json * Update Ryujinx.Ava/Input/AvaloniaMappingHelper.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Apply suggestions from code review Co-authored-by: Ac_K <Acoustik666@gmail.com> * Apply suggestions from code review Co-authored-by: Ac_K <Acoustik666@gmail.com> * addressed review * Update Ryujinx.Ava/Ui/Backend/OpenGl/OpenGlRenderTarget.cs Co-authored-by: gdkchan <gab.dark.100@gmail.com> * use avalonia's inbuilt renderer on linux * removed whitespace * workaround for queue render crash with vsync off * drop custom backend * format files * fix not closing issue * remove warnings * rebase * update avalonia library * Reposition the Text and Button on About Page * Assign build version * Remove appveyor text Co-authored-by: gdk <gab.dark.100@gmail.com> Co-authored-by: Niwu34 <67392333+Niwu34@users.noreply.github.com> Co-authored-by: Antonio Brugnolo <36473846+AntoSkate@users.noreply.github.com> Co-authored-by: aegiff <99728970+aegiff@users.noreply.github.com> Co-authored-by: Ac_K <Acoustik666@gmail.com> Co-authored-by: MostlyWhat <78652091+MostlyWhat@users.noreply.github.com>
Diffstat (limited to 'Ryujinx.Ava/Input')
-rw-r--r--Ryujinx.Ava/Input/AvaloniaKeyboard.cs205
-rw-r--r--Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs113
-rw-r--r--Ryujinx.Ava/Input/AvaloniaMappingHelper.cs188
-rw-r--r--Ryujinx.Ava/Input/AvaloniaMouse.cs90
-rw-r--r--Ryujinx.Ava/Input/AvaloniaMouseDriver.cs129
5 files changed, 725 insertions, 0 deletions
diff --git a/Ryujinx.Ava/Input/AvaloniaKeyboard.cs b/Ryujinx.Ava/Input/AvaloniaKeyboard.cs
new file mode 100644
index 00000000..a75758b9
--- /dev/null
+++ b/Ryujinx.Ava/Input/AvaloniaKeyboard.cs
@@ -0,0 +1,205 @@
+using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Configuration.Hid.Keyboard;
+using Ryujinx.Input;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+
+using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
+using Key = Ryujinx.Input.Key;
+
+namespace Ryujinx.Ava.Input
+{
+ public class AvaloniaKeyboard : IKeyboard
+ {
+ private readonly List<ButtonMappingEntry> _buttonsUserMapping;
+ private readonly AvaloniaKeyboardDriver _driver;
+
+ private readonly object _userMappingLock = new();
+
+ private StandardKeyboardInputConfig _configuration;
+
+ private bool HasConfiguration => _configuration != null;
+
+ public string Id { get; }
+ public string Name { get; }
+
+ public bool IsConnected => true;
+
+ public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
+
+ public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name)
+ {
+ _driver = driver;
+ Id = id;
+ Name = name;
+ _buttonsUserMapping = new List<ButtonMappingEntry>();
+ }
+
+ public void Dispose() { }
+
+ public KeyboardStateSnapshot GetKeyboardStateSnapshot()
+ {
+ return IKeyboard.GetStateSnapshot(this);
+ }
+
+ public GamepadStateSnapshot GetMappedStateSnapshot()
+ {
+ KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
+ GamepadStateSnapshot result = default;
+
+ lock (_userMappingLock)
+ {
+ if (!HasConfiguration)
+ {
+ return result;
+ }
+
+ foreach (ButtonMappingEntry entry in _buttonsUserMapping)
+ {
+ if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound)
+ {
+ continue;
+ }
+
+ // Do not touch state of the button already pressed
+ if (!result.IsPressed(entry.To))
+ {
+ result.SetPressed(entry.To, rawState.IsPressed(entry.From));
+ }
+ }
+
+ (short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
+ (short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
+
+ result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
+ result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
+ }
+
+ return result;
+ }
+
+ public GamepadStateSnapshot GetStateSnapshot()
+ {
+ throw new NotSupportedException();
+ }
+
+ public (float, float) GetStick(StickInputId inputId)
+ {
+ throw new NotSupportedException();
+ }
+
+ public bool IsPressed(GamepadButtonInputId inputId)
+ {
+ throw new NotSupportedException();
+ }
+
+ public bool IsPressed(Key key)
+ {
+ try
+ {
+ return _driver.IsPressed(key);
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ public void SetConfiguration(InputConfig configuration)
+ {
+ lock (_userMappingLock)
+ {
+ _configuration = (StandardKeyboardInputConfig)configuration;
+
+ _buttonsUserMapping.Clear();
+
+ // Left joycon
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
+
+ // Finally right joycon
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
+ }
+ }
+
+ public void SetTriggerThreshold(float triggerThreshold) { }
+
+ public void Rumble(float lowFrequency, float highFrequency, uint durationMs) { }
+
+ public Vector3 GetMotionData(MotionInputId inputId) => Vector3.Zero;
+
+ private static float ConvertRawStickValue(short value)
+ {
+ const float ConvertRate = 1.0f / (short.MaxValue + 0.5f);
+
+ return value * ConvertRate;
+ }
+
+ private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig)
+ {
+ short stickX = 0;
+ short stickY = 0;
+
+ if (snapshot.IsPressed((Key)stickConfig.StickUp))
+ {
+ stickY += 1;
+ }
+
+ if (snapshot.IsPressed((Key)stickConfig.StickDown))
+ {
+ stickY -= 1;
+ }
+
+ if (snapshot.IsPressed((Key)stickConfig.StickRight))
+ {
+ stickX += 1;
+ }
+
+ if (snapshot.IsPressed((Key)stickConfig.StickLeft))
+ {
+ stickX -= 1;
+ }
+
+ Vector2 stick = new(stickX, stickY);
+
+ stick = Vector2.Normalize(stick);
+
+ return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
+ }
+
+ public void Clear()
+ {
+ _driver?.ResetKeys();
+ }
+
+ private class ButtonMappingEntry
+ {
+ public readonly Key From;
+ public readonly GamepadButtonInputId To;
+
+ public ButtonMappingEntry(GamepadButtonInputId to, Key from)
+ {
+ To = to;
+ From = from;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs b/Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs
new file mode 100644
index 00000000..3babfe9d
--- /dev/null
+++ b/Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs
@@ -0,0 +1,113 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Input;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using AvaKey = Avalonia.Input.Key;
+using Key = Ryujinx.Input.Key;
+using TextInputEventArgs = OpenTK.Windowing.Common.TextInputEventArgs;
+
+namespace Ryujinx.Ava.Input
+{
+ public class AvaloniaKeyboardDriver : IGamepadDriver
+ {
+ private static readonly string[] _keyboardIdentifers = new string[1] { "0" };
+ private readonly Control _control;
+ private readonly HashSet<AvaKey> _pressedKeys;
+
+ public event EventHandler<KeyEventArgs> KeyPressed;
+ public event EventHandler<KeyEventArgs> KeyRelease;
+ public event EventHandler<TextInputEventArgs> TextInput;
+
+ public string DriverName => "Avalonia";
+
+ public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers;
+
+ public AvaloniaKeyboardDriver(Control control)
+ {
+ _control = control;
+ _pressedKeys = new HashSet<AvaKey>();
+
+ _control.KeyDown += OnKeyPress;
+ _control.KeyUp += OnKeyRelease;
+ _control.TextInput += Control_TextInput;
+ }
+
+ private void Control_TextInput(object sender, Avalonia.Input.TextInputEventArgs e)
+ {
+ TextInput?.Invoke(this, new TextInputEventArgs(e.Text.First()));
+ }
+
+ public event Action<string> OnGamepadConnected
+ {
+ add { }
+ remove { }
+ }
+
+ public event Action<string> OnGamepadDisconnected
+ {
+ add { }
+ remove { }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ public IGamepad GetGamepad(string id)
+ {
+ if (!_keyboardIdentifers[0].Equals(id))
+ {
+ return null;
+ }
+
+ return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance["AllKeyboards"]);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _control.KeyUp -= OnKeyPress;
+ _control.KeyDown -= OnKeyRelease;
+ }
+ }
+
+ protected void OnKeyPress(object sender, KeyEventArgs args)
+ {
+ AvaKey key = args.Key;
+
+ _pressedKeys.Add(args.Key);
+
+ KeyPressed?.Invoke(this, args);
+ }
+
+ protected void OnKeyRelease(object sender, KeyEventArgs args)
+ {
+ _pressedKeys.Remove(args.Key);
+
+ KeyRelease?.Invoke(this, args);
+ }
+
+ internal bool IsPressed(Key key)
+ {
+ if (key == Key.Unbound || key == Key.Unknown)
+ {
+ return false;
+ }
+
+ AvaloniaMappingHelper.TryGetAvaKey(key, out var nativeKey);
+
+ return _pressedKeys.Contains(nativeKey);
+ }
+
+ public void ResetKeys()
+ {
+ _pressedKeys.Clear();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Ava/Input/AvaloniaMappingHelper.cs b/Ryujinx.Ava/Input/AvaloniaMappingHelper.cs
new file mode 100644
index 00000000..b0aa2cc7
--- /dev/null
+++ b/Ryujinx.Ava/Input/AvaloniaMappingHelper.cs
@@ -0,0 +1,188 @@
+using Ryujinx.Input;
+using System;
+using System.Collections.Generic;
+using AvaKey = Avalonia.Input.Key;
+
+namespace Ryujinx.Ava.Input
+{
+ public static class AvaloniaMappingHelper
+ {
+ private static readonly AvaKey[] _keyMapping = new AvaKey[(int)Key.Count]
+ {
+ // NOTE: Invalid
+ AvaKey.None,
+
+ AvaKey.LeftShift,
+ AvaKey.RightShift,
+ AvaKey.LeftCtrl,
+ AvaKey.RightCtrl,
+ AvaKey.LeftAlt,
+ AvaKey.RightAlt,
+ AvaKey.LWin,
+ AvaKey.RWin,
+ AvaKey.Apps,
+ AvaKey.F1,
+ AvaKey.F2,
+ AvaKey.F3,
+ AvaKey.F4,
+ AvaKey.F5,
+ AvaKey.F6,
+ AvaKey.F7,
+ AvaKey.F8,
+ AvaKey.F9,
+ AvaKey.F10,
+ AvaKey.F11,
+ AvaKey.F12,
+ AvaKey.F13,
+ AvaKey.F14,
+ AvaKey.F15,
+ AvaKey.F16,
+ AvaKey.F17,
+ AvaKey.F18,
+ AvaKey.F19,
+ AvaKey.F20,
+ AvaKey.F21,
+ AvaKey.F22,
+ AvaKey.F23,
+ AvaKey.F24,
+
+ AvaKey.None,
+ AvaKey.None,
+ AvaKey.None,
+ AvaKey.None,
+ AvaKey.None,
+ AvaKey.None,
+ AvaKey.None,
+ AvaKey.None,
+ AvaKey.None,
+ AvaKey.None,
+ AvaKey.None,
+
+ AvaKey.Up,
+ AvaKey.Down,
+ AvaKey.Left,
+ AvaKey.Right,
+ AvaKey.Return,
+ AvaKey.Escape,
+ AvaKey.Space,
+ AvaKey.Tab,
+ AvaKey.Back,
+ AvaKey.Insert,
+ AvaKey.Delete,
+ AvaKey.PageUp,
+ AvaKey.PageDown,
+ AvaKey.Home,
+ AvaKey.End,
+ AvaKey.CapsLock,
+ AvaKey.Scroll,
+ AvaKey.Print,
+ AvaKey.Pause,
+ AvaKey.NumLock,
+ AvaKey.Clear,
+ AvaKey.NumPad0,
+ AvaKey.NumPad1,
+ AvaKey.NumPad2,
+ AvaKey.NumPad3,
+ AvaKey.NumPad4,
+ AvaKey.NumPad5,
+ AvaKey.NumPad6,
+ AvaKey.NumPad7,
+ AvaKey.NumPad8,
+ AvaKey.NumPad9,
+ AvaKey.Divide,
+ AvaKey.Multiply,
+ AvaKey.Subtract,
+ AvaKey.Add,
+ AvaKey.Decimal,
+ AvaKey.Enter,
+ AvaKey.A,
+ AvaKey.B,
+ AvaKey.C,
+ AvaKey.D,
+ AvaKey.E,
+ AvaKey.F,
+ AvaKey.G,
+ AvaKey.H,
+ AvaKey.I,
+ AvaKey.J,
+ AvaKey.K,
+ AvaKey.L,
+ AvaKey.M,
+ AvaKey.N,
+ AvaKey.O,
+ AvaKey.P,
+ AvaKey.Q,
+ AvaKey.R,
+ AvaKey.S,
+ AvaKey.T,
+ AvaKey.U,
+ AvaKey.V,
+ AvaKey.W,
+ AvaKey.X,
+ AvaKey.Y,
+ AvaKey.Z,
+ AvaKey.D0,
+ AvaKey.D1,
+ AvaKey.D2,
+ AvaKey.D3,
+ AvaKey.D4,
+ AvaKey.D5,
+ AvaKey.D6,
+ AvaKey.D7,
+ AvaKey.D8,
+ AvaKey.D9,
+ AvaKey.OemTilde,
+ AvaKey.OemTilde,AvaKey.OemMinus,
+ AvaKey.OemPlus,
+ AvaKey.OemOpenBrackets,
+ AvaKey.OemCloseBrackets,
+ AvaKey.OemSemicolon,
+ AvaKey.OemQuotes,
+ AvaKey.OemComma,
+ AvaKey.OemPeriod,
+ AvaKey.OemQuestion,
+ AvaKey.OemBackslash,
+
+ // NOTE: invalid
+ AvaKey.None
+ };
+
+ private static readonly Dictionary<AvaKey, Key> _avaKeyMapping;
+
+ static AvaloniaMappingHelper()
+ {
+ var inputKeys = Enum.GetValues(typeof(Key));
+
+ // Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array.
+ _avaKeyMapping = new Dictionary<AvaKey, Key>();
+
+ foreach (var key in inputKeys)
+ {
+ if (TryGetAvaKey((Key)key, out var index))
+ {
+ _avaKeyMapping[index] = (Key)key;
+ }
+ }
+ }
+
+ public static bool TryGetAvaKey(Key key, out AvaKey avaKey)
+ {
+ var keyExist = (int)key < _keyMapping.Length;
+ if (keyExist)
+ {
+ avaKey = _keyMapping[(int)key];
+ }
+ else
+ {
+ avaKey = AvaKey.None;
+ }
+
+ return keyExist;
+ }
+
+ public static Key ToInputKey(AvaKey key)
+ {
+ return _avaKeyMapping.GetValueOrDefault(key, Key.Unknown);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Ava/Input/AvaloniaMouse.cs b/Ryujinx.Ava/Input/AvaloniaMouse.cs
new file mode 100644
index 00000000..f3c2271b
--- /dev/null
+++ b/Ryujinx.Ava/Input/AvaloniaMouse.cs
@@ -0,0 +1,90 @@
+using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Input;
+using System;
+using System.Drawing;
+using System.Numerics;
+
+namespace Ryujinx.Ava.Input
+{
+ public class AvaloniaMouse : IMouse
+ {
+ private AvaloniaMouseDriver _driver;
+
+ public GamepadFeaturesFlag Features => throw new NotImplementedException();
+
+ public string Id => "0";
+
+ public string Name => "AvaloniaMouse";
+
+ public bool IsConnected => true;
+
+ public bool[] Buttons => _driver.PressedButtons;
+
+ public AvaloniaMouse(AvaloniaMouseDriver driver)
+ {
+ _driver = driver;
+ }
+
+ public Size ClientSize => _driver.GetClientSize();
+
+ public Vector2 GetPosition()
+ {
+ return _driver.CurrentPosition;
+ }
+
+ public Vector2 GetScroll()
+ {
+ return _driver.Scroll;
+ }
+
+ public GamepadStateSnapshot GetMappedStateSnapshot()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Vector3 GetMotionData(MotionInputId inputId)
+ {
+ throw new NotImplementedException();
+ }
+
+ public GamepadStateSnapshot GetStateSnapshot()
+ {
+ throw new NotImplementedException();
+ }
+
+ public (float, float) GetStick(StickInputId inputId)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool IsButtonPressed(MouseButton button)
+ {
+ return _driver.IsButtonPressed(button);
+ }
+
+ public bool IsPressed(GamepadButtonInputId inputId)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void SetConfiguration(InputConfig configuration)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void SetTriggerThreshold(float triggerThreshold)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Dispose()
+ {
+ _driver = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs b/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs
new file mode 100644
index 00000000..5a9fe791
--- /dev/null
+++ b/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs
@@ -0,0 +1,129 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Threading;
+using Ryujinx.Input;
+using System;
+using System.Numerics;
+using MouseButton = Ryujinx.Input.MouseButton;
+using Size = System.Drawing.Size;
+
+namespace Ryujinx.Ava.Input
+{
+ public class AvaloniaMouseDriver : IGamepadDriver
+ {
+ private Control _widget;
+ private bool _isDisposed;
+
+ public bool[] PressedButtons { get; }
+
+ public Vector2 CurrentPosition { get; private set; }
+ public Vector2 Scroll { get; private set; }
+
+ public AvaloniaMouseDriver(Control parent)
+ {
+ _widget = parent;
+
+ _widget.PointerMoved += Parent_PointerMovedEvent;
+ _widget.PointerPressed += Parent_PointerPressEvent;
+ _widget.PointerReleased += Parent_PointerReleaseEvent;
+ _widget.PointerWheelChanged += Parent_ScrollEvent;
+
+ PressedButtons = new bool[(int)MouseButton.Count];
+ }
+
+ private void Parent_ScrollEvent(object o, PointerWheelEventArgs args)
+ {
+ Scroll = new Vector2((float)args.Delta.X, (float)args.Delta.Y);
+ }
+
+ private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args)
+ {
+ var pointerProperties = args.GetCurrentPoint(_widget).Properties;
+ PressedButtons[(int)args.InitialPressMouseButton - 1] = false;
+ }
+
+ private void Parent_PointerPressEvent(object o, PointerPressedEventArgs args)
+ {
+ var pointerProperties = args.GetCurrentPoint(_widget).Properties;
+
+ PressedButtons[(int)pointerProperties.PointerUpdateKind] = true;
+ }
+
+ private void Parent_PointerMovedEvent(object o, PointerEventArgs args)
+ {
+ var position = args.GetPosition(_widget);
+
+ CurrentPosition = new Vector2((float)position.X, (float)position.Y);
+ }
+
+ public void SetMousePressed(MouseButton button)
+ {
+ PressedButtons[(int)button] = true;
+ }
+
+ public void SetMouseReleased(MouseButton button)
+ {
+ PressedButtons[(int)button] = false;
+ }
+
+ public void SetPosition(double x, double y)
+ {
+ CurrentPosition = new Vector2((float)x, (float)y);
+ }
+
+ public bool IsButtonPressed(MouseButton button)
+ {
+ return PressedButtons[(int)button];
+ }
+
+ public Size GetClientSize()
+ {
+ Size size = new();
+
+ Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ size = new Size((int)_widget.Bounds.Width, (int)_widget.Bounds.Height);
+ }).Wait();
+
+ return size;
+ }
+
+ public string DriverName => "Avalonia";
+
+ public event Action<string> OnGamepadConnected
+ {
+ add { }
+ remove { }
+ }
+
+ public event Action<string> OnGamepadDisconnected
+ {
+ add { }
+ remove { }
+ }
+
+ public ReadOnlySpan<string> GamepadsIds => new[] { "0" };
+
+ public IGamepad GetGamepad(string id)
+ {
+ return new AvaloniaMouse(this);
+ }
+
+ public void Dispose()
+ {
+ if (_isDisposed)
+ {
+ return;
+ }
+
+ _isDisposed = true;
+
+ _widget.PointerMoved -= Parent_PointerMovedEvent;
+ _widget.PointerPressed -= Parent_PointerPressEvent;
+ _widget.PointerReleased -= Parent_PointerReleaseEvent;
+ _widget.PointerWheelChanged -= Parent_ScrollEvent;
+
+ _widget = null;
+ }
+ }
+} \ No newline at end of file