From 56c5dbe5572190df3cb176955c50afe9724c2f56 Mon Sep 17 00:00:00 2001 From: Exhigh Date: Mon, 29 Apr 2024 04:51:08 +0530 Subject: [PATCH] Fix Cursor States On Windows (#6725) * [Ava]: Fix Cursor States On Windows It's been sometime since the last PR #5415 was made and last time i was waiting for Ava 11 to be merged before re-writing the code and along the way forgot about the PR. Anyway this PR supersedes both #5288 and #5415, and fixes issue: #5136 * Now, the bounds for which the cursor should be detected in renderer should be accurate to any scaling / resolution, taking into account the status and the menu bar. ( This issue was partially resolved by #6450 ) * Reduced the number of times the cursor updates from per frame update to updating only when the cursor state needs to be changed. * Fixed the issue wherein you weren't able to resize the window, because of the cursor passthrough which caused the cursor to reset from the reset icon or flicker. * Fixed the issue caused by #6450 which caused the cursor to disappear over the submenus while cursor was set to always hide. * Changed the cursor state to not disappear while the game is being loaded. ( Needs Feedback ). * Removed an unused library import. * PR feedback * Fix excessive line breaks and whitespaces and other feedback * Add a check before calculating cursor idle time, such that it calculates only while the cursor mode is OnIdle. * PR Feedback * Rework the cursor state check code block Co-Authored-By: gdkchan <5624669+gdkchan@users.noreply.github.com> * PR Feedback * A simpler version of the previous implementation. Co-Authored-By: gdkchan <5624669+gdkchan@users.noreply.github.com> * PR Feedback * PR Feedback --------- Co-authored-by: gdkchan <5624669+gdkchan@users.noreply.github.com> --- src/Ryujinx/AppHost.cs | 116 +++++++++++++----- src/Ryujinx/UI/Helpers/Win32NativeInterop.cs | 3 - src/Ryujinx/UI/Renderer/EmbeddedWindow.cs | 2 +- .../UI/ViewModels/MainWindowViewModel.cs | 12 ++ .../UI/Views/Main/MainMenuBarView.axaml | 5 +- src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 7 +- 6 files changed, 108 insertions(+), 37 deletions(-) diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 43e7a79eb..d405f3205 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -94,6 +94,17 @@ namespace Ryujinx.Ava private long _lastCursorMoveTime; private bool _isCursorInRenderer = true; + private bool _ignoreCursorState = false; + + private enum CursorStates + { + CursorIsHidden, + CursorIsVisible, + ForceChangeCursor + }; + + private CursorStates _cursorState = !ConfigurationState.Instance.Hid.EnableMouse.Value ? + CursorStates.CursorIsVisible : CursorStates.CursorIsHidden; private bool _isStopped; private bool _isActive; @@ -201,23 +212,65 @@ namespace Ryujinx.Ava private void TopLevel_PointerEnteredOrMoved(object sender, PointerEventArgs e) { + if (!_viewModel.IsActive) + { + _isCursorInRenderer = false; + _ignoreCursorState = false; + return; + } + if (sender is MainWindow window) { - _lastCursorMoveTime = Stopwatch.GetTimestamp(); + if (ConfigurationState.Instance.HideCursor.Value == HideCursorMode.OnIdle) + { + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + } var point = e.GetCurrentPoint(window).Position; var bounds = RendererHost.EmbeddedWindow.Bounds; + var windowYOffset = bounds.Y + window.MenuBarHeight; + var windowYLimit = (int)window.Bounds.Height - window.StatusBarHeight - 1; + + if (!_viewModel.ShowMenuAndStatusBar) + { + windowYOffset -= window.MenuBarHeight; + windowYLimit += window.StatusBarHeight + 1; + } _isCursorInRenderer = point.X >= bounds.X && - point.X <= bounds.Width + bounds.X && - point.Y >= bounds.Y && - point.Y <= bounds.Height + bounds.Y; + Math.Ceiling(point.X) <= (int)window.Bounds.Width && + point.Y >= windowYOffset && + point.Y <= windowYLimit && + !_viewModel.IsSubMenuOpen; + + _ignoreCursorState = false; } } private void TopLevel_PointerExited(object sender, PointerEventArgs e) { _isCursorInRenderer = false; + + if (sender is MainWindow window) + { + var point = e.GetCurrentPoint(window).Position; + var bounds = RendererHost.EmbeddedWindow.Bounds; + var windowYOffset = bounds.Y + window.MenuBarHeight; + var windowYLimit = (int)window.Bounds.Height - window.StatusBarHeight - 1; + + if (!_viewModel.ShowMenuAndStatusBar) + { + windowYOffset -= window.MenuBarHeight; + windowYLimit += window.StatusBarHeight + 1; + } + + _ignoreCursorState = (point.X == bounds.X || + Math.Ceiling(point.X) == (int)window.Bounds.Width) && + point.Y >= windowYOffset && + point.Y <= windowYLimit; + } + + _cursorState = CursorStates.ForceChangeCursor; } private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs e) @@ -245,9 +298,14 @@ namespace Ryujinx.Ava if (OperatingSystem.IsWindows()) { - SetCursor(_defaultCursorWin); + if (_cursorState != CursorStates.CursorIsHidden && !_ignoreCursorState) + { + SetCursor(_defaultCursorWin); + } } }); + + _cursorState = CursorStates.CursorIsVisible; } private void HideCursor() @@ -261,6 +319,8 @@ namespace Ryujinx.Ava SetCursor(_invisibleCursorWin); } }); + + _cursorState = CursorStates.CursorIsHidden; } private void SetRendererWindowSize(Size size) @@ -523,6 +583,8 @@ namespace Ryujinx.Ava { _lastCursorMoveTime = Stopwatch.GetTimestamp(); } + + _cursorState = CursorStates.ForceChangeCursor; } public async Task LoadGuestApplication() @@ -1037,38 +1099,32 @@ namespace Ryujinx.Ava if (_viewModel.IsActive) { - if (_isCursorInRenderer) + bool isCursorVisible = true; + + if (_isCursorInRenderer && !_viewModel.ShowLoadProgress) { - if (ConfigurationState.Instance.Hid.EnableMouse) + if (ConfigurationState.Instance.Hid.EnableMouse.Value) { - HideCursor(); + isCursorVisible = ConfigurationState.Instance.HideCursor.Value == HideCursorMode.Never; } else { - switch (ConfigurationState.Instance.HideCursor.Value) - { - case HideCursorMode.Never: - ShowCursor(); - break; - case HideCursorMode.OnIdle: - if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency) - { - HideCursor(); - } - else - { - ShowCursor(); - } - break; - case HideCursorMode.Always: - HideCursor(); - break; - } + isCursorVisible = ConfigurationState.Instance.HideCursor.Value == HideCursorMode.Never || + (ConfigurationState.Instance.HideCursor.Value == HideCursorMode.OnIdle && + Stopwatch.GetTimestamp() - _lastCursorMoveTime < CursorHideIdleTime * Stopwatch.Frequency); } } - else + + if (_cursorState != (isCursorVisible ? CursorStates.CursorIsVisible : CursorStates.CursorIsHidden)) { - ShowCursor(); + if (isCursorVisible) + { + ShowCursor(); + } + else + { + HideCursor(); + } } Dispatcher.UIThread.Post(() => @@ -1154,7 +1210,7 @@ namespace Ryujinx.Ava // Touchscreen. bool hasTouch = false; - if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse) + if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse.Value) { hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); } diff --git a/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs index fce2d518a..01478cb3d 100644 --- a/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs +++ b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs @@ -111,8 +111,5 @@ namespace Ryujinx.Ava.UI.Helpers [LibraryImport("user32.dll", SetLastError = true)] public static partial IntPtr SetWindowLongPtrW(IntPtr hWnd, int nIndex, IntPtr value); - - [LibraryImport("user32.dll", SetLastError = true)] - public static partial IntPtr SetWindowLongW(IntPtr hWnd, int nIndex, int value); } } diff --git a/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs index 8c5e31fff..0930e7795 100644 --- a/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs +++ b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs @@ -157,7 +157,7 @@ namespace Ryujinx.Ava.UI.Renderer lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate), style = ClassStyles.CsOwndc, lpszClassName = Marshal.StringToHGlobalUni(_className), - hCursor = CreateArrowCursor(), + hCursor = CreateArrowCursor() }; RegisterClassEx(ref wndClassEx); diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 130e708c7..549eebf14 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -104,6 +104,7 @@ namespace Ryujinx.Ava.UI.ViewModels private double _windowHeight; private bool _isActive; + private bool _isSubMenuOpen; public ApplicationData ListSelectedApplication; public ApplicationData GridSelectedApplication; @@ -317,6 +318,17 @@ namespace Ryujinx.Ava.UI.ViewModels } } + public bool IsSubMenuOpen + { + get => _isSubMenuOpen; + set + { + _isSubMenuOpen = value; + + OnPropertyChanged(); + } + } + public bool ShowAll { get => _showAll; diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml index 30358adab..ea432f78d 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml @@ -1,4 +1,4 @@ - + HorizontalAlignment="Left" + IsOpen="{Binding IsSubMenuOpen, Mode=OneWayToSource}"> diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index 37e2e71a8..eebbb81fe 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -56,6 +56,9 @@ namespace Ryujinx.Ava.UI.Windows public static bool ShowKeyErrorOnLoad { get; set; } public ApplicationLibrary ApplicationLibrary { get; set; } + public readonly double StatusBarHeight; + public readonly double MenuBarHeight; + public MainWindow() { ViewModel = new MainWindowViewModel(); @@ -74,7 +77,9 @@ namespace Ryujinx.Ava.UI.Windows ViewModel.Title = $"Ryujinx {Program.Version}"; // NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point. - double barHeight = MenuBar.MinHeight + StatusBarView.StatusBar.MinHeight; + StatusBarHeight = StatusBarView.StatusBar.MinHeight; + MenuBarHeight = MenuBar.MinHeight; + double barHeight = MenuBarHeight + StatusBarHeight; Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight; Width /= Program.WindowScaleFactor;