dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.07k stars 1.73k forks source link

Custom Cursor when hovering over View on Desktop #4552

Open Redth opened 2 years ago

Redth commented 2 years ago

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:

mattleibow commented 2 years ago

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

nCastle1 commented 2 years ago

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

valimaties commented 1 year ago

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.

gbelicka commented 1 year ago

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?

nor0x commented 1 year ago

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();

Redth commented 1 year ago

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.

mobycorp commented 1 year ago

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.

@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...

shv07 commented 1 year ago

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 !! 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 :)

mobycorp commented 1 year ago

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: @.***>

nor0x commented 1 year ago

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 !! 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
        }
    }
mouralabank commented 1 year ago

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.

MartyIX commented 1 year ago

There is also https://github.com/VladislavAntonyuk/MauiSamples/tree/main/MauiCursor by @VladislavAntonyuk for all platforms.

orosbogdan commented 8 months ago

This is still reproducible on blazor hybrid (maui) with .net 8, "cursor:pointer" css is not taking any effect.

Eilon commented 7 months ago

@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:

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).

mhrastegari commented 7 months ago

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;!

orosbogdan commented 7 months ago

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;!

Right, my bad, I meant cursor:pointer.