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)