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>
This commit is contained in:
parent
5976a5161b
commit
56c5dbe557
6 changed files with 108 additions and 37 deletions
|
@ -94,6 +94,17 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
private long _lastCursorMoveTime;
|
private long _lastCursorMoveTime;
|
||||||
private bool _isCursorInRenderer = true;
|
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 _isStopped;
|
||||||
private bool _isActive;
|
private bool _isActive;
|
||||||
|
@ -201,23 +212,65 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
private void TopLevel_PointerEnteredOrMoved(object sender, PointerEventArgs e)
|
private void TopLevel_PointerEnteredOrMoved(object sender, PointerEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (!_viewModel.IsActive)
|
||||||
|
{
|
||||||
|
_isCursorInRenderer = false;
|
||||||
|
_ignoreCursorState = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (sender is MainWindow window)
|
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 point = e.GetCurrentPoint(window).Position;
|
||||||
var bounds = RendererHost.EmbeddedWindow.Bounds;
|
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 &&
|
_isCursorInRenderer = point.X >= bounds.X &&
|
||||||
point.X <= bounds.Width + bounds.X &&
|
Math.Ceiling(point.X) <= (int)window.Bounds.Width &&
|
||||||
point.Y >= bounds.Y &&
|
point.Y >= windowYOffset &&
|
||||||
point.Y <= bounds.Height + bounds.Y;
|
point.Y <= windowYLimit &&
|
||||||
|
!_viewModel.IsSubMenuOpen;
|
||||||
|
|
||||||
|
_ignoreCursorState = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TopLevel_PointerExited(object sender, PointerEventArgs e)
|
private void TopLevel_PointerExited(object sender, PointerEventArgs e)
|
||||||
{
|
{
|
||||||
_isCursorInRenderer = false;
|
_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)
|
private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs<int> e)
|
||||||
|
@ -245,9 +298,14 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
SetCursor(_defaultCursorWin);
|
if (_cursorState != CursorStates.CursorIsHidden && !_ignoreCursorState)
|
||||||
|
{
|
||||||
|
SetCursor(_defaultCursorWin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_cursorState = CursorStates.CursorIsVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HideCursor()
|
private void HideCursor()
|
||||||
|
@ -261,6 +319,8 @@ namespace Ryujinx.Ava
|
||||||
SetCursor(_invisibleCursorWin);
|
SetCursor(_invisibleCursorWin);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_cursorState = CursorStates.CursorIsHidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetRendererWindowSize(Size size)
|
private void SetRendererWindowSize(Size size)
|
||||||
|
@ -523,6 +583,8 @@ namespace Ryujinx.Ava
|
||||||
{
|
{
|
||||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_cursorState = CursorStates.ForceChangeCursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> LoadGuestApplication()
|
public async Task<bool> LoadGuestApplication()
|
||||||
|
@ -1037,38 +1099,32 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
if (_viewModel.IsActive)
|
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
|
else
|
||||||
{
|
{
|
||||||
switch (ConfigurationState.Instance.HideCursor.Value)
|
isCursorVisible = ConfigurationState.Instance.HideCursor.Value == HideCursorMode.Never ||
|
||||||
{
|
(ConfigurationState.Instance.HideCursor.Value == HideCursorMode.OnIdle &&
|
||||||
case HideCursorMode.Never:
|
Stopwatch.GetTimestamp() - _lastCursorMoveTime < CursorHideIdleTime * Stopwatch.Frequency);
|
||||||
ShowCursor();
|
|
||||||
break;
|
|
||||||
case HideCursorMode.OnIdle:
|
|
||||||
if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency)
|
|
||||||
{
|
|
||||||
HideCursor();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ShowCursor();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case HideCursorMode.Always:
|
|
||||||
HideCursor();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (_cursorState != (isCursorVisible ? CursorStates.CursorIsVisible : CursorStates.CursorIsHidden))
|
||||||
{
|
{
|
||||||
ShowCursor();
|
if (isCursorVisible)
|
||||||
|
{
|
||||||
|
ShowCursor();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HideCursor();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
@ -1154,7 +1210,7 @@ namespace Ryujinx.Ava
|
||||||
// Touchscreen.
|
// Touchscreen.
|
||||||
bool hasTouch = false;
|
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());
|
hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,8 +111,5 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||||
|
|
||||||
[LibraryImport("user32.dll", SetLastError = true)]
|
[LibraryImport("user32.dll", SetLastError = true)]
|
||||||
public static partial IntPtr SetWindowLongPtrW(IntPtr hWnd, int nIndex, IntPtr value);
|
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,7 +157,7 @@ namespace Ryujinx.Ava.UI.Renderer
|
||||||
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
|
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
|
||||||
style = ClassStyles.CsOwndc,
|
style = ClassStyles.CsOwndc,
|
||||||
lpszClassName = Marshal.StringToHGlobalUni(_className),
|
lpszClassName = Marshal.StringToHGlobalUni(_className),
|
||||||
hCursor = CreateArrowCursor(),
|
hCursor = CreateArrowCursor()
|
||||||
};
|
};
|
||||||
|
|
||||||
RegisterClassEx(ref wndClassEx);
|
RegisterClassEx(ref wndClassEx);
|
||||||
|
|
|
@ -104,6 +104,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
private double _windowHeight;
|
private double _windowHeight;
|
||||||
|
|
||||||
private bool _isActive;
|
private bool _isActive;
|
||||||
|
private bool _isSubMenuOpen;
|
||||||
|
|
||||||
public ApplicationData ListSelectedApplication;
|
public ApplicationData ListSelectedApplication;
|
||||||
public ApplicationData GridSelectedApplication;
|
public ApplicationData GridSelectedApplication;
|
||||||
|
@ -317,6 +318,17 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsSubMenuOpen
|
||||||
|
{
|
||||||
|
get => _isSubMenuOpen;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isSubMenuOpen = value;
|
||||||
|
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool ShowAll
|
public bool ShowAll
|
||||||
{
|
{
|
||||||
get => _showAll;
|
get => _showAll;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<UserControl
|
<UserControl
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
@ -16,7 +16,8 @@
|
||||||
Name="Menu"
|
Name="Menu"
|
||||||
Height="35"
|
Height="35"
|
||||||
Margin="0"
|
Margin="0"
|
||||||
HorizontalAlignment="Left">
|
HorizontalAlignment="Left"
|
||||||
|
IsOpen="{Binding IsSubMenuOpen, Mode=OneWayToSource}">
|
||||||
<Menu.ItemsPanel>
|
<Menu.ItemsPanel>
|
||||||
<ItemsPanelTemplate>
|
<ItemsPanelTemplate>
|
||||||
<DockPanel Margin="0" HorizontalAlignment="Stretch" />
|
<DockPanel Margin="0" HorizontalAlignment="Stretch" />
|
||||||
|
|
|
@ -56,6 +56,9 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
public static bool ShowKeyErrorOnLoad { get; set; }
|
public static bool ShowKeyErrorOnLoad { get; set; }
|
||||||
public ApplicationLibrary ApplicationLibrary { get; set; }
|
public ApplicationLibrary ApplicationLibrary { get; set; }
|
||||||
|
|
||||||
|
public readonly double StatusBarHeight;
|
||||||
|
public readonly double MenuBarHeight;
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
ViewModel = new MainWindowViewModel();
|
ViewModel = new MainWindowViewModel();
|
||||||
|
@ -74,7 +77,9 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
ViewModel.Title = $"Ryujinx {Program.Version}";
|
ViewModel.Title = $"Ryujinx {Program.Version}";
|
||||||
|
|
||||||
// NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
|
// 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;
|
Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight;
|
||||||
Width /= Program.WindowScaleFactor;
|
Width /= Program.WindowScaleFactor;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue