macOS: Invalidate window shadow after flushing backingstore to CALayer
The macOS bug preventing us from doing this has now been fixed: http://openradar.appspot.com/radar?id=4976602949615616 Change-Id: I3bfa75d6bf982a051a9b274f530529f4ec4351a0 Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io> Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
parent
99c3ef0789
commit
8c91070606
@ -132,113 +132,114 @@ void QCocoaBackingStore::flush(QWindow *window, const QRegion ®ion, const QPo
|
|||||||
// The contentsRect is in unit coordinate system
|
// The contentsRect is in unit coordinate system
|
||||||
CGAffineTransformMakeScale(1.0 / m_image.width(), 1.0 / m_image.height()));
|
CGAffineTransformMakeScale(1.0 / m_image.width(), 1.0 / m_image.height()));
|
||||||
}
|
}
|
||||||
return;
|
} else {
|
||||||
}
|
// Normally a NSView is drawn via drawRect, as part of the display cycle in the
|
||||||
|
// main runloop, via setNeedsDisplay and friends. AppKit will lock focus on each
|
||||||
|
// individual view, starting with the top level and then traversing any subviews,
|
||||||
|
// calling drawRect for each of them. This pull model results in expose events
|
||||||
|
// sent to Qt, which result in drawing to the backingstore and flushing it.
|
||||||
|
// Qt may also decide to paint and flush the backingstore via e.g. timers,
|
||||||
|
// or other events such as mouse events, in which case we're in a push model.
|
||||||
|
// If there is no focused view, it means we're in the latter case, and need
|
||||||
|
// to manually flush the NSWindow after drawing to its graphic context.
|
||||||
|
const bool drawingOutsideOfDisplayCycle = ![NSView focusView];
|
||||||
|
|
||||||
// Normally a NSView is drawn via drawRect, as part of the display cycle in the
|
// We also need to ensure the flushed view has focus, so that the graphics
|
||||||
// main runloop, via setNeedsDisplay and friends. AppKit will lock focus on each
|
// context is set up correctly (coordinate system, clipping, etc). Outside
|
||||||
// individual view, starting with the top level and then traversing any subviews,
|
// of the normal display cycle there is no focused view, as explained above,
|
||||||
// calling drawRect for each of them. This pull model results in expose events
|
// so we have to handle it manually. There's also a corner case inside the
|
||||||
// sent to Qt, which result in drawing to the backingstore and flushing it.
|
// normal display cycle due to way QWidgetBackingStore composits native child
|
||||||
// Qt may also decide to paint and flush the backingstore via e.g. timers,
|
// widgets, where we'll get a flush of a native child during the drawRect of
|
||||||
// or other events such as mouse events, in which case we're in a push model.
|
// its parent/ancestor, and the parent/ancestor being the one locked by AppKit.
|
||||||
// If there is no focused view, it means we're in the latter case, and need
|
// In this case we also need to lock and unlock focus manually.
|
||||||
// to manually flush the NSWindow after drawing to its graphic context.
|
const bool shouldHandleViewLockManually = [NSView focusView] != view;
|
||||||
const bool drawingOutsideOfDisplayCycle = ![NSView focusView];
|
if (shouldHandleViewLockManually && ![view lockFocusIfCanDraw]) {
|
||||||
|
qWarning() << "failed to lock focus of" << view;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// We also need to ensure the flushed view has focus, so that the graphics
|
const qreal devicePixelRatio = m_image.devicePixelRatio();
|
||||||
// context is set up correctly (coordinate system, clipping, etc). Outside
|
|
||||||
// of the normal display cycle there is no focused view, as explained above,
|
|
||||||
// so we have to handle it manually. There's also a corner case inside the
|
|
||||||
// normal display cycle due to way QWidgetBackingStore composits native child
|
|
||||||
// widgets, where we'll get a flush of a native child during the drawRect of
|
|
||||||
// its parent/ancestor, and the parent/ancestor being the one locked by AppKit.
|
|
||||||
// In this case we also need to lock and unlock focus manually.
|
|
||||||
const bool shouldHandleViewLockManually = [NSView focusView] != view;
|
|
||||||
if (shouldHandleViewLockManually && ![view lockFocusIfCanDraw]) {
|
|
||||||
qWarning() << "failed to lock focus of" << view;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const qreal devicePixelRatio = m_image.devicePixelRatio();
|
// If the flushed window is a content view, and not in unified toolbar mode,
|
||||||
|
// and is fully opaque, we can get away with copying the backingstore instead
|
||||||
// If the flushed window is a content view, and not in unified toolbar mode,
|
// of blending.
|
||||||
// and is fully opaque, we can get away with copying the backingstore instead
|
QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle());
|
||||||
// of blending.
|
const NSCompositingOperation compositingOperation = cocoaWindow->isContentView()
|
||||||
QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle());
|
&& cocoaWindow->isOpaque() && !windowHasUnifiedToolbar() ?
|
||||||
const NSCompositingOperation compositingOperation = cocoaWindow->isContentView()
|
NSCompositingOperationCopy : NSCompositingOperationSourceOver;
|
||||||
&& cocoaWindow->isOpaque() && !windowHasUnifiedToolbar() ?
|
|
||||||
NSCompositingOperationCopy : NSCompositingOperationSourceOver;
|
|
||||||
|
|
||||||
#ifdef QT_DEBUG
|
#ifdef QT_DEBUG
|
||||||
static bool debugBackingStoreFlush = [[NSUserDefaults standardUserDefaults]
|
static bool debugBackingStoreFlush = [[NSUserDefaults standardUserDefaults]
|
||||||
boolForKey:@"QtCocoaDebugBackingStoreFlush"];
|
boolForKey:@"QtCocoaDebugBackingStoreFlush"];
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
// The current contexts is typically a NSWindowGraphicsContext, but can be
|
// The current contexts is typically a NSWindowGraphicsContext, but can be
|
||||||
// NSBitmapGraphicsContext e.g. when debugging the view hierarchy in Xcode.
|
// NSBitmapGraphicsContext e.g. when debugging the view hierarchy in Xcode.
|
||||||
// If we need to distinguish things here in the future, we can use e.g.
|
// If we need to distinguish things here in the future, we can use e.g.
|
||||||
// [NSGraphicsContext drawingToScreen], or the attributes of the context.
|
// [NSGraphicsContext drawingToScreen], or the attributes of the context.
|
||||||
NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext];
|
NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext];
|
||||||
Q_ASSERT_X(graphicsContext, "QCocoaBackingStore",
|
Q_ASSERT_X(graphicsContext, "QCocoaBackingStore",
|
||||||
"Focusing the view should give us a current graphics context");
|
"Focusing the view should give us a current graphics context");
|
||||||
|
|
||||||
// Create temporary image to use for blitting, without copying image data
|
// Create temporary image to use for blitting, without copying image data
|
||||||
NSImage *backingStoreImage = [[[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize] autorelease];
|
NSImage *backingStoreImage = [[[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize] autorelease];
|
||||||
|
|
||||||
QRegion clippedRegion = region;
|
QRegion clippedRegion = region;
|
||||||
for (QWindow *w = window; w; w = w->parent()) {
|
for (QWindow *w = window; w; w = w->parent()) {
|
||||||
if (!w->mask().isEmpty()) {
|
if (!w->mask().isEmpty()) {
|
||||||
clippedRegion &= w == window ? w->mask()
|
clippedRegion &= w == window ? w->mask()
|
||||||
: w->mask().translated(window->mapFromGlobal(w->mapToGlobal(QPoint(0, 0))));
|
: w->mask().translated(window->mapFromGlobal(w->mapToGlobal(QPoint(0, 0))));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const QRect &viewLocalRect : clippedRegion) {
|
|
||||||
QPoint backingStoreOffset = viewLocalRect.topLeft() + offset;
|
|
||||||
QRect backingStoreRect(backingStoreOffset * devicePixelRatio, viewLocalRect.size() * devicePixelRatio);
|
|
||||||
if (graphicsContext.flipped) // Flip backingStoreRect to match graphics context
|
|
||||||
backingStoreRect.moveTop(m_image.height() - (backingStoreRect.y() + backingStoreRect.height()));
|
|
||||||
|
|
||||||
CGRect viewRect = viewLocalRect.toCGRect();
|
|
||||||
|
|
||||||
if (windowHasUnifiedToolbar())
|
|
||||||
NSDrawWindowBackground(viewRect);
|
|
||||||
|
|
||||||
[backingStoreImage drawInRect:viewRect fromRect:backingStoreRect.toCGRect()
|
|
||||||
operation:compositingOperation fraction:1.0 respectFlipped:YES hints:nil];
|
|
||||||
|
|
||||||
#ifdef QT_DEBUG
|
|
||||||
if (Q_UNLIKELY(debugBackingStoreFlush)) {
|
|
||||||
[[NSColor colorWithCalibratedRed:drand48() green:drand48() blue:drand48() alpha:0.3] set];
|
|
||||||
[NSBezierPath fillRect:viewRect];
|
|
||||||
|
|
||||||
if (drawingOutsideOfDisplayCycle) {
|
|
||||||
[[[NSColor magentaColor] colorWithAlphaComponent:0.5] set];
|
|
||||||
[NSBezierPath strokeLineFromPoint:viewLocalRect.topLeft().toCGPoint()
|
|
||||||
toPoint:viewLocalRect.bottomRight().toCGPoint()];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const QRect &viewLocalRect : clippedRegion) {
|
||||||
|
QPoint backingStoreOffset = viewLocalRect.topLeft() + offset;
|
||||||
|
QRect backingStoreRect(backingStoreOffset * devicePixelRatio, viewLocalRect.size() * devicePixelRatio);
|
||||||
|
if (graphicsContext.flipped) // Flip backingStoreRect to match graphics context
|
||||||
|
backingStoreRect.moveTop(m_image.height() - (backingStoreRect.y() + backingStoreRect.height()));
|
||||||
|
|
||||||
|
CGRect viewRect = viewLocalRect.toCGRect();
|
||||||
|
|
||||||
|
if (windowHasUnifiedToolbar())
|
||||||
|
NSDrawWindowBackground(viewRect);
|
||||||
|
|
||||||
|
[backingStoreImage drawInRect:viewRect fromRect:backingStoreRect.toCGRect()
|
||||||
|
operation:compositingOperation fraction:1.0 respectFlipped:YES hints:nil];
|
||||||
|
|
||||||
|
#ifdef QT_DEBUG
|
||||||
|
if (Q_UNLIKELY(debugBackingStoreFlush)) {
|
||||||
|
[[NSColor colorWithCalibratedRed:drand48() green:drand48() blue:drand48() alpha:0.3] set];
|
||||||
|
[NSBezierPath fillRect:viewRect];
|
||||||
|
|
||||||
|
if (drawingOutsideOfDisplayCycle) {
|
||||||
|
[[[NSColor magentaColor] colorWithAlphaComponent:0.5] set];
|
||||||
|
[NSBezierPath strokeLineFromPoint:viewLocalRect.topLeft().toCGPoint()
|
||||||
|
toPoint:viewLocalRect.bottomRight().toCGPoint()];
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if (shouldHandleViewLockManually)
|
||||||
|
[view unlockFocus];
|
||||||
|
|
||||||
|
if (drawingOutsideOfDisplayCycle) {
|
||||||
|
redrawRoundedBottomCorners([view convertRect:region.boundingRect().toCGRect() toView:nil]);
|
||||||
|
[view.window flushWindow];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Done flushing to either CALayer or NSWindow backingstore
|
||||||
|
|
||||||
QCocoaWindow *topLevelCocoaWindow = static_cast<QCocoaWindow *>(topLevelWindow->handle());
|
QCocoaWindow *topLevelCocoaWindow = static_cast<QCocoaWindow *>(topLevelWindow->handle());
|
||||||
if (Q_UNLIKELY(topLevelCocoaWindow->m_needsInvalidateShadow)) {
|
if (Q_UNLIKELY(topLevelCocoaWindow->m_needsInvalidateShadow)) {
|
||||||
[topLevelView.window invalidateShadow];
|
[topLevelView.window invalidateShadow];
|
||||||
topLevelCocoaWindow->m_needsInvalidateShadow = false;
|
topLevelCocoaWindow->m_needsInvalidateShadow = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
if (shouldHandleViewLockManually)
|
|
||||||
[view unlockFocus];
|
|
||||||
|
|
||||||
if (drawingOutsideOfDisplayCycle) {
|
|
||||||
redrawRoundedBottomCorners([view convertRect:region.boundingRect().toCGRect() toView:nil]);
|
|
||||||
[view.window flushWindow];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1032,12 +1032,6 @@ void QCocoaWindow::setMask(const QRegion ®ion)
|
|||||||
// time, and so would not result in an updated backingstore.
|
// time, and so would not result in an updated backingstore.
|
||||||
m_needsInvalidateShadow = true;
|
m_needsInvalidateShadow = true;
|
||||||
[m_view setNeedsDisplay:YES];
|
[m_view setNeedsDisplay:YES];
|
||||||
|
|
||||||
// FIXME: [NSWindow invalidateShadow] has no effect when in layer-backed mode,
|
|
||||||
// so if the mask is changed after the initial mask is applied, it will not
|
|
||||||
// result in any visual change to the shadow. This is an Apple bug, and there
|
|
||||||
// may be ways to work around it, such as calling setFrame on the window to
|
|
||||||
// trigger some internal invalidation, but that needs more research.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user