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

Reply via email to