vcl/inc/osx/salframeview.h | 7 +++ vcl/osx/salframe.cxx | 30 -------------- vcl/osx/salframeview.mm | 91 ++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 94 insertions(+), 34 deletions(-)
New commits: commit c64a6b23b54137301dc93bc1f0012509df5868f6 Author: Patrick Luby <guibmac...@gmail.com> AuthorDate: Tue May 13 09:34:35 2025 -0400 Commit: Adolfo Jayme Barrientos <fit...@ubuntu.com> CommitDate: Mon May 19 15:24:58 2025 +0200 tdf#163945 Coalesce mouse dragged events Previously, the rate of flushes when using Skia/Metal were limited. While the various approaches to limiting flushes fixed tdf#166258, they caused tdf#163945 to reoccur on some machines. So instead of trying to reduce the number of Skia/Metal flushes per second, coalesce mouse dragged events when macOS floods the native event queue with mouse dragged events. Change-Id: Ie355c9da705d7ff7d045b924ba0b3d10c093e780 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/185258 Tested-by: Jenkins Reviewed-by: Patrick Luby <guibomac...@gmail.com> (cherry picked from commit 5913201efff027e683b2ff15349943d99b726414) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/185371 Reviewed-by: Adolfo Jayme Barrientos <fit...@ubuntu.com> diff --git a/vcl/inc/osx/salframeview.h b/vcl/inc/osx/salframeview.h index 8b141f7aafe5..8254d8973a58 100644 --- a/vcl/inc/osx/salframeview.h +++ b/vcl/inc/osx/salframeview.h @@ -114,9 +114,14 @@ enum class SalEvent; NSTrackingArea* mpLastTrackingArea; BOOL mbInViewDidChangeEffectiveAppearance; + + NSTimer* mpMouseDraggedTimer; + NSEvent* mpPendingMouseDraggedEvent; } +(void)unsetMouseFrame: (AquaSalFrame*)pFrame; -(id)initWithSalFrame: (AquaSalFrame*)pFrame; +-(void)clearMouseDraggedTimer; +-(void)clearPendingMouseDraggedEvent; -(void)dealloc; -(AquaSalFrame*)getSalFrame; -(BOOL)acceptsFirstResponder; @@ -274,6 +279,8 @@ enum class SalEvent; -(void)viewDidChangeEffectiveAppearance; +-(void)mouseDraggedWithTimer:(NSTimer *)pTimer; + @end @interface SalFrameViewA11yWrapper : AquaA11yWrapper diff --git a/vcl/osx/salframe.cxx b/vcl/osx/salframe.cxx index f5823da6ab5f..b24e8974b972 100644 --- a/vcl/osx/salframe.cxx +++ b/vcl/osx/salframe.cxx @@ -57,10 +57,6 @@ #include <quartz/CGHelpers.hxx> #include <postmac.h> -#if HAVE_FEATURE_SKIA -#include <vcl/skia/SkiaHelper.hxx> -#endif - const int nMinBlinkCursorDelay = 500; AquaSalFrame* AquaSalFrame::s_pCaptureFrame = nullptr; @@ -1148,34 +1144,12 @@ bool AquaSalFrame::doFlush() if( mbForceFlushScrolling || mbForceFlushProgressBar || ImplGetSVData()->maAppData.mnDispatchLevel <= 0 ) { - // Related: tdf#163945 don't directly flush graphics with Skia/Metal - // When dragging a selection box on an empty background in - // Impress and only with Skia/Metal, the selection box - // would not keep up with the pointer. The selection box - // would repaint sporadically or not at all if the pointer - // was dragged rapidly and the status bar was visible. - // Apparently, flushing a graphics doesn't actually do much - // of anything with Skia/Raster and Skia disabled so the - // selection box repaints without any noticeable delay. - // However, with Skia/Metal every flush of a graphics - // creates and queues a new CAMetalLayer drawable. During - // rapid dragging, this can lead to creating and queueing - // up to 200 drawables per second leaving no spare time for - // the Impress selection box painting timer to fire. - // So with Skia/Metal, throttle the rate of flushing by - // calling display on the view. - bool bDisplay = false; -#if HAVE_FEATURE_SKIA - // tdf#164428 Skia/Metal needs flush after drawing progress bar - bDisplay = !mbForceFlushProgressBar && SkiaHelper::isVCLSkiaEnabled() && SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster; -#endif - if (!bDisplay) - mpGraphics->Flush(); + mpGraphics->Flush(); // Related: tdf#155266 skip redisplay of the view when forcing flush // It appears that calling -[NSView display] overwhelms some Intel Macs // so only flush the graphics and skip immediate redisplay of the view. - bRet = bDisplay || ImplGetSVData()->maAppData.mnDispatchLevel <= 0; + bRet = ImplGetSVData()->maAppData.mnDispatchLevel <= 0; mbForceFlushScrolling = false; mbForceFlushProgressBar = false; diff --git a/vcl/osx/salframeview.mm b/vcl/osx/salframeview.mm index 3e50105f178d..640c91c006c6 100644 --- a/vcl/osx/salframeview.mm +++ b/vcl/osx/salframeview.mm @@ -961,13 +961,39 @@ static void updateMenuBarVisibility( const AquaSalFrame *pFrame ) mpLastTrackingArea = nil; mbInViewDidChangeEffectiveAppearance = NO; + + mpMouseDraggedTimer = nil; + mpPendingMouseDraggedEvent = nil; } return self; } +-(void)clearMouseDraggedTimer +{ + if ( mpMouseDraggedTimer ) + { + [mpMouseDraggedTimer invalidate]; + [mpMouseDraggedTimer release]; + mpMouseDraggedTimer = nil; + } + + // Clear the pending mouse dragged event as well + [self clearPendingMouseDraggedEvent]; +} + +-(void)clearPendingMouseDraggedEvent +{ + if ( mpPendingMouseDraggedEvent ) + { + [mpPendingMouseDraggedEvent release]; + mpPendingMouseDraggedEvent = nil; + } +} + -(void)dealloc { + [self clearMouseDraggedTimer]; [self clearLastEvent]; [self clearLastMarkedText]; [self clearLastTrackingArea]; @@ -1137,17 +1163,47 @@ static void updateMenuBarVisibility( const AquaSalFrame *pFrame ) -(void)mouseDragged: (NSEvent*)pEvent { - if ( mpMouseEventListener != nil && - [mpMouseEventListener respondsToSelector: @selector(mouseDragged:)]) - { - [mpMouseEventListener mouseDragged: [pEvent copyWithZone: nullptr]]; + // tdf#163945 Coalesce mouse dragged events + // When dragging a selection box on an empty background in Impress + // while using Skia/Metal, the selection box would not keep up with + // the pointer. The selection box would repaint sporadically or not + // at all if the pointer was dragged rapidly and the status bar was + // visible. + // Apparently, flushing a graphics doesn't actually do much of + // anything with Skia/Raster and Skia disabled so the selection box + // repaints without any noticeable delay. + // However, with Skia/Metal every flush of a graphics creates and + // queues a new CAMetalLayer drawable. During rapid dragging, this + // can lead to creating and queueing up to 200 drawables per second + // leaving no spare time for the Impress selection box painting + // timer to fire. So coalesce mouse dragged events so that only + // a maximum of 50 mouse dragged events are dispatched per second. + [self clearPendingMouseDraggedEvent]; + mpPendingMouseDraggedEvent = [pEvent retain]; + if ( !mpMouseDraggedTimer ) + { + mpMouseDraggedTimer = [NSTimer scheduledTimerWithTimeInterval:0.025f target:self selector:@selector(mouseDraggedWithTimer:) userInfo:nil repeats:YES]; + if ( mpMouseDraggedTimer ) + { + [mpMouseDraggedTimer retain]; + + // The timer won't fire without a call to + // Application::Reschedule() unless we copy the fix for + // #i84055# from vcl/osx/saltimer.cxx and add the timer + // to the NSEventTrackingRunLoopMode run loop mode + [[NSRunLoop currentRunLoop] addTimer:mpMouseDraggedTimer forMode:NSEventTrackingRunLoopMode]; + } } - s_nLastButton = MOUSE_LEFT; - [self sendMouseEventToFrame:pEvent button:MOUSE_LEFT eventtype:SalEvent::MouseMove]; } -(void)mouseUp: (NSEvent*)pEvent { + // Dispatch any pending mouse dragged event before dispatching the + // mouse up event + if ( mpPendingMouseDraggedEvent ) + [self mouseDraggedWithTimer: nil]; + [self clearMouseDraggedTimer]; + s_nLastButton = 0; [self sendMouseEventToFrame:pEvent button:MOUSE_LEFT eventtype:SalEvent::MouseButtonUp]; } @@ -2884,6 +2940,29 @@ static void updateMenuBarVisibility( const AquaSalFrame *pFrame ) mbInViewDidChangeEffectiveAppearance = NO; } +-(void)mouseDraggedWithTimer: (NSTimer *)pTimer +{ + (void)pTimer; + + if ( mpPendingMouseDraggedEvent ) + { + if ( mpMouseEventListener != nil && + [mpMouseEventListener respondsToSelector: @selector(mouseDragged:)]) + { + [mpMouseEventListener mouseDragged: [mpPendingMouseDraggedEvent copyWithZone: nullptr]]; + } + + s_nLastButton = MOUSE_LEFT; + [self sendMouseEventToFrame:mpPendingMouseDraggedEvent button:MOUSE_LEFT eventtype:SalEvent::MouseMove]; + + [self clearPendingMouseDraggedEvent]; + } + else + { + [self clearMouseDraggedTimer]; + } +} + @end @implementation SalFrameViewA11yWrapper