Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore pointer capture for PointerEntered and PointerExited events following UWP behavior #15357

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

maxkatz6
Copy link
Member

What does the pull request do?

This one is interesting, and might not be an expected behavior for some (including me).
But after testing #15293 on UWP, I noticed that they handle PointerExited event differently from how we do.

Bonus change: fixed devtools incorrectly reporting events that reuse the same RoutedEventArgs instance.

What is the current behavior?

In Avalonia before this PR it looked like:

// Enter pointer over control, PointerEntered is executed before Move, built-in IsPointerOver property is true.
Border PointerEntered Captured: False PointerOver: True
Border PointerMoved Captured: False PointerOver: True

// Press pointer, Avalonia automatically captures pointer. PointerOver is still true.
Border PointerPressed Captured: True PointerOver: True

// Move pointer out of the control, captured element is the same, but PointerExited isn't executed **at all**.
// This is because pointer exited logic respects Captured element, and ignored actual hit test result.
Border PointerMoved Captured: True PointerOver: True

// Release pointer, control is still captured in the event handler, but will automatically release capture right after.
Border PointerReleased Captured: True PointerOver: True

// And PointerExited is raised ONLY after capture was lost.
Border PointerExited Captured: False PointerOver: False

What is the updated/expected behavior with this PR?

In UWP behavior looks like:

// Enter pointer over control, PointerEntered is executed before Move, manually mark PointerOver as true.
Border.PointerEntered Captured: False PointerOver:True
Border PointerMoved Captured: False PointerOver:True

// Press pointer, UWP does not automatically capture pointer, so we do it manually. Pointer is still over.
Border PointerPressed Captured: True PointerOver:True

// Move pointer out of the control, captured element is the same (as expected), but PointerExited is actually executed anyway.
Border PointerMoved Captured: True PointerOver:True
Border PointerExited Captured: True PointerOver:False
Border PointerMoved Captured: True PointerOver:False

// Release pointer way outside of the control, control is still captured in the event handler, but will automatically release capture right after.
Border PointerReleased Captured: True PointerOver:False 
Border PointerMoved Captured: False PointerOver:False

After this PR, Avalonia behavior is the same as in UWP. Aside from automatic capturing on pointer pressed.

Checklist

Breaking changes

Behavioral change.

Fixed issues

Fixes #15293

@maxkatz6
Copy link
Member Author

I wonder if there are any other corner cases that I could have missed.
But so far it looks just as simple as ignoring capture element completely.

cc @robloo @kekekeks

Projects that I used to test (WinUI and Avalonia sandbox):
App2.zip
Sandbox.zip

@BAndysc
Copy link
Contributor

BAndysc commented Apr 13, 2024

Not related to the PR directly, but btw. is there any reason why Avalonia automatically captures pointer? This is causing some issues in my port of node control - Nodify. I guess this behaviour can't be changed now, because many apps behave on this behaviour. What if before capturing the pointer

_pointer.Capture(source);
, some event would be emitted which could be used to cancel capturing the pointer?

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 11.2.999-cibuild0047270-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@robloo
Copy link
Contributor

robloo commented Apr 16, 2024

Personally, I think this is a good change. PointerExited should follow the control bounds regardless of capture. There are cases where you need pointer exited to fire while PointerMoved events are still firing on the control. To me UWP behavior makes the most sense and seems most usable.

Was this behavior confirmed on macOS as well? We know the pointer event logic is different there and needs a few fixes: #13977

I also agree that perhaps Avalonia shouldn't automatically capture the pointer. However, in porting code over from UWP/WinUI I found this actually wasn't an issue. The automatic capture helped in a few places and didn't seem to hurt anything.

Side note that I wouldn't follow UWP pointer event behavior in all cases. There are enough quirks there that we probably don't want. For example: PointerReleased not always called due to the addition of PointerCanceled: https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.uielement.pointercanceled?view=winrt-22621#remarks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

IsPointerOver does not Work with OnPointerReleased
4 participants