vcl/inc/osx/salframeview.h   |    7 +
 vcl/inc/osx/salinst.h        |    1 
 vcl/inc/svdata.hxx           |    1 
 vcl/osx/salframeview.mm      |  152 +++++++++++++++++++++++++++++++++----------
 vcl/osx/salinst.cxx          |   14 +++
 vcl/osx/saltimer.cxx         |    4 -
 vcl/skia/gdiimpl.cxx         |    5 +
 vcl/source/app/scheduler.cxx |    7 +
 8 files changed, 154 insertions(+), 37 deletions(-)

New commits:
commit 0a6e128ef6c5f3951873e28931f331591759ae5f
Author:     Patrick Luby <plub...@neooffice.org>
AuthorDate: Fri Jan 20 18:57:02 2023 -0500
Commit:     Patrick Luby <plub...@neooffice.org>
CommitDate: Sat Jan 21 23:48:12 2023 +0000

    tdf#152703 Force relayout during live resizing of window
    
    Merge the following commits from the master branch:
    
    3f7406bc4df3c7d6cc618312607bff1ec36a12f7
    2d1a0d86d2d0c00fcfee61c39f2221e786e4245b
    fed429e4f6f437997aa6a88e2d071f58aa00ee34
    24eabdbebcbc3b9189bbb9809205ead9f903a0cb
    d56d76a4204aad18f75463b0c9aa6130d558e0ef
    eefc323cd592c7958b13062e3f08e105d24055b1
    9cc91e17172709c65742c092d3f312bce48ac6d9
    
    Change-Id: Id0f001b3a1e09a6187bf5c08611e7eaa06385743
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/145918
    Tested-by: Jenkins
    Reviewed-by: Patrick Luby <plub...@neooffice.org>

diff --git a/vcl/inc/osx/salframeview.h b/vcl/inc/osx/salframeview.h
index 2fcff0d1c1e5..6242f3d4146a 100644
--- a/vcl/inc/osx/salframeview.h
+++ b/vcl/inc/osx/salframeview.h
@@ -28,8 +28,13 @@ enum class SalEvent;
 {
     AquaSalFrame*       mpFrame;
     id mDraggingDestinationHandler;
+    BOOL                mbInLiveResize;
+    BOOL                mbInWindowDidResize;
+    NSTimer*            mpLiveResizeTimer;
 }
 -(id)initWithSalFrame: (AquaSalFrame*)pFrame;
+-(void)clearLiveResizeTimer;
+-(void)dealloc;
 -(BOOL)canBecomeKeyWindow;
 -(void)displayIfNeeded;
 -(void)windowDidBecomeKey: (NSNotification*)pNotification;
@@ -62,6 +67,8 @@ enum class SalEvent;
 
 -(void)endExtTextInput;
 -(void)endExtTextInput:(EndExtTextInputFlags)nFlags;
+
+-(void)windowDidResizeWithTimer:(NSTimer *)pTimer;
 @end
 
 @interface SalFrameView : AquaA11yWrapper <NSTextInputClient>
diff --git a/vcl/inc/osx/salinst.h b/vcl/inc/osx/salinst.h
index 1e6fce7092fd..8811fa3c9c72 100644
--- a/vcl/inc/osx/salinst.h
+++ b/vcl/inc/osx/salinst.h
@@ -89,7 +89,6 @@ public:
     int                                     mnActivePrintJobs;
     osl::Mutex                              maUserEventListMutex;
     osl::Condition                          maWaitingYieldCond;
-    bool                                    mbIsLiveResize;
     bool                                    mbNoYieldLock;
     bool                                    mbTimerProcessed;
 
diff --git a/vcl/inc/svdata.hxx b/vcl/inc/svdata.hxx
index 3651eb3bce61..06d0aeb9b9af 100644
--- a/vcl/inc/svdata.hxx
+++ b/vcl/inc/svdata.hxx
@@ -269,6 +269,7 @@ struct ImplSVWinData
     StartAutoScrollFlags    mnAutoScrollFlags = StartAutoScrollFlags::NONE; // 
auto scroll flags
     bool                    mbNoDeactivate = false;         // true: do not 
execute Deactivate
     bool                    mbNoSaveFocus = false;          // true: menus 
must not save/restore focus
+    bool                    mbIsLiveResize = false;         // true: skip 
waiting for events and low priority timers
 };
 
 typedef std::vector< std::pair< OUString, FieldUnit > > FieldUnitStringList;
diff --git a/vcl/osx/salframeview.mm b/vcl/osx/salframeview.mm
index 09643b5c307d..8445a6c40904 100644
--- a/vcl/osx/salframeview.mm
+++ b/vcl/osx/salframeview.mm
@@ -169,6 +169,9 @@ static AquaSalFrame* getMouseContainerFrame()
 -(id)initWithSalFrame: (AquaSalFrame*)pFrame
 {
     mDraggingDestinationHandler = nil;
+    mbInLiveResize = NO;
+    mbInWindowDidResize = NO;
+    mpLiveResizeTimer = nil;
     mpFrame = pFrame;
     NSRect aRect = { { static_cast<CGFloat>(pFrame->maGeometry.x()), 
static_cast<CGFloat>(pFrame->maGeometry.y()) },
                      { static_cast<CGFloat>(pFrame->maGeometry.width()), 
static_cast<CGFloat>(pFrame->maGeometry.height()) } };
@@ -209,6 +212,22 @@ static AquaSalFrame* getMouseContainerFrame()
     return static_cast<SalFrameWindow *>(pNSWindow);
 }
 
+-(void)clearLiveResizeTimer
+{
+    if ( mpLiveResizeTimer )
+    {
+        [mpLiveResizeTimer invalidate];
+        [mpLiveResizeTimer release];
+        mpLiveResizeTimer = nil;
+    }
+}
+
+-(void)dealloc
+{
+    [self clearLiveResizeTimer];
+    [super dealloc];
+}
+
 -(AquaSalFrame*)getSalFrame
 {
     return mpFrame;
@@ -219,21 +238,6 @@ static AquaSalFrame* getMouseContainerFrame()
     if( GetSalData() && GetSalData()->mpInstance )
     {
         SolarMutexGuard aGuard;
-
-#if HAVE_FEATURE_SKIA
-        // Related: tdf#152703 Eliminate empty window with Skia/Metal while 
resizing
-        // The window will clear its background so when Skia/Metal is enabled,
-        // explicitly flush the Skia graphics to the window during live
-        // resizing or else nothing will be drawn until after live resizing
-        // has ended.
-        if ( [self inLiveResize] && SkiaHelper::isVCLSkiaEnabled() && mpFrame 
&& AquaSalFrame::isAlive( mpFrame ) )
-        {
-            AquaSalGraphics* pGraphics = mpFrame->mpGraphics;
-            if ( pGraphics )
-                pGraphics->Flush();
-        }
-#endif
-
         [super displayIfNeeded];
     }
 }
@@ -332,22 +336,100 @@ static AquaSalFrame* getMouseContainerFrame()
     (void)pNotification;
     SolarMutexGuard aGuard;
 
+    if ( mbInWindowDidResize )
+        return;
+
+    mbInWindowDidResize = YES;
+
     if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
     {
         mpFrame->UpdateFrameGeometry();
         mpFrame->CallCallback( SalEvent::Resize, nullptr );
 
-        // Related: tdf#152703 Stop flicker with Skia/Metal while resizing
-        // When Skia/Metal is enabled, rapidly resizing a window has a
-        // noticeable amount of flicker so don't send any paint events during
-        // live resizing.
-        // Also, it appears that most of the LibreOffice layouts do not change
-        // their layout much during live resizing so apply this change when
-        // Skia is not enabled to ensure consistent behavior whether Skia is
-        // enabled or not.
-        if ( ![self inLiveResize] )
+        bool bInLiveResize = [self inLiveResize];
+        ImplSVData* pSVData = ImplGetSVData();
+        assert( pSVData );
+        if ( pSVData )
+        {
+            const bool bWasLiveResize = pSVData->mpWinData->mbIsLiveResize;
+            if ( bWasLiveResize != bInLiveResize )
+            {
+                pSVData->mpWinData->mbIsLiveResize = bInLiveResize;
+                Scheduler::Wakeup();
+            }
+        }
+
+        if ( bInLiveResize || mbInLiveResize )
+        {
+            mbInLiveResize = bInLiveResize;
+
+#if HAVE_FEATURE_SKIA
+            // Related: tdf#152703 Eliminate empty window with Skia/Metal 
while resizing
+            // The window will clear its background so when Skia/Metal is
+            // enabled, explicitly flush the Skia graphics to the window
+            // during live resizing or else nothing will be drawn until after
+            // live resizing has ended.
+            // Also, flushing during [self windowDidResize:] eliminates flicker
+            // by forcing this window's SkSurface to recreate its underlying
+            // CAMetalLayer with the new size. Flushing in
+            // [self displayIfNeeded] does not eliminate flicker so apparently
+            // [self windowDidResize:] is called earlier.
+            if ( SkiaHelper::isVCLSkiaEnabled() )
+            {
+                AquaSalGraphics* pGraphics = mpFrame->mpGraphics;
+                if ( pGraphics )
+                    pGraphics->Flush();
+            }
+#endif
+
+            // tdf#152703 Force relayout during live resizing of window
+            // During a live resize, macOS floods the application with
+            // windowDidResize: notifications so sending a paint event does
+            // not trigger redrawing with the new size.
+            // Instead, force relayout by dispatching all pending internal
+            // events and firing any pending timers.
+            // Also, Application::Reschedule() can potentially display a
+            // modal dialog which will cause a hang so temporarily disable
+            // live resize by clamping the window's minimum and maximum sizes
+            // to the current frame size which in Application::Reschedule().
+            NSRect aFrame = [self frame];
+            NSSize aMinSize = [self minSize];
+            NSSize aMaxSize = [self maxSize];
+            [self setMinSize:aFrame.size];
+            [self setMaxSize:aFrame.size];
+            Application::Reschedule( true );
+            [self setMinSize:aMinSize];
+            [self setMaxSize:aMaxSize];
+
+            if ( mbInLiveResize )
+            {
+                // tdf#152703 Force repaint after live resizing ends
+                // Repost this notification so that this selector will be 
called
+                // at least once after live resizing ends
+                if ( !mpLiveResizeTimer )
+                {
+                    mpLiveResizeTimer = [NSTimer 
scheduledTimerWithTimeInterval:0.1f target:self 
selector:@selector(windowDidResizeWithTimer:) userInfo:pNotification 
repeats:YES];
+                    if ( mpLiveResizeTimer )
+                    {
+                        [mpLiveResizeTimer 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:mpLiveResizeTimer 
forMode:NSEventTrackingRunLoopMode];
+                    }
+                }
+            }
+        }
+        else
+        {
+            [self clearLiveResizeTimer];
             mpFrame->SendPaintEvent();
+        }
     }
+
+    mbInWindowDidResize = NO;
 }
 
 -(void)windowDidMiniaturize: (NSNotification*)pNotification
@@ -486,6 +568,12 @@ static AquaSalFrame* getMouseContainerFrame()
         [pView endExtTextInput:nFlags];
 }
 
+-(void)windowDidResizeWithTimer:(NSTimer *)pTimer
+{
+    if ( pTimer )
+        [self windowDidResize:[pTimer userInfo]];
+}
+
 @end
 
 @implementation SalFrameView
@@ -565,9 +653,9 @@ static AquaSalFrame* getMouseContainerFrame()
 
 -(void)drawRect: (NSRect)aRect
 {
-    AquaSalInstance *pInstance = GetSalData()->mpInstance;
-    assert(pInstance);
-    if (!pInstance)
+    ImplSVData* pSVData = ImplGetSVData();
+    assert( pSVData );
+    if ( !pSVData )
         return;
 
     SolarMutexGuard aGuard;
@@ -575,10 +663,10 @@ static AquaSalFrame* getMouseContainerFrame()
         return;
 
     const bool bIsLiveResize = [self inLiveResize];
-    const bool bWasLiveResize = pInstance->mbIsLiveResize;
+    const bool bWasLiveResize = pSVData->mpWinData->mbIsLiveResize;
     if (bWasLiveResize != bIsLiveResize)
     {
-        pInstance->mbIsLiveResize = bIsLiveResize;
+        pSVData->mpWinData->mbIsLiveResize = bIsLiveResize;
         Scheduler::Wakeup();
     }
 
@@ -1705,7 +1793,7 @@ static AquaSalFrame* getMouseContainerFrame()
         else
              mSelectedRange = NSMakeRange( selRange.location, 
selRange.location + selRange.length > mMarkedRange.length ? mMarkedRange.length 
- selRange.location : selRange.length );
 
-        // If we are going to post uncommitted text, cache the string paramater
+        // If we are going to post uncommitted text, cache the string parameter
         // as is needed in both [self endExtTextInput] and
         // [self attributedSubstringForProposedRange:actualRange:]
         mpLastMarkedText = [aString retain];
@@ -1851,7 +1939,7 @@ static AquaSalFrame* getMouseContainerFrame()
     // the returned position won't be anywhere near the text cursor. So,
     // dispatch an empty SalEvent::ExtTextInput event, fetch the position,
     // and then dispatch a SalEvent::EndExtTextInput event.
-    BOOL bNeedsExtTextInput = ( mbInKeyInput && !mpLastMarkedText && 
mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && [mpLastEvent 
isARepeat] );
+    bool bNeedsExtTextInput = ( mbInKeyInput && !mpLastMarkedText && 
mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && [mpLastEvent 
isARepeat] );
     if ( bNeedsExtTextInput )
     {
         SalExtTextInputEvent aInputEvent;
@@ -2018,7 +2106,7 @@ static AquaSalFrame* getMouseContainerFrame()
             mbInCommitMarkedText = YES;
             if (nFlags & EndExtTextInputFlags::Complete)
             {
-                // Retain the last marked text as it will be releasd in
+                // Retain the last marked text as it will be released in
                 // [self insertText:replacementText:]
                 NSAttributedString *pText = [mpLastMarkedText retain];
                 [self insertText:pText replacementRange:NSMakeRange(0, 
[mpLastMarkedText length])];
diff --git a/vcl/osx/salinst.cxx b/vcl/osx/salinst.cxx
index e8a4a94efc08..ee5a1b245750 100644
--- a/vcl/osx/salinst.cxx
+++ b/vcl/osx/salinst.cxx
@@ -368,7 +368,6 @@ VCLPLUG_OSX_PUBLIC SalInstance* create_SalInstance()
 AquaSalInstance::AquaSalInstance()
     : SalInstance(std::make_unique<SalYieldMutex>())
     , mnActivePrintJobs( 0 )
-    , mbIsLiveResize( false )
     , mbNoYieldLock( false )
     , mbTimerProcessed( false )
 {
@@ -556,6 +555,13 @@ static bool isWakeupEvent( NSEvent *pEvent )
 
 bool AquaSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
 {
+    // Related: tdf#152703 Eliminate potential blocking during live resize
+    // Some events and timers call Application::Reschedule() or
+    // Application::Yield() so don't block and wait for events when a
+    // window is in live resize
+    if ( ImplGetSVData()->mpWinData->mbIsLiveResize )
+        bWait = false;
+
     // ensure that the per thread autorelease pool is top level and
     // will therefore not be destroyed by cocoa implicitly
     SalData::ensureThreadAutoreleasePool();
@@ -565,7 +571,11 @@ bool AquaSalInstance::DoYield(bool bWait, bool 
bHandleAllCurrentEvents)
     ReleasePoolHolder aReleasePool;
 
     // first, process current user events
-    bool bHadEvent = DispatchUserEvents( bHandleAllCurrentEvents );
+    // Related: tdf#152703 Eliminate potential blocking during live resize
+    // Only native events and timers need to be dispatched to redraw
+    // the window so skip dispatching user events when a window is in
+    // live resize
+    bool bHadEvent = ( !ImplGetSVData()->mpWinData->mbIsLiveResize && 
DispatchUserEvents( bHandleAllCurrentEvents ) );
     if ( !bHandleAllCurrentEvents && bHadEvent )
         return true;
 
diff --git a/vcl/osx/saltimer.cxx b/vcl/osx/saltimer.cxx
index 4c33c321d729..8af7de217678 100644
--- a/vcl/osx/saltimer.cxx
+++ b/vcl/osx/saltimer.cxx
@@ -83,7 +83,7 @@ void AquaSalTimer::Start( sal_uInt64 nMS )
         return;
     }
 
-    m_bDirectTimeout = (0 == nMS) && !pSalData->mpInstance->mbIsLiveResize;
+    m_bDirectTimeout = (0 == nMS) && 
!ImplGetSVData()->mpWinData->mbIsLiveResize;
     if ( m_bDirectTimeout )
         Stop();
     else
@@ -142,7 +142,7 @@ void AquaSalTimer::callTimerCallback()
 
 void AquaSalTimer::handleTimerElapsed()
 {
-    if ( m_bDirectTimeout || GetSalData()->mpInstance->mbIsLiveResize )
+    if ( m_bDirectTimeout || ImplGetSVData()->mpWinData->mbIsLiveResize )
     {
         // Stop the timer, as it is just invalidated after the firing function
         Stop();
diff --git a/vcl/skia/gdiimpl.cxx b/vcl/skia/gdiimpl.cxx
index 896849baefa3..c218d5d565e1 100644
--- a/vcl/skia/gdiimpl.cxx
+++ b/vcl/skia/gdiimpl.cxx
@@ -372,6 +372,11 @@ void SkiaSalGraphicsImpl::performFlush()
 {
     SkiaZone zone;
     flushDrawing();
+    // Related: tdf#152703 Eliminate flickering during live resizing of a 
window
+    // When in live resize, the SkiaSalGraphicsImpl class does not detect that
+    // the window size has changed until after the flush has been called so
+    // call checkSurface() to recreate the SkSurface if needed before flushing.
+    checkSurface();
     if (mSurface)
     {
         if (mDirtyRect.intersect(SkIRect::MakeWH(GetWidth(), GetHeight())))
diff --git a/vcl/source/app/scheduler.cxx b/vcl/source/app/scheduler.cxx
index 251b972fe5ac..f83a4bb04d32 100644
--- a/vcl/source/app/scheduler.cxx
+++ b/vcl/source/app/scheduler.cxx
@@ -359,6 +359,13 @@ void Scheduler::CallbackTaskScheduling()
 
     for (; nTaskPriority < PRIO_COUNT; ++nTaskPriority)
     {
+        // Related: tdf#152703 Eliminate potential blocking during live resize
+        // Only higher priority tasks need to be fired to redraw the window
+        // so skip firing potentially long-running tasks, such as the Writer
+        // idle layout timer, when a window is in live resize
+        if ( ImplGetSVData()->mpWinData->mbIsLiveResize && nTaskPriority == 
static_cast<int>(TaskPriority::LOWEST) )
+            continue;
+
         pSchedulerData = rSchedCtx.mpFirstSchedulerData[nTaskPriority];
         pPrevSchedulerData = nullptr;
         while (pSchedulerData)

Reply via email to