forked from Mirror/Ryujinx
Ava UI: Fixes "Hide Cursor on Idle" for Windows (#4266)
* Ava: Fixes "Hide Cursor on Idle" for Windows * Add check in MouseDriver and reduce the time of idling * Fix linux error * Change idle time everywhere for consistencies
This commit is contained in:
parent
b402b4e7f6
commit
8071c8c8c0
6 changed files with 125 additions and 62 deletions
|
@ -44,8 +44,10 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
using InputManager = Ryujinx.Input.HLE.InputManager;
|
||||
using Key = Ryujinx.Input.Key;
|
||||
|
@ -58,12 +60,14 @@ namespace Ryujinx.Ava
|
|||
{
|
||||
internal class AppHost
|
||||
{
|
||||
private const int CursorHideIdleTime = 8; // Hide Cursor seconds.
|
||||
private const int CursorHideIdleTime = 5; // Hide Cursor seconds.
|
||||
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
|
||||
private const int TargetFps = 60;
|
||||
private const float VolumeDelta = 0.05f;
|
||||
|
||||
private static readonly Cursor InvisibleCursor = new(StandardCursorType.None);
|
||||
private readonly IntPtr InvisibleCursorWin;
|
||||
private readonly IntPtr DefaultCursorWin;
|
||||
|
||||
private readonly long _ticksPerFrame;
|
||||
private readonly Stopwatch _chrono;
|
||||
|
@ -81,7 +85,6 @@ namespace Ryujinx.Ava
|
|||
private float _newVolume;
|
||||
private KeyboardHotkeyState _prevHotkeyState;
|
||||
|
||||
private bool _hideCursorOnIdle;
|
||||
private long _lastCursorMoveTime;
|
||||
private bool _isCursorInRenderer;
|
||||
|
||||
|
@ -131,7 +134,6 @@ namespace Ryujinx.Ava
|
|||
_accountManager = accountManager;
|
||||
_userChannelPersistence = userChannelPersistence;
|
||||
_renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" };
|
||||
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
|
||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
|
||||
_topLevel = topLevel;
|
||||
|
@ -159,9 +161,14 @@ namespace Ryujinx.Ava
|
|||
|
||||
ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorState_Changed;
|
||||
|
||||
_topLevel.PointerLeave += TopLevel_PointerLeave;
|
||||
_topLevel.PointerMoved += TopLevel_PointerMoved;
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
InvisibleCursorWin = CreateEmptyCursor();
|
||||
DefaultCursorWin = CreateArrowCursor();
|
||||
}
|
||||
|
||||
ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState;
|
||||
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
|
||||
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
|
||||
|
@ -172,20 +179,47 @@ namespace Ryujinx.Ava
|
|||
|
||||
private void TopLevel_PointerMoved(object sender, PointerEventArgs e)
|
||||
{
|
||||
if (sender is Control visual)
|
||||
if (sender is MainWindow window)
|
||||
{
|
||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||
|
||||
var point = e.GetCurrentPoint(visual).Position;
|
||||
if ((Renderer.Content as EmbeddedWindow).TransformedBounds != null)
|
||||
{
|
||||
var point = e.GetCurrentPoint(window).Position;
|
||||
var bounds = (Renderer.Content as EmbeddedWindow).TransformedBounds.Value.Clip;
|
||||
|
||||
_isCursorInRenderer = Equals(visual.InputHitTest(point), Renderer);
|
||||
_isCursorInRenderer = point.X >= bounds.X &&
|
||||
point.X <= bounds.Width + bounds.X &&
|
||||
point.Y >= bounds.Y &&
|
||||
point.Y <= bounds.Height + bounds.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TopLevel_PointerLeave(object sender, PointerEventArgs e)
|
||||
private void ShowCursor()
|
||||
{
|
||||
_isCursorInRenderer = false;
|
||||
_viewModel.Cursor = Cursor.Default;
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
_viewModel.Cursor = Cursor.Default;
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
SetCursor(DefaultCursorWin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void HideCursor()
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
_viewModel.Cursor = InvisibleCursor;
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
SetCursor(InvisibleCursorWin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void SetRendererWindowSize(Size size)
|
||||
|
@ -380,7 +414,6 @@ namespace Ryujinx.Ava
|
|||
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
|
||||
ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState;
|
||||
|
||||
_topLevel.PointerLeave -= TopLevel_PointerLeave;
|
||||
_topLevel.PointerMoved -= TopLevel_PointerMoved;
|
||||
|
||||
_gpuCancellationTokenSource.Cancel();
|
||||
|
@ -406,19 +439,10 @@ namespace Ryujinx.Ava
|
|||
|
||||
private void HideCursorState_Changed(object sender, ReactiveEventArgs<bool> state)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(delegate
|
||||
if (state.NewValue)
|
||||
{
|
||||
_hideCursorOnIdle = state.NewValue;
|
||||
|
||||
if (_hideCursorOnIdle)
|
||||
{
|
||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||
}
|
||||
else
|
||||
{
|
||||
_viewModel.Cursor = Cursor.Default;
|
||||
}
|
||||
});
|
||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> LoadGuestApplication()
|
||||
|
@ -860,29 +884,6 @@ namespace Ryujinx.Ava
|
|||
}
|
||||
}
|
||||
|
||||
private void HandleScreenState()
|
||||
{
|
||||
if (ConfigurationState.Instance.Hid.EnableMouse)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
_viewModel.Cursor = _isCursorInRenderer ? InvisibleCursor : Cursor.Default;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_hideCursorOnIdle)
|
||||
{
|
||||
long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime;
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
_viewModel.Cursor = cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency ? InvisibleCursor : Cursor.Default;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool UpdateFrame()
|
||||
{
|
||||
if (!_isActive)
|
||||
|
@ -890,23 +891,44 @@ namespace Ryujinx.Ava
|
|||
return false;
|
||||
}
|
||||
|
||||
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
|
||||
|
||||
if (_viewModel.IsActive)
|
||||
{
|
||||
if (ConfigurationState.Instance.Hid.EnableMouse)
|
||||
{
|
||||
if (_isCursorInRenderer)
|
||||
{
|
||||
HideCursor();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowCursor();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ConfigurationState.Instance.HideCursorOnIdle)
|
||||
{
|
||||
if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency)
|
||||
{
|
||||
HideCursor();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowCursor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
HandleScreenState();
|
||||
|
||||
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
|
||||
{
|
||||
Device.Application.DiskCacheLoadState?.Cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
|
||||
|
||||
if (_viewModel.IsActive)
|
||||
{
|
||||
KeyboardHotkeyState currentHotkeyState = GetHotkeyState();
|
||||
|
||||
if (currentHotkeyState != _prevHotkeyState)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using FluentAvalonia.Core;
|
||||
using Ryujinx.Input;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
@ -69,12 +70,22 @@ namespace Ryujinx.Ava.Input
|
|||
|
||||
private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args)
|
||||
{
|
||||
PressedButtons[(int)args.InitialPressMouseButton - 1] = false;
|
||||
int button = (int)args.InitialPressMouseButton - 1;
|
||||
|
||||
if (PressedButtons.Count() >= button)
|
||||
{
|
||||
PressedButtons[button] = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Parent_PointerPressEvent(object o, PointerPressedEventArgs args)
|
||||
{
|
||||
PressedButtons[(int)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind] = true;
|
||||
int button = (int)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind;
|
||||
|
||||
if (PressedButtons.Count() >= button)
|
||||
{
|
||||
PressedButtons[button] = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Parent_PointerMovedEvent(object o, PointerEventArgs args)
|
||||
|
@ -86,12 +97,18 @@ namespace Ryujinx.Ava.Input
|
|||
|
||||
public void SetMousePressed(MouseButton button)
|
||||
{
|
||||
PressedButtons[(int)button] = true;
|
||||
if (PressedButtons.Count() >= (int)button)
|
||||
{
|
||||
PressedButtons[(int)button] = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetMouseReleased(MouseButton button)
|
||||
{
|
||||
PressedButtons[(int)button] = false;
|
||||
if (PressedButtons.Count() >= (int)button)
|
||||
{
|
||||
PressedButtons[(int)button] = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPosition(double x, double y)
|
||||
|
@ -101,7 +118,12 @@ namespace Ryujinx.Ava.Input
|
|||
|
||||
public bool IsButtonPressed(MouseButton button)
|
||||
{
|
||||
return PressedButtons[(int)button];
|
||||
if (PressedButtons.Count() >= (int)button)
|
||||
{
|
||||
return PressedButtons[(int)button];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Size GetClientSize()
|
||||
|
|
|
@ -34,6 +34,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||
{
|
||||
WindowHandle = IntPtr.Zero;
|
||||
X11Display = IntPtr.Zero;
|
||||
NsView = IntPtr.Zero;
|
||||
MetalLayer = IntPtr.Zero;
|
||||
}
|
||||
|
||||
public EmbeddedWindow()
|
||||
|
@ -42,7 +44,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||
|
||||
stateObserverable.Subscribe(StateChanged);
|
||||
|
||||
this.Initialized += NativeEmbeddedWindow_Initialized;
|
||||
Initialized += NativeEmbeddedWindow_Initialized;
|
||||
}
|
||||
|
||||
public virtual void OnWindowCreated() { }
|
||||
|
@ -127,7 +129,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
|
||||
style = ClassStyles.CS_OWNDC,
|
||||
lpszClassName = Marshal.StringToHGlobalUni(_className),
|
||||
hCursor = LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IDC_ARROW)
|
||||
hCursor = CreateArrowCursor()
|
||||
};
|
||||
|
||||
var atom = RegisterClassEx(ref wndClassEx);
|
||||
|
@ -198,6 +200,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||
KeyModifiers.None));
|
||||
break;
|
||||
}
|
||||
|
||||
return DefWindowProc(hWnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
|
|
|
@ -70,6 +70,22 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||
}
|
||||
}
|
||||
|
||||
public static IntPtr CreateEmptyCursor()
|
||||
{
|
||||
return CreateCursor(IntPtr.Zero, 0, 0, 1, 1, new byte[] { 0xFF }, new byte[] { 0x00 });
|
||||
}
|
||||
|
||||
public static IntPtr CreateArrowCursor()
|
||||
{
|
||||
return LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IDC_ARROW);
|
||||
}
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
public static partial IntPtr SetCursor(IntPtr handle);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
public static partial IntPtr CreateCursor(IntPtr hInst, int xHotSpot, int yHotSpot, int nWidth, int nHeight, byte[] pvANDPlane, byte[] pvXORPlane);
|
||||
|
||||
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")]
|
||||
public static partial ushort RegisterClassEx(ref WNDCLASSEX param);
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Ryujinx.Headless.SDL2
|
|||
{
|
||||
class SDL2MouseDriver : IGamepadDriver
|
||||
{
|
||||
private const int CursorHideIdleTime = 8; // seconds
|
||||
private const int CursorHideIdleTime = 5; // seconds
|
||||
|
||||
private bool _isDisposed;
|
||||
private HideCursor _hideCursor;
|
||||
|
|
|
@ -68,7 +68,7 @@ namespace Ryujinx.Ui
|
|||
private readonly CancellationTokenSource _gpuCancellationTokenSource;
|
||||
|
||||
// Hide Cursor
|
||||
const int CursorHideIdleTime = 8; // seconds
|
||||
const int CursorHideIdleTime = 5; // seconds
|
||||
private static readonly Cursor _invisibleCursor = new Cursor(Display.Default, CursorType.BlankCursor);
|
||||
private long _lastCursorMoveTime;
|
||||
private bool _hideCursorOnIdle;
|
||||
|
|
Loading…
Reference in a new issue