mono / SkiaSharp

SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library. It provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.
MIT License
4.41k stars 537 forks source link

[BUG] SKCanvasView on MAUI(MacCatalyst) captures the mouse and does not release it #2367

Open polqnaP opened 1 year ago

polqnaP commented 1 year ago

Description Maui: On MacCatalyst the SKCanvasView captures the mouse and does not release it if EnableTouchEvents=true and the Touch is handled. This is a blocker if one wishes to implement drawing in a SKCanvasView based on touch events inside the view.

Code Add a Button and a SKCanvasView in a ContentPage in a Maui app.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:TestSKCanvasViewTouch"
             xmlns:controls="clr-namespace:SkiaSharp.Views.Maui.Controls;assembly=SkiaSharp.Views.Maui.Controls"
             x:Class="TestSKCanvasViewTouch.MainPage">
    <VerticalStackLayout>
        <Button x:Name="CounterBtn" Clicked="OnCounterClicked" Text="Clicked 0 times."/>
        <controls:SKCanvasView HeightRequest="400" BackgroundColor="AliceBlue" EnableTouchEvents="True" Touch="SKCanvasView_Touch"/>
    </VerticalStackLayout>
</ContentPage>

Handle the Touch event of the SKCanvasView

public partial class MainPage : ContentPage
{
    int count = 0;

    public MainPage()
    {
        InitializeComponent();
    }

    private void OnCounterClicked(object sender, EventArgs e)
    {
        count++;

        if (count == 1)
            CounterBtn.Text = $"Clicked {count} time";
        else
            CounterBtn.Text = $"Clicked {count} times";

        SemanticScreenReader.Announce(CounterBtn.Text);
     }

     private void SKCanvasView_Touch(System.Object sender, SkiaSharp.Views.Maui.SKTouchEventArgs e)
     {
            e.Handled = true;
     }
}

Expected Behavior Mouse down, then move, then up in the SKCanvasView. Then click the button - the button is clicked (Clicked event handler is called; Command is executed).

Actual Behavior Mouse down, then move, then up in the SKCanvasView. Then click the Button - the Button is not clicked (Clicked is not called; the Button command is not executed).

Basic Information

Detailed IDE/OS information (click to expand) ``` macOS: 12.6 ```

Screenshots

https://user-images.githubusercontent.com/61967449/214117627-a77342c7-ebde-433d-8d00-4ead0c74ad28.mov

Reproduction Link https://github.com/telerik/ms-samples/tree/main/Maui/SKCanvasViewTouchCaptured_MacCatalyst

DanTravison commented 10 months ago

@polqnaP - did you find a resolution to this? I'm seeing it also.

DanTravison commented 9 months ago

Can anyone provide any insight here? I'm hitting this in three different scenarios, drawing on the canvas with mouse/touch, a custom slider control, and resizer thumb.

I thought it limited to the thumb since I'm moving SKCanvasView within move touch events but I also see it in other cases where I'm simply redrawing a shape on the canvas view.

FWIW: it appears the capture remains in place until I switch away, click on the apps window header, or wait for a while(??). It doesn't block touch interaction with the SKCanvasView but does prevent touch events to controls outside the SKCanvasView.

I'm using Maui 8.03, SkiaSharp.Maui.Controls/Corel 2.88.6

DanTravison commented 9 months ago

I've narrowed down the repro somewhat.

When I click the mouse button, I receive a Pressed event and set e.Handled to true. As long as I keep the mouse pointer within the application's Window, the capture is NOT released when the mouse button is released. If I move the pointer outside the application's window before releasing the button, the capture is released.

The behavior is the same using the Touch event or overriding OnTouch.

I'm running on a Macbook Air/M2 with Simona 14.1.1.

DanTravison commented 9 months ago

I believe I found the root cause of the capture not being released.

In SKTouchHandler.TouchesEnded, FireEvent is called for each subscriber but the UIGestureRecognizer.State is left unchanged. I changed the logic to set State to Recognized if any subscriber sets TouchEventArgs.Handled to true.

This appears to resolve the issue for both Mouse and TouchPad usage for both simple 'click' usage as well as press/move/release.

I do have a few caveats:

I'm not a mac developer and don't play one on TV and I don't know how this will impact other usages. I implemented it by creating my own UIGestureRecognizer modeling SKTouchHandler's logic with my change. I inject the recognizer in HandlerChanged and control enabling it outside EnableTouchEvents. I still have deployment issues to iOS so I haven't been able to verify it in a simulator or iOS device.

DanTravison commented 7 months ago

I'm surprised this isn't getting any attention; it occurs whenever a press/release occurs withing the confines of an SKCanvasView on MacCatalyst.

It's intrusive enough that I ended up completely replacing touch handling is SKCanvasView to get consistent behavior across platforms.