Browse Source

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>
Exhigh 2 years ago
parent
commit
56c5dbe557

+ 86 - 30
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<int> 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<bool> 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());
             }

+ 0 - 3
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);
     }
 }

+ 1 - 1
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);

+ 12 - 0
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;

+ 3 - 2
src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml

@@ -1,4 +1,4 @@
-<UserControl
+<UserControl
     xmlns="https://github.com/avaloniaui"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -16,7 +16,8 @@
             Name="Menu"
             Height="35"
             Margin="0"
-            HorizontalAlignment="Left">
+            HorizontalAlignment="Left"
+            IsOpen="{Binding IsSubMenuOpen, Mode=OneWayToSource}">
             <Menu.ItemsPanel>
                 <ItemsPanelTemplate>
                     <DockPanel Margin="0" HorizontalAlignment="Stretch" />

+ 6 - 1
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;