maui
maui copied to clipboard
Custom Cursor when hovering over View on Desktop
On Desktop platforms, when hovering over a view, provide the ability to change the mouse cursor.
Could we provide this via an attached property, or perhaps provide an out of the box behaviour for this?
The API should provide a way to select and set one of the available system cursor types when the mouse/pointer is over a view. This could be an enum of cursor values.
Notes:
- Windows: https://docs.microsoft.com/en-us/uwp/api/windows.ui.core.corecursortype?view=winrt-22000
- MacCatalyst: https://gist.github.com/Redth/489cbbb934c22aa9db5412e73ee58134 this is done via a UIHoverGestureRecognizer, though we may need a click handler as well as the hover gesture stops when clicking
We can probably use NSTrackingArea
for macOS to avoid affecting the gestures:
https://developer.apple.com/documentation/appkit/nstrackingarea https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/TrackingAreaObjects/TrackingAreaObjects.html#//apple_ref/doc/uid/10000060i-CH8-SW1
I encourage the MAUI team to think beyond desktop for this. iPadOS also supports customizing cursors, albeit in a more focused way. E.g. https://developer.apple.com/documentation/uikit/uipointershape/roundedrect_radius Android also has a cursor API https://developer.android.com/reference/android/view/PointerIcon
I think MAUI must cover all things, desktop and mobiles/tablets environments. I saw a lot of "tutorials" on youtube with MAUI, but most of them are for android environment! Why they (we) have to think of MAUI only to mobile devices as long as it was made for desktop, mobile and now TV OSes?
So, what I want to tell here is MAUI must cover the MouseHover
events for desktop environments, Cursor
property, and all it needs to be able to cover the desktop application and Mouse
events as well.
Assuming for the moment the app developer knows the pointer has entered or exited the area occupied by a specific control, can the mouse cursor actually be changed in Maui? For example, SkiaSharp does provide enough information to know when you want to change the cursor, but how do you actually change the cursor?
for Windows i currently use the following Extension method to change the cursor
public static void ChangeCursor(this UIElement uiElement, InputCursor cursor)
{
Type type = typeof(UIElement);
type.InvokeMember("ProtectedCursor", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, null, uiElement, new object[] { cursor });
}
it can be called like this (for example in the EventHandler
of a PointerGestureRecognizer
)
myUIElement.ChangeCursor(InputSystemCursor.Create(InputSystemCursorShape.Hand));
on macOS it's even easier - i'm using
NSCursor.PointingHandCursor.Set();
Cursors are relatively easy to set on MacCatalyst as well:
AppKit.NSCursor.ResizeDownCursor.Push(); // .Pop() to remove
However the problem is, the selection of cursor types is limited on MacCatalyst compared to AppKit. For example, there is no ResizeNorthWestCursor
(or NorthEast, SouthWest, SouthEast - so none of the 'corner' resize cursors).
It's trivial to access these 'unsupported' API's via C# code:
void setCursor(bool push, string cursorType = "_windowResizeNorthEastCursor")
{
var nsCursor = Runtime.GetNSObject(Class.GetHandle(nameof(NSCursor)));
var cursor = nsCursor.PerformSelector(new Selector(cursorType));
cursor.PerformSelector(new Selector(push ? "push" : "pop"));
}
// Unsupported cursors:
//[NSCursor _windowResizeEastCursor]
//[NSCursor _windowResizeWestCursor]
//[NSCursor _windowResizeEastWestCursor]
//[NSCursor _windowResizeNorthCursor]
//[NSCursor _windowResizeSouthCursor]
//[NSCursor _windowResizeNorthSouthCursor]
//[NSCursor _windowResizeNorthEastCursor]
//[NSCursor _windowResizeNorthWestCursor]
//[NSCursor _windowResizeSouthEastCursor]
//[NSCursor _windowResizeSouthWestCursor]
//[NSCursor _windowResizeNorthEastSouthWestCursor]
//[NSCursor _windowResizeNorthWestSouthEastCursor]
//[NSCursor _zoomInCursor]
//[NSCursor _zoomOutCursor]
//[NSCursor _helpCursor]
//[NSCursor _copyDragCursor]
//[NSCursor _genericDragCursor]
//[NSCursor _handCursor]
//[NSCursor _closedHandCursor]
//[NSCursor _moveCursor]
//[NSCursor _waitCursor]
//[NSCursor _crosshairCursor]
//[NSCursor _horizontalResizeCursor]
//[NSCursor _verticalResizeCursor]
//[NSCursor _bottomLeftResizeCursor]
//[NSCursor _topLeftResizeCursor]
//[NSCursor _bottomRightResizeCursor]
//[NSCursor _topRightResizeCursor]
//[NSCursor _resizeLeftCursor]
//[NSCursor _resizeRightCursor]
//[NSCursor _resizeLeftRightCursor]
But this is not something we are currently comfortable adding to the core MAUI product. We have no reason to believe Apple will reject MacCatalyst app store submissions which use API's officially supported on AppKit, but not officially supported on MacCatalyst, and there is reasonable evidence if you search, to suggest many apps are shipping to the store with these types of API usages. But we also don't want to add implicitly agreed to risk to our customers by adding this code to MAUI.
For now, I'd suggest writing a simple service that can handle mouse cursors on each of the platforms you're interested in, and deciding if you want to leverage these unsupported API's on MacCatalyst or not yourself so that you are in control if something changes with Apple's decisions around appstore submissions in this area.
I think MAUI must cover all things, desktop and mobiles/tablets environments. I saw a lot of "tutorials" on youtube with MAUI, but most of them are for android environment! Why they (we) have to think of MAUI only to mobile devices as long as it was made for desktop, mobile and now TV OSes? So, what I want to tell here is MAUI must cover the
MouseHover
events for desktop environments,Cursor
property, and all it needs to be able to cover the desktop application andMouse
events as well.
@jamesmontemagno, @jfversluis, and @davidortinau, I see I'm not the only one who feels that the MAUI team has relegated Desktop to the back burner...
for Windows i currently use the following Extension method to change the cursor
public static void ChangeCursor(this UIElement uiElement, InputCursor cursor) { Type type = typeof(UIElement); type.InvokeMember("ProtectedCursor", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, null, uiElement, new object[] { cursor }); }
it can be called like this (for example in the
EventHandler
of aPointerGestureRecognizer
)myUIElement.ChangeCursor(InputSystemCursor.Create(InputSystemCursorShape.Hand));
on macOS it's even easier - i'm using
NSCursor.PointingHandCursor.Set();
Thanks for this @nor0x !! It was really helpful! (P.S. Mentioning how you convert the MAUI elements to UIElement could have been a plus but figured that out anyways :)
Thanks for this!!!
On Sun, Feb 19, 2023 at 10:50 PM Shivji Bhagat @.***> wrote:
for Windows i currently use the following Extension method to change the cursor
public static void ChangeCursor(this UIElement uiElement, InputCursor cursor) { Type type = typeof(UIElement); type.InvokeMember("ProtectedCursor", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, null, uiElement, new object[] { cursor }); }
it can be called like this (for example in the EventHandler of a PointerGestureRecognizer)
myUIElement.ChangeCursor(InputSystemCursor.Create(InputSystemCursorShape.Hand));
on macOS it's even easier - i'm using
NSCursor.PointingHandCursor.Set();
Thanks for this @nor0x https://github.com/nor0x !! It was really helpful, after not getting anything in the official docs!
— Reply to this email directly, view it on GitHub https://github.com/dotnet/maui/issues/4552#issuecomment-1436419251, or unsubscribe https://github.com/notifications/unsubscribe-auth/AOPRYYPVR5IYSXZPCEPU3Z3WYMH5FANCNFSM5NZF5YJA . You are receiving this because you commented.Message ID: @.***>
for Windows i currently use the following Extension method to change the cursor
public static void ChangeCursor(this UIElement uiElement, InputCursor cursor) { Type type = typeof(UIElement); type.InvokeMember("ProtectedCursor", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, null, uiElement, new object[] { cursor }); }
it can be called like this (for example in the
EventHandler
of aPointerGestureRecognizer
)myUIElement.ChangeCursor(InputSystemCursor.Create(InputSystemCursorShape.Hand));
on macOS it's even easier - i'm using
NSCursor.PointingHandCursor.Set();
Thanks for this @nor0x !! It was really helpful! (P.S. Mentioning how you convert the MAUI elements to UIElement could have been a plus but figured that out anyways :)
about getting the UIElement
- it could be done as simple as that for Windows and macOS
void PointerEntered(object sender, Microsoft.Maui.Controls.PointerEventArgs e)
{
if (sender is Label label)
{
#if WINDOWS
if (label.Handler.PlatformView is Microsoft.UI.Xaml.Controls.TextBlock textBlock)
{
textBlock.ChangeCursor(InputSystemCursor.Create(InputSystemCursorShape.Hand));
}
#endif
#if MACCATALYST
NSCursor.PointingHandCursor.Set();
#endif
}
}
I agree with that, providing the ability to change the mouse cursor when hovering over a view can be a useful feature for desktop apps, as it can help to provide visual feedback to users and guide their interactions with the application.
There is also https://github.com/VladislavAntonyuk/MauiSamples/tree/main/MauiCursor by @VladislavAntonyuk for all platforms.
This is still reproducible on blazor hybrid (maui) with .net 8, "cursor:pointer" css is not taking any effect.
@orosbogdan said:
This is still reproducible on blazor hybrid (maui) with .net 8, "cursor:hand" css is not taking any effect.
For CSS it's up to each platform's native webview whether it supports certain CSS properties. I couldn't find any cursor: hand
support on any platform. And it seems that in WebView2 some CSS cursors work, but not all: https://github.com/MicrosoftEdge/WebView2Feedback/issues/2766
From my test just now with WebView2:
- N/A: auto/default - not very meaningful to test
- Working: pointer, wait, text, n-resize, e-resize, s-resize, w-resize, ns-resize, ew-resize, ne-resize, nw-resize, se-resize, sw-resize, nesw-resize, nwse-resize
- Not working: none, context-menu, help, progress, cell, crosshair, vertical-text, alias, copy, move, no-drop, not-allowed, all-scroll, col-resize, row-resize
Interestingly, many of these seem to work fine in Edge itself, but not in Edge WebView2. Not sure why that is. But there's this WebView2 bug filed to track that: https://github.com/MicrosoftEdge/WebView2Feedback/issues/2766. I added some additional info there about my testing and the current status.
So as far as WebView/BlazorWebView is concerned, this issue is up to each platform's own native WebView, and I don't think any further action is requried within .NET MAUI.
But this bug should remain open to track support for native UI (as opposed to web UI).
This is still reproducible on blazor hybrid (maui) with .net 8, "cursor:hand" css is not taking any effect.
I think by hand
you mean cursor: pointer;
or cursor: grab;
!
This is still reproducible on blazor hybrid (maui) with .net 8, "cursor:hand" css is not taking any effect.
I think by
hand
you meancursor: pointer;
orcursor: grab;
!
Right, my bad, I meant cursor:pointer.