diff --git a/Ryujinx.Input/HLE/InputManager.cs b/Ryujinx.Input/HLE/InputManager.cs index 277e8ec22..699e521d8 100644 --- a/Ryujinx.Input/HLE/InputManager.cs +++ b/Ryujinx.Input/HLE/InputManager.cs @@ -6,6 +6,7 @@ namespace Ryujinx.Input.HLE { public IGamepadDriver KeyboardDriver { get; private set; } public IGamepadDriver GamepadDriver { get; private set; } + public IGamepadDriver MouseDriver { get; private set; } public InputManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver) { @@ -13,10 +14,27 @@ namespace Ryujinx.Input.HLE GamepadDriver = gamepadDriver; } + public void SetMouseDriver(IGamepadDriver mouseDriver) + { + MouseDriver?.Dispose(); + + MouseDriver = mouseDriver; + } + public NpadManager CreateNpadManager() { return new NpadManager(KeyboardDriver, GamepadDriver); } + + public TouchScreenManager CreateTouchScreenManager() + { + if (MouseDriver == null) + { + throw new InvalidOperationException("Mouse Driver has not been initialized."); + } + + return new TouchScreenManager(MouseDriver.GetGamepad("0") as IMouse); + } protected virtual void Dispose(bool disposing) { @@ -24,6 +42,7 @@ namespace Ryujinx.Input.HLE { KeyboardDriver?.Dispose(); GamepadDriver?.Dispose(); + MouseDriver?.Dispose(); } } diff --git a/Ryujinx.Input/HLE/TouchScreenManager.cs b/Ryujinx.Input/HLE/TouchScreenManager.cs new file mode 100644 index 000000000..ffa8eeacc --- /dev/null +++ b/Ryujinx.Input/HLE/TouchScreenManager.cs @@ -0,0 +1,57 @@ +using Ryujinx.HLE; +using Ryujinx.HLE.HOS.Services.Hid; +using System; + +namespace Ryujinx.Input.HLE +{ + public class TouchScreenManager : IDisposable + { + private readonly IMouse _mouse; + private Switch _device; + + public TouchScreenManager(IMouse mouse) + { + _mouse = mouse; + } + + public void Initialize(Switch device) + { + _device = device; + } + + public bool Update(bool isFocused, float aspectRatio = 0) + { + if (!isFocused) + { + _device.Hid.Touchscreen.Update(); + + return false; + } + + if (aspectRatio > 0) + { + var snapshot = IMouse.GetMouseStateSnapshot(_mouse); + var touchPosition = IMouse.GetTouchPosition(snapshot.Position, _mouse.ClientSize, aspectRatio); + + TouchPoint currentPoint = new TouchPoint + { + X = (uint)touchPosition.X, + Y = (uint)touchPosition.Y, + + // Placeholder values till more data is acquired + DiameterX = 10, + DiameterY = 10, + Angle = 90 + }; + + _device.Hid.Touchscreen.Update(currentPoint); + + return true; + } + + return false; + } + + public void Dispose() { } + } +} \ No newline at end of file diff --git a/Ryujinx.Input/IMouse.cs b/Ryujinx.Input/IMouse.cs new file mode 100644 index 000000000..37de0229e --- /dev/null +++ b/Ryujinx.Input/IMouse.cs @@ -0,0 +1,100 @@ +using System.Drawing; +using System.Numerics; + +namespace Ryujinx.Input +{ + /// + /// Represent an emulated mouse. + /// + public interface IMouse : IGamepad + { + private const int SwitchPanelWidth = 1280; + private const int SwitchPanelHeight = 720; + + /// + /// Check if a given button is pressed on the mouse. + /// + /// The button + /// True if the given button is pressed on the mouse + bool IsButtonPressed(MouseButton button); + + /// + /// Get the position of the mouse in the client. + /// + Vector2 GetPosition(); + + /// + /// Get the client size. + /// + Size ClientSize { get; } + + /// + /// Get the button states of the mouse. + /// + bool[] Buttons { get; } + + /// + /// Get a snaphost of the state of a mouse. + /// + /// The mouse to do a snapshot of + /// A snaphost of the state of the mouse. + public static MouseStateSnapshot GetMouseStateSnapshot(IMouse mouse) + { + var position = mouse.GetPosition(); + bool[] buttons = new bool[(int)MouseButton.Count]; + + mouse.Buttons.CopyTo(buttons, 0); + + return new MouseStateSnapshot(buttons, position); + } + + /// + /// Get the touch position of a mouse position relative to the app's view + /// + /// The position of the mouse in the client + /// The size of the client + /// The aspect ratio of the view + /// A snaphost of the state of the mouse. + public static Vector2 GetTouchPosition(Vector2 mousePosition, Size clientSize, float aspectRatio) + { + float mouseX = mousePosition.X; + float mouseY = mousePosition.Y; + + float aspectWidth = SwitchPanelHeight * aspectRatio; + + int screenWidth = clientSize.Width; + int screenHeight = clientSize.Height; + + if (clientSize.Width > clientSize.Height * aspectWidth / SwitchPanelHeight) + { + screenWidth = (int)(clientSize.Height * aspectWidth) / SwitchPanelHeight; + } + else + { + screenHeight = (clientSize.Width * SwitchPanelHeight) / (int)aspectWidth; + } + + int startX = (clientSize.Width - screenWidth) >> 1; + int startY = (clientSize.Height - screenHeight) >> 1; + + int endX = startX + screenWidth; + int endY = startY + screenHeight; + + if (mouseX >= startX && + mouseY >= startY && + mouseX < endX && + mouseY < endY) + { + int screenMouseX = (int)mouseX - startX; + int screenMouseY = (int)mouseY - startY; + + mouseX = (screenMouseX * (int)aspectWidth) / screenWidth; + mouseY = (screenMouseY * SwitchPanelHeight) / screenHeight; + + return new Vector2(mouseX, mouseY); + } + + return new Vector2(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Input/MouseButton.cs b/Ryujinx.Input/MouseButton.cs new file mode 100644 index 000000000..ab7642167 --- /dev/null +++ b/Ryujinx.Input/MouseButton.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Input +{ + public enum MouseButton : byte + { + Button1, + Button2, + Button3, + Button4, + Button5, + Button6, + Button7, + Button8, + Button9, + Count + } +} \ No newline at end of file diff --git a/Ryujinx.Input/MouseStateSnapshot.cs b/Ryujinx.Input/MouseStateSnapshot.cs new file mode 100644 index 000000000..4fbfeebd1 --- /dev/null +++ b/Ryujinx.Input/MouseStateSnapshot.cs @@ -0,0 +1,34 @@ +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Input +{ + /// + /// A snapshot of a . + /// + public class MouseStateSnapshot + { + private bool[] _buttonState; + + public Vector2 Position { get; } + + /// + /// Create a new . + /// + /// The keys state + public MouseStateSnapshot(bool[] buttonState, Vector2 position) + { + _buttonState = buttonState; + + Position = position; + } + + /// + /// Check if a given button is pressed. + /// + /// The button + /// True if the given button is pressed + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsPressed(MouseButton button) => _buttonState[(int)button]; + } +} \ No newline at end of file diff --git a/Ryujinx/Input/GTK3/GTK3Mouse.cs b/Ryujinx/Input/GTK3/GTK3Mouse.cs new file mode 100644 index 000000000..eb0c8c9a0 --- /dev/null +++ b/Ryujinx/Input/GTK3/GTK3Mouse.cs @@ -0,0 +1,84 @@ +using Ryujinx.Common.Configuration.Hid; +using System; +using System.Drawing; +using System.Numerics; + +namespace Ryujinx.Input.GTK3 +{ + public class GTK3Mouse : IMouse + { + private GTK3MouseDriver _driver; + + public GamepadFeaturesFlag Features => throw new NotImplementedException(); + + public string Id => "0"; + + public string Name => "GTKMouse"; + + public bool IsConnected => true; + + public bool[] Buttons => _driver.PressedButtons; + + public GTK3Mouse(GTK3MouseDriver driver) + { + _driver = driver; + } + + public Size ClientSize => _driver.GetClientSize(); + + public Vector2 GetPosition() + { + return _driver.CurrentPosition; + } + + 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/Input/GTK3/GTK3MouseDriver.cs b/Ryujinx/Input/GTK3/GTK3MouseDriver.cs new file mode 100644 index 000000000..015f58176 --- /dev/null +++ b/Ryujinx/Input/GTK3/GTK3MouseDriver.cs @@ -0,0 +1,97 @@ +using Gdk; +using Gtk; +using System; +using System.Numerics; +using Size = System.Drawing.Size; + +namespace Ryujinx.Input.GTK3 +{ + public class GTK3MouseDriver : IGamepadDriver + { + private Widget _widget; + private bool _isDisposed; + + public bool[] PressedButtons { get; } + + public Vector2 CurrentPosition { get; private set; } + + public GTK3MouseDriver(Widget parent) + { + _widget = parent; + + _widget.MotionNotifyEvent += Parent_MotionNotifyEvent; + _widget.ButtonPressEvent += Parent_ButtonPressEvent; + _widget.ButtonReleaseEvent += Parent_ButtonReleaseEvent; + + PressedButtons = new bool[(int)MouseButton.Count]; + } + + [GLib.ConnectBefore] + private void Parent_ButtonReleaseEvent(object o, ButtonReleaseEventArgs args) + { + PressedButtons[args.Event.Button - 1] = false; + } + + [GLib.ConnectBefore] + private void Parent_ButtonPressEvent(object o, ButtonPressEventArgs args) + { + PressedButtons[args.Event.Button - 1] = true; + } + + [GLib.ConnectBefore] + private void Parent_MotionNotifyEvent(object o, MotionNotifyEventArgs args) + { + if (args.Event.Device.InputSource == InputSource.Mouse) + { + CurrentPosition = new Vector2((float)args.Event.X, (float)args.Event.Y); + } + } + + public bool IsButtonPressed(MouseButton button) + { + return PressedButtons[(int) button]; + } + + public Size GetClientSize() + { + return new Size(_widget.AllocatedWidth, _widget.AllocatedHeight); + } + + public string DriverName => "GTK3"; + + public event Action OnGamepadConnected + { + add { } + remove { } + } + + public event Action OnGamepadDisconnected + { + add { } + remove { } + } + + public ReadOnlySpan GamepadsIds => new[] {"0"}; + + public IGamepad GetGamepad(string id) + { + return new GTK3Mouse(this); + } + + public void Dispose() + { + if (_isDisposed) + { + return; + } + + _isDisposed = true; + + _widget.MotionNotifyEvent -= Parent_MotionNotifyEvent; + _widget.ButtonPressEvent -= Parent_ButtonPressEvent; + _widget.ButtonReleaseEvent -= Parent_ButtonReleaseEvent; + + _widget = null; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs index 00882ba0c..4ba87a1b1 100644 --- a/Ryujinx/Ui/RendererWidgetBase.cs +++ b/Ryujinx/Ui/RendererWidgetBase.cs @@ -8,6 +8,7 @@ using Ryujinx.Configuration; using Ryujinx.Graphics.GAL; using Ryujinx.HLE.HOS.Services.Hid; using Ryujinx.Input; +using Ryujinx.Input.GTK3; using Ryujinx.Input.HLE; using Ryujinx.Ui.Widgets; using System; @@ -28,6 +29,7 @@ namespace Ryujinx.Ui public ManualResetEvent WaitEvent { get; set; } public NpadManager NpadManager { get; } + public TouchScreenManager TouchScreenManager { get; } public Switch Device { get; private set; } public IRenderer Renderer { get; private set; } @@ -37,10 +39,6 @@ namespace Ryujinx.Ui private bool _isStopped; private bool _isFocused; - private double _mouseX; - private double _mouseY; - private bool _mousePressed; - private bool _toggleFullscreen; private bool _toggleDockedMode; @@ -69,8 +67,12 @@ namespace Ryujinx.Ui public RendererWidgetBase(InputManager inputManager, GraphicsDebugLevel glLogLevel) { + var mouseDriver = new GTK3MouseDriver(this); + _inputManager = inputManager; + _inputManager.SetMouseDriver(mouseDriver); NpadManager = _inputManager.CreateNpadManager(); + TouchScreenManager = _inputManager.CreateTouchScreenManager(); _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0"); WaitEvent = new ManualResetEvent(false); @@ -145,37 +147,8 @@ namespace Ryujinx.Ui _isFocused = ParentWindow.State.HasFlag(Gdk.WindowState.Focused); } - protected override bool OnButtonPressEvent(EventButton evnt) - { - _mouseX = evnt.X; - _mouseY = evnt.Y; - - if (evnt.Button == 1) - { - _mousePressed = true; - } - - return false; - } - - protected override bool OnButtonReleaseEvent(EventButton evnt) - { - if (evnt.Button == 1) - { - _mousePressed = false; - } - - return false; - } - protected override bool OnMotionNotifyEvent(EventMotion evnt) { - if (evnt.Device.InputSource == InputSource.Mouse) - { - _mouseX = evnt.X; - _mouseY = evnt.Y; - } - if (_hideCursorOnIdle) { _lastCursorMoveTime = Stopwatch.GetTimestamp(); @@ -300,6 +273,7 @@ namespace Ryujinx.Ui Renderer?.Window.SetSize(_windowWidth, _windowHeight); NpadManager.Initialize(device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard); + TouchScreenManager.Initialize(device); } public void Render() @@ -412,6 +386,7 @@ namespace Ryujinx.Ui public void Exit() { + TouchScreenManager?.Dispose(); NpadManager?.Dispose(); if (_isStopped) @@ -507,60 +482,14 @@ namespace Ryujinx.Ui bool hasTouch = false; // Get screen touch position from left mouse click - // OpenTK always captures mouse events, even if out of focus, so check if window is focused. - if (_isFocused && _mousePressed) + if (_isFocused && (_inputManager.MouseDriver as GTK3MouseDriver).IsButtonPressed(MouseButton.Button1)) { - float aspectWidth = SwitchPanelHeight * ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat(); - - int screenWidth = AllocatedWidth; - int screenHeight = AllocatedHeight; - - if (AllocatedWidth > AllocatedHeight * aspectWidth / SwitchPanelHeight) - { - screenWidth = (int)(AllocatedHeight * aspectWidth) / SwitchPanelHeight; - } - else - { - screenHeight = (AllocatedWidth * SwitchPanelHeight) / (int)aspectWidth; - } - - int startX = (AllocatedWidth - screenWidth) >> 1; - int startY = (AllocatedHeight - screenHeight) >> 1; - - int endX = startX + screenWidth; - int endY = startY + screenHeight; - - if (_mouseX >= startX && - _mouseY >= startY && - _mouseX < endX && - _mouseY < endY) - { - int screenMouseX = (int)_mouseX - startX; - int screenMouseY = (int)_mouseY - startY; - - int mX = (screenMouseX * (int)aspectWidth) / screenWidth; - int mY = (screenMouseY * SwitchPanelHeight) / screenHeight; - - TouchPoint currentPoint = new TouchPoint - { - X = (uint)mX, - Y = (uint)mY, - - // Placeholder values till more data is acquired - DiameterX = 10, - DiameterY = 10, - Angle = 90 - }; - - hasTouch = true; - - Device.Hid.Touchscreen.Update(currentPoint); - } + hasTouch = TouchScreenManager.Update(true, ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); } if (!hasTouch) { - Device.Hid.Touchscreen.Update(); + TouchScreenManager.Update(false); } Device.Hid.DebugPad.Update(); @@ -568,7 +497,6 @@ namespace Ryujinx.Ui return true; } - [Flags] private enum KeyboardHotkeyState {