From 4f666f0ba1432555a0e41a438cabe42be3aeed47 Mon Sep 17 00:00:00 2001 From: Vladislav Pozdniakov Date: Fri, 15 Aug 2025 12:21:01 +0100 Subject: [PATCH] Fix macOS thick titlebar mouse event duplication (#19447) * Added failing test for OSXThickTitleBar single title area click produce double click #19320 * Fixed OSXThickTitleBar title area click duplication and event delays until the event-tracking-loop is completed * IntegrationTestApp. Move event counter controls to a separate column. Fixes Changing_Size_Should_Not_Change_Position test * Move pointer tests to Default group to avoid interference * Try to fix CI crash * Try disabling test * Fix CI test. Collection back to Default * CI fix. Return back empty test. * CI fix. Minimal bug test * CI test. Add double click test * CI fix. Remove double click test. --------- Co-authored-by: Julien Lebosquain --- native/Avalonia.Native/src/OSX/AvnWindow.mm | 98 +++++++------------ .../IntegrationTestApp/ShowWindowTest.axaml | 18 ++-- .../ShowWindowTest.axaml.cs | 27 +++++ .../ElementExtensions.cs | 5 + .../PointerTests_MacOS.cs | 53 +++++++++- 5 files changed, 134 insertions(+), 67 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index 03daa2f296..79fe095731 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -35,6 +35,7 @@ bool _canBecomeKeyWindow; bool _isExtended; bool _isTransitioningToFullScreen; + bool _isTitlebarSession; AvnMenu* _menu; IAvnAutomationPeer* _automationPeer; AvnAutomationNode* _automationNode; @@ -501,68 +502,10 @@ return NO; } -- (void)forwardToAvnView:(NSEvent *)event -{ - auto parent = _parent.tryGetWithCast(); - if (!parent) { - return; - } - - switch(event.type) { - case NSEventTypeLeftMouseDown: - [parent->View mouseDown:event]; - break; - case NSEventTypeLeftMouseUp: - [parent->View mouseUp:event]; - break; - case NSEventTypeLeftMouseDragged: - [parent->View mouseDragged:event]; - break; - case NSEventTypeRightMouseDown: - [parent->View rightMouseDown:event]; - break; - case NSEventTypeRightMouseUp: - [parent->View rightMouseUp:event]; - break; - case NSEventTypeRightMouseDragged: - [parent->View rightMouseDragged:event]; - break; - case NSEventTypeOtherMouseDown: - [parent->View otherMouseDown:event]; - break; - case NSEventTypeOtherMouseUp: - [parent->View otherMouseUp:event]; - break; - case NSEventTypeOtherMouseDragged: - [parent->View otherMouseDragged:event]; - break; - case NSEventTypeMouseMoved: - [parent->View mouseMoved:event]; - break; - default: - break; - } -} - - (void)sendEvent:(NSEvent *_Nonnull)event { - // Event-tracking loop for thick titlebar mouse events - if (event.type == NSEventTypeLeftMouseDown && [self isPointInTitlebar:event.locationInWindow]) - { - NSEventMask mask = NSEventMaskLeftMouseDragged | NSEventMaskLeftMouseUp; - NSEvent *ev = event; - while (ev.type != NSEventTypeLeftMouseUp) - { - [self forwardToAvnView:ev]; - [super sendEvent:ev]; - ev = [NSApp nextEventMatchingMask:mask - untilDate:[NSDate distantFuture] - inMode:NSEventTrackingRunLoopMode - dequeue:YES]; - } - [self forwardToAvnView:ev]; - [super sendEvent:ev]; - return; + if (event.type == NSEventTypeLeftMouseDown) { + _isTitlebarSession = [self isPointInTitlebar:event.locationInWindow]; } [super sendEvent:event]; @@ -603,6 +546,37 @@ } break; + case NSEventTypeLeftMouseDragged: + case NSEventTypeMouseMoved: + case NSEventTypeLeftMouseUp: + { + // Usually NSToolbar events are passed natively to AvnView when the mouse is inside the control. + // When a drag operation started in NSToolbar leaves the control region, the view does not get any + // events. We will detect this scenario and pass events ourselves. + + if(!_isTitlebarSession || [self isPointInTitlebar:event.locationInWindow]) + break; + + AvnView* view = parent->View; + + if(!view) + break; + + if(event.type == NSEventTypeLeftMouseDragged) + { + [view mouseDragged:event]; + } + else if(event.type == NSEventTypeMouseMoved) + { + [view mouseMoved:event]; + } + else if(event.type == NSEventTypeLeftMouseUp) + { + [view mouseUp:event]; + } + } + break; + case NSEventTypeMouseEntered: { parent->UpdateCursor(); @@ -618,6 +592,10 @@ default: break; } + + if(event.type == NSEventTypeLeftMouseUp) { + _isTitlebarSession = NO; + } } } diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml index c43e00497f..272c61ed0c 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml @@ -15,7 +15,7 @@ - + @@ -62,13 +62,19 @@