Bug 676241 part 6/9/11: Correct titlebar drawing. r=bgirard, r=smichaud, r=mattwoodrow

This commit is contained in:
Markus Stange 2013-05-23 16:49:17 +02:00
parent 0a085125ca
commit e3ca87e242
4 changed files with 466 additions and 87 deletions

View File

@ -279,6 +279,10 @@ typedef NSInteger NSEventGestureAxis;
// Whether this uses off-main-thread compositing.
BOOL mUsingOMTCompositor;
// The mask image that's used when painting into the titlebar using basic
// CGContext painting (i.e. non-accelerated).
CGImageRef mTopLeftCornerMask;
}
// class initialization
@ -316,6 +320,8 @@ typedef NSInteger NSEventGestureAxis;
- (void)setGLContext:(NSOpenGLContext *)aGLContext;
- (void)preRender:(NSOpenGLContext *)aGLContext;
- (BOOL)isCoveringTitlebar;
// Simple gestures support
//
// XXX - The swipeWithEvent, beginGestureWithEvent, magnifyWithEvent,
@ -538,6 +544,8 @@ public:
return mTextInputHandler;
}
void NotifyDirtyRegion(const nsIntRegion& aDirtyRegion);
// unit conversion convenience functions
int32_t CocoaPointsToDevPixels(CGFloat aPts) {
return nsCocoaUtils::CocoaPointsToDevPixels(aPts, BackingScaleFactor());
@ -573,8 +581,20 @@ protected:
return widget.forget();
}
void MaybeDrawResizeIndicator(mozilla::layers::GLManager* aManager, nsIntRect aRect);
void MaybeDrawRoundedBottomCorners(mozilla::layers::GLManager* aManager, nsIntRect aRect);
// Overlay drawing functions for OpenGL drawing
void MaybeDrawResizeIndicator(mozilla::layers::GLManager* aManager, const nsIntRect& aRect);
void MaybeDrawRoundedCorners(mozilla::layers::GLManager* aManager, const nsIntRect& aRect);
void MaybeDrawTitlebar(mozilla::layers::GLManager* aManager, const nsIntRect& aRect);
// Redraw the contents of mTitlebarImageBuffer on the main thread, as
// determined by mDirtyTitlebarRegion.
void UpdateTitlebarImageBuffer();
// Upload the contents of mTitlebarImageBuffer to mTitlebarImage on the
// compositor thread, as determined by mUpdatedTitlebarRegion.
void UpdateTitlebarImage(mozilla::layers::GLManager* aManager, const nsIntRect& aRect);
nsIntRect RectContainingTitlebarControls();
nsIWidget* GetWidgetForListenerEvents();
@ -604,12 +624,25 @@ protected:
nsIntRect mResizeIndicatorRect;
bool mHasRoundedBottomCorners;
int mDevPixelCornerRadius;
bool mIsCoveringTitlebar;
nsIntRect mTitlebarRect;
// The area of mTitlebarImageBuffer that needs to be redrawn during the next
// transaction. Accessed from any thread, protected by mEffectsLock.
nsIntRegion mUpdatedTitlebarRegion;
nsRefPtr<gfxQuartzSurface> mTitlebarImageBuffer;
// Compositor thread only
bool mFailedResizerImage;
bool mFailedCornerMaskImage;
nsRefPtr<mozilla::gl::TextureImage> mResizerImage;
nsRefPtr<mozilla::gl::TextureImage> mCornerMaskImage;
nsRefPtr<mozilla::gl::TextureImage> mTitlebarImage;
// The area of mTitlebarImageBuffer that has changed and needs to be
// uploaded to to mTitlebarImage. Main thread only.
nsIntRegion mDirtyTitlebarRegion;
// Cached value of [mView backingScaleFactor], to avoid sending two obj-c
// messages (respondsToSelector, backingScaleFactor) every time we need to

View File

@ -48,6 +48,7 @@
#include "gfxContext.h"
#include "gfxQuartzSurface.h"
#include "gfxUtils.h"
#include "nsRegion.h"
#include "Layers.h"
#include "LayerManagerOGL.h"
@ -146,8 +147,12 @@ uint32_t nsChildView::sLastInputEventCount = 0;
- (void)drawUsingOpenGLCallback;
- (BOOL)hasRoundedBottomCorners;
- (CGFloat)bottomCornerRadius;
- (void)maybeClearBottomCorners;
- (CGFloat)cornerRadius;
- (void)clearCorners;
// Overlay drawing functions for traditional CGContext drawing
- (void)drawTitlebarHighlight;
- (void)maskTopCornersInContext:(CGContextRef)aContext;
// Called using performSelector:withObject:afterDelay:0 to release
// aWidgetArray (and its contents) the next time through the run loop.
@ -246,6 +251,7 @@ nsChildView::nsChildView() : nsBaseWidget()
, mEffectsLock("WidgetEffects")
, mShowsResizeIndicator(false)
, mHasRoundedBottomCorners(false)
, mIsCoveringTitlebar(false)
, mFailedResizerImage(false)
, mFailedCornerMaskImage(false)
, mBackingScaleFactor(0.0)
@ -1815,15 +1821,45 @@ nsChildView::GetThebesSurface()
return mTempThebesSurface;
}
void
nsChildView::NotifyDirtyRegion(const nsIntRegion& aDirtyRegion)
{
if ([(ChildView*)mView isCoveringTitlebar]) {
// We store the dirty region so that we know what to repaint in the titlebar.
mDirtyTitlebarRegion.Or(mDirtyTitlebarRegion, aDirtyRegion);
mDirtyTitlebarRegion.And(mDirtyTitlebarRegion, RectContainingTitlebarControls());
}
}
static const CGFloat kTitlebarHighlightHeight = 6;
nsIntRect
nsChildView::RectContainingTitlebarControls()
{
// Start with a 6px high strip at the top of the window for the highlight line.
NSRect rect = NSMakeRect(0, 0, [mView bounds].size.width,
kTitlebarHighlightHeight);
// Add the rects of the titlebar controls.
for (id view in [(BaseWindow*)[mView window] titlebarControls]) {
rect = NSUnionRect(rect, [mView convertRect:[view bounds] fromView:view]);
}
return CocoaPointsToDevPixels(rect);
}
void
nsChildView::PrepareWindowEffects()
{
MutexAutoLock lock(mEffectsLock);
mShowsResizeIndicator = ShowsResizeIndicator(&mResizeIndicatorRect);
mHasRoundedBottomCorners = [mView isKindOfClass:[ChildView class]] &&
[(ChildView*)mView hasRoundedBottomCorners];
CGFloat cornerRadius = [(ChildView*)mView bottomCornerRadius];
mHasRoundedBottomCorners = [(ChildView*)mView hasRoundedBottomCorners];
CGFloat cornerRadius = [(ChildView*)mView cornerRadius];
mDevPixelCornerRadius = cornerRadius * BackingScaleFactor();
mIsCoveringTitlebar = [(ChildView*)mView isCoveringTitlebar];
if (mIsCoveringTitlebar) {
mTitlebarRect = RectContainingTitlebarControls();
UpdateTitlebarImageBuffer();
}
}
void
@ -1831,6 +1867,7 @@ nsChildView::CleanupWindowEffects()
{
mResizerImage = nullptr;
mCornerMaskImage = nullptr;
mTitlebarImage = nullptr;
}
void
@ -1852,8 +1889,22 @@ nsChildView::DrawWindowOverlay(LayerManager* aManager, nsIntRect aRect)
return;
}
manager->gl()->PushScissorRect(aRect);
MaybeDrawTitlebar(manager, aRect);
MaybeDrawResizeIndicator(manager, aRect);
MaybeDrawRoundedBottomCorners(manager, aRect);
MaybeDrawRoundedCorners(manager, aRect);
manager->gl()->PopScissorRect();
}
static void
ClearRegion(gfxASurface* aSurface, nsIntRegion aRegion)
{
nsRefPtr<gfxContext> ctx = new gfxContext(aSurface);
gfxUtils::ClipToRegion(ctx, aRegion);
ctx->SetOperator(gfxContext::OPERATOR_CLEAR);
ctx->Paint();
}
static void
@ -1890,14 +1941,14 @@ DrawResizer(CGContextRef aCtx)
}
void
nsChildView::MaybeDrawResizeIndicator(GLManager* aManager, nsIntRect aRect)
nsChildView::MaybeDrawResizeIndicator(GLManager* aManager, const nsIntRect& aRect)
{
MutexAutoLock lock(mEffectsLock);
if (!mShowsResizeIndicator || mFailedResizerImage) {
return;
}
if (!mResizerImage) {
MutexAutoLock lock(mEffectsLock);
mResizerImage =
aManager->gl()->CreateTextureImage(nsIntSize(mResizeIndicatorRect.width,
mResizeIndicatorRect.height),
@ -1916,17 +1967,18 @@ nsChildView::MaybeDrawResizeIndicator(GLManager* aManager, nsIntRect aRect)
return;
}
// is the CairoSurface of a non QuartzSurface usable in the gfxQuartzSurface constructor ?
// answer seems to be NO (see comments on bug 675410
// this should not work for instance: new gfxQuartzSurface(asurf->CairoSurface(), false))
// We need a Quartz surface because DrawResizer wants a CGContext.
if (asurf->GetType() != gfxASurface::SurfaceTypeQuartz) {
NS_WARN_IF_FALSE(FALSE, "mResizerImage's surface is not Quartz");
mResizerImage->EndUpdate();
mResizerImage = nullptr;
mFailedResizerImage = true;
return;
}
nsRefPtr<gfxQuartzSurface> image = static_cast<gfxQuartzSurface*>(asurf);
ClearRegion(asurf, update);
nsRefPtr<gfxQuartzSurface> image = static_cast<gfxQuartzSurface*>(asurf);
DrawResizer(image->GetCGContext());
mResizerImage->EndUpdate();
@ -1934,8 +1986,8 @@ nsChildView::MaybeDrawResizeIndicator(GLManager* aManager, nsIntRect aRect)
NS_ABORT_IF_FALSE(mResizerImage, "Must have a texture allocated by now!");
float bottomX = aRect.x + aRect.width;
float bottomY = aRect.y + aRect.height;
float bottomX = aRect.XMost();
float bottomY = aRect.YMost();
TextureImage::ScopedBindTexture texBind(mResizerImage, LOCAL_GL_TEXTURE0);
@ -1954,6 +2006,195 @@ nsChildView::MaybeDrawResizeIndicator(GLManager* aManager, nsIntRect aRect)
aManager->BindAndDrawQuad(program);
}
// Draw the highlight line at the top of the titlebar.
// This function draws into the current NSGraphicsContext and assumes flippedness.
static void
DrawTitlebarHighlight(NSSize aWindowSize, CGFloat aRadius, CGFloat aDevicePixelWidth)
{
[NSGraphicsContext saveGraphicsState];
// Set up the clip path. We start with the outer rectangle and cut out a
// slightly smaller inner rectangle with rounded corners.
// The outer corners of the resulting path will be square, but they will be
// masked away in a later step.
NSBezierPath* path = [NSBezierPath bezierPath];
[path setWindingRule:NSEvenOddWindingRule];
NSRect pathRect = NSMakeRect(0, 0, aWindowSize.width, kTitlebarHighlightHeight + 2);
[path appendBezierPathWithRect:pathRect];
pathRect = NSInsetRect(pathRect, aDevicePixelWidth, aDevicePixelWidth);
CGFloat innerRadius = aRadius - aDevicePixelWidth;
[path appendBezierPathWithRoundedRect:pathRect xRadius:innerRadius yRadius:innerRadius];
[path addClip];
// Now we fill the path with a subtle highlight gradient.
NSColor* topColor = [NSColor colorWithDeviceWhite:1.0 alpha:0.4];
NSColor* bottomColor = [NSColor colorWithDeviceWhite:1.0 alpha:0.0];
NSGradient* gradient = [[NSGradient alloc] initWithStartingColor:topColor endingColor:bottomColor];
[gradient drawInRect:NSMakeRect(0, 0, aWindowSize.width, kTitlebarHighlightHeight) angle:90];
[gradient release];
[NSGraphicsContext restoreGraphicsState];
}
// When this method is entered, mEffectsLock is already being held.
void
nsChildView::UpdateTitlebarImageBuffer()
{
nsIntRegion dirtyTitlebarRegion = mDirtyTitlebarRegion;
mDirtyTitlebarRegion.SetEmpty();
if (!mTitlebarImageBuffer ||
mTitlebarImageBuffer->GetSize() != mTitlebarRect.Size()) {
dirtyTitlebarRegion = mTitlebarRect;
mTitlebarImageBuffer = new gfxQuartzSurface(mTitlebarRect.Size(),
gfxASurface::ImageFormatARGB32);
}
if (dirtyTitlebarRegion.IsEmpty())
return;
ClearRegion(mTitlebarImageBuffer, dirtyTitlebarRegion);
CGContextRef ctx = mTitlebarImageBuffer->GetCGContext();
double scale = BackingScaleFactor();
CGContextScaleCTM(ctx, scale, scale);
NSGraphicsContext* oldContext = [NSGraphicsContext currentContext];
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:YES]];
CGContextSaveGState(ctx);
BaseWindow* window = (BaseWindow*)[mView window];
NSView* frameView = [[window contentView] superview];
if (![frameView isFlipped]) {
CGContextTranslateCTM(ctx, 0, [frameView bounds].size.height);
CGContextScaleCTM(ctx, 1, -1);
}
// Draw the titlebar controls into the titlebar image.
for (id view in [window titlebarControls]) {
NSRect viewFrame = [view frame];
nsIntRect viewRect = CocoaPointsToDevPixels([mView convertRect:viewFrame fromView:frameView]);
nsIntRegion intersection;
intersection.And(dirtyTitlebarRegion, viewRect);
if (intersection.IsEmpty()) {
continue;
}
// All of the titlebar controls we're interested in are subclasses of
// NSButton.
if (![view isKindOfClass:[NSButton class]]) {
continue;
}
NSButton *button = (NSButton *) view;
id cellObject = [button cell];
if (![cellObject isKindOfClass:[NSCell class]]) {
continue;
}
NSCell *cell = (NSCell *) cellObject;
CGContextSaveGState(ctx);
CGContextTranslateCTM(ctx, viewFrame.origin.x, viewFrame.origin.y);
if ([view isFlipped]) {
CGContextTranslateCTM(ctx, 0, viewFrame.size.height);
CGContextScaleCTM(ctx, 1, -1);
}
NSRect intersectRect = DevPixelsToCocoaPoints(intersection.GetBounds());
[cell drawWithFrame:[view convertRect:intersectRect fromView:mView] inView:button];
CGContextRestoreGState(ctx);
}
CGContextRestoreGState(ctx);
DrawTitlebarHighlight([frameView bounds].size, [(ChildView*)mView cornerRadius],
DevPixelsToCocoaPoints(1));
[NSGraphicsContext setCurrentContext:oldContext];
mUpdatedTitlebarRegion.Or(mUpdatedTitlebarRegion, dirtyTitlebarRegion);
}
// When this method is entered, mEffectsLock is already being held.
void
nsChildView::UpdateTitlebarImage(GLManager* aManager, const nsIntRect& aRect)
{
nsIntRegion updatedTitlebarRegion;
updatedTitlebarRegion.And(mUpdatedTitlebarRegion, mTitlebarRect);
mUpdatedTitlebarRegion.SetEmpty();
if (!mTitlebarImage || mTitlebarImage->GetSize() != mTitlebarRect.Size()) {
updatedTitlebarRegion = mTitlebarRect;
mTitlebarImage =
aManager->gl()->CreateTextureImage(mTitlebarRect.Size(),
gfxASurface::CONTENT_COLOR_ALPHA,
LOCAL_GL_CLAMP_TO_EDGE,
TextureImage::UseNearestFilter);
// Creation of texture images can fail.
if (!mTitlebarImage)
return;
}
if (updatedTitlebarRegion.IsEmpty())
return;
gfxASurface *asurf = mTitlebarImage->BeginUpdate(updatedTitlebarRegion);
if (!asurf) {
mTitlebarImage = nullptr;
return;
}
nsRefPtr<gfxContext> ctx = new gfxContext(asurf);
ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
ctx->SetSource(mTitlebarImageBuffer);
ctx->Paint();
mTitlebarImage->EndUpdate();
}
// This method draws an overlay in the top of the window which contains the
// titlebar controls (e.g. close, min, zoom, fullscreen) and the titlebar
// highlight effect.
// This is necessary because the real titlebar controls are covered by our
// OpenGL context. Note that in terms of the NSView hierarchy, our ChildView
// is actually below the titlebar controls - that's why hovering and clicking
// them works as expected - but their visual representation is only drawn into
// the normal window buffer, and the window buffer surface lies below the
// GLContext surface. In order to make the titlebar controls visible, we have
// to redraw them inside the OpenGL context surface.
void
nsChildView::MaybeDrawTitlebar(GLManager* aManager, const nsIntRect& aRect)
{
MutexAutoLock lock(mEffectsLock);
if (!mIsCoveringTitlebar) {
return;
}
UpdateTitlebarImage(aManager, aRect);
if (!mTitlebarImage) {
return;
}
TextureImage::ScopedBindTexture texBind(mTitlebarImage, LOCAL_GL_TEXTURE0);
ShaderProgramOGL *program =
aManager->GetProgram(mTitlebarImage->GetShaderProgramType());
program->Activate();
program->SetLayerQuadRect(nsIntRect(nsIntPoint(0, 0),
mTitlebarImage->GetSize()));
program->SetLayerTransform(gfx3DMatrix());
program->SetLayerOpacity(1.0);
program->SetRenderOffset(nsIntPoint(0,0));
program->SetTextureUnit(0);
aManager->BindAndDrawQuad(program);
}
static void
DrawTopLeftCornerMask(CGContextRef aCtx, int aRadius)
{
@ -1962,14 +2203,14 @@ DrawTopLeftCornerMask(CGContextRef aCtx, int aRadius)
}
void
nsChildView::MaybeDrawRoundedBottomCorners(GLManager* aManager, nsIntRect aRect)
nsChildView::MaybeDrawRoundedCorners(GLManager* aManager, const nsIntRect& aRect)
{
if (!mHasRoundedBottomCorners ||
mFailedCornerMaskImage)
return;
MutexAutoLock lock(mEffectsLock);
if (mFailedCornerMaskImage) {
return;
}
if (!mCornerMaskImage) {
mCornerMaskImage =
aManager->gl()->CreateTextureImage(nsIntSize(mDevPixelCornerRadius,
@ -1988,20 +2229,20 @@ nsChildView::MaybeDrawRoundedBottomCorners(GLManager* aManager, nsIntRect aRect)
mCornerMaskImage = nullptr;
return;
}
// is the CairoSurface of a non QuartzSurface usable in the gfxQuartzSurface constructor ?
// answer seems to be NO (see comments on bug 675410
// this should not work for instance: new gfxQuartzSurface(asurf->CairoSurface(), false))
if (asurf->GetType() != gfxASurface::SurfaceTypeQuartz) {
NS_WARN_IF_FALSE(FALSE, "mCornerMaskImage's surface is not Quartz");
NS_WARNING("mCornerMaskImage's surface is not Quartz");
mCornerMaskImage->EndUpdate();
mCornerMaskImage = nullptr;
mFailedCornerMaskImage = true;
return;
}
ClearRegion(asurf, update);
nsRefPtr<gfxQuartzSurface> image = static_cast<gfxQuartzSurface*>(asurf);
DrawTopLeftCornerMask(image->GetCGContext(), mDevPixelCornerRadius);
mCornerMaskImage->EndUpdate();
}
@ -2011,25 +2252,36 @@ nsChildView::MaybeDrawRoundedBottomCorners(GLManager* aManager, nsIntRect aRect)
ShaderProgramOGL *program = aManager->GetProgram(mCornerMaskImage->GetShaderProgramType());
program->Activate();
program->SetLayerQuadRect(nsIntRect(0, 0, // aRect.x, aRect.y,
mDevPixelCornerRadius,
mDevPixelCornerRadius));
program->SetLayerQuadRect(nsIntRect(nsIntPoint(0, 0),
mCornerMaskImage->GetSize()));
program->SetLayerOpacity(1.0);
program->SetRenderOffset(nsIntPoint(0,0));
program->SetTextureUnit(0);
// Use operator destination in: multiply all 4 channels with source alpha.
aManager->gl()->fBlendFuncSeparate(LOCAL_GL_ZERO, LOCAL_GL_SRC_ALPHA,
LOCAL_GL_ZERO, LOCAL_GL_SRC_ALPHA);
// Draw; first bottom left, then bottom right.
program->SetLayerTransform(gfx3DMatrix::ScalingMatrix(1, -1, 1) *
gfx3DMatrix::Translation(0, aRect.height, 0));
aManager->BindAndDrawQuad(program);
program->SetLayerTransform(gfx3DMatrix::ScalingMatrix(-1, -1, 1) *
gfx3DMatrix::Translation(aRect.width, aRect.height, 0));
aManager->BindAndDrawQuad(program);
if (mIsCoveringTitlebar) {
// Mask the top corners.
program->SetLayerTransform(gfx3DMatrix::ScalingMatrix(1, 1, 1) *
gfx3DMatrix::Translation(0, 0, 0));
aManager->BindAndDrawQuad(program);
program->SetLayerTransform(gfx3DMatrix::ScalingMatrix(-1, 1, 1) *
gfx3DMatrix::Translation(aRect.width, 0, 0));
aManager->BindAndDrawQuad(program);
}
if (mHasRoundedBottomCorners) {
// Mask the bottom corners.
program->SetLayerTransform(gfx3DMatrix::ScalingMatrix(1, -1, 1) *
gfx3DMatrix::Translation(0, aRect.height, 0));
aManager->BindAndDrawQuad(program);
program->SetLayerTransform(gfx3DMatrix::ScalingMatrix(-1, -1, 1) *
gfx3DMatrix::Translation(aRect.width, aRect.height, 0));
aManager->BindAndDrawQuad(program);
}
// Reset blend mode.
aManager->gl()->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA,
LOCAL_GL_ONE, LOCAL_GL_ONE);
@ -2235,6 +2487,8 @@ NSEvent* gLastDragMouseDownEvent = nil;
mCancelSwipeAnimation = nil;
mCurrentSwipeDir = 0;
#endif
mTopLeftCornerMask = NULL;
}
// register for things we'll take from other applications
@ -2335,6 +2589,7 @@ NSEvent* gLastDragMouseDownEvent = nil;
[mPendingDirtyRects release];
[mLastMouseDownEvent release];
[mClickThroughMouseDownEvent release];
CGImageRelease(mTopLeftCornerMask);
ChildViewMouseTracker::OnDestroyView(self);
[[NSNotificationCenter defaultCenter] removeObserver:self];
@ -2688,6 +2943,13 @@ NSEvent* gLastDragMouseDownEvent = nil;
}
}
- (BOOL)isCoveringTitlebar
{
return [[self window] isKindOfClass:[BaseWindow class]] &&
[(BaseWindow*)[self window] mainChildView] == self &&
[(BaseWindow*)[self window] drawsContentsIntoWindowFrame];
}
// The display system has told us that a portion of our view is dirty. Tell
// gecko to paint it
- (void)drawRect:(NSRect)aRect
@ -2724,31 +2986,14 @@ NSEvent* gLastDragMouseDownEvent = nil;
fprintf (stderr, " xform in: [%f %f %f %f %f %f]\n", xform.a, xform.b, xform.c, xform.d, xform.tx, xform.ty);
#endif
if ([self isUsingOpenGL]) {
// We usually don't get here for Gecko-initiated repaints. Instead, those
// eventually call drawUsingOpenGL, and don't go through drawRect.
// Paints that come through here are triggered by something that Cocoa
// controls, for example by window resizing or window focus changes.
// Since our window is usually declared as opaque, the window's pixel
// buffer may now contain garbage which we need to prevent from reaching
// the screen. The only place where garbage can show is in the bottom
// corners - the rest of the window is covered by our OpenGL surface.
// So we need to clear the pixel buffer contents in the corners.
[self maybeClearBottomCorners];
// Do GL composition and return.
[self drawUsingOpenGL];
return;
}
PROFILER_LABEL("widget", "ChildView::drawRect");
nsIntRegion region;
nsIntRect boundingRect = mGeckoChild->CocoaPointsToDevPixels(aRect);
const NSRect *rects;
NSInteger count, i;
[[NSView focusView] getRectsBeingDrawn:&rects count:&count];
CGContextClipToRects(aContext, (CGRect*)rects, count);
if (count < MAX_RECTS_IN_REGION) {
for (i = 0; i < count; ++i) {
// Add the rect to the region.
@ -2760,26 +3005,53 @@ NSEvent* gLastDragMouseDownEvent = nil;
region = boundingRect;
}
// Create Cairo objects.
if ([self isUsingOpenGL]) {
// For Gecko-initiated repaints in OpenGL mode, drawUsingOpenGL is
// directly called from a delayed perform callback - without going through
// drawRect.
// Paints that come through here are triggered by something that Cocoa
// controls, for example by window resizing or window focus changes.
// Since this view is usually declared as opaque, the window's pixel
// buffer may now contain garbage which we need to prevent from reaching
// the screen. The only place where garbage can show is in the window
// corners - the rest of the window is covered by opaque content in our
// OpenGL surface.
// So we need to clear the pixel buffer contents in the corners.
[self clearCorners];
// When our view covers the titlebar, we need to repaint the titlebar
// texture buffer when, for example, the window buttons are hovered.
// So we notify our nsChildView about any areas needing repainting.
mGeckoChild->NotifyDirtyRegion(region);
// Do GL composition and return.
[self drawUsingOpenGL];
return;
}
PROFILER_LABEL("widget", "ChildView::drawRect");
// The CGContext that drawRect supplies us with comes with a transform that
// scales one user space unit to one Cocoa point, which can consist of
// multiple dev pixels. But Gecko expects its supplied context to be scaled
// to device pixels, so we need to reverse the scaling.
double scale = mGeckoChild->BackingScaleFactor();
CGContextSaveGState(aContext);
CGContextScaleCTM(aContext, 1.0 / scale, 1.0 / scale);
NSSize viewSize = [self bounds].size;
nsIntSize backingSize(viewSize.width * scale, viewSize.height * scale);
CGContextSaveGState(aContext);
// Create Cairo objects.
nsRefPtr<gfxQuartzSurface> targetSurface =
new gfxQuartzSurface(aContext, backingSize);
targetSurface->SetAllowUseAsSource(false);
nsRefPtr<gfxContext> targetContext = new gfxContext(targetSurface);
gfxContextMatrixAutoSaveRestore save(targetContext);
// Set up the clip region.
nsIntRegionRectIterator iter(region);
targetContext->NewPath();
@ -2806,6 +3078,15 @@ NSEvent* gLastDragMouseDownEvent = nil;
painted = mGeckoChild->PaintWindow(region);
}
targetContext = nullptr;
targetSurface = nullptr;
CGContextRestoreGState(aContext);
// Undo the scale transform so that from now on the context is in
// CocoaPoints again.
CGContextRestoreGState(aContext);
if (!painted && [self isOpaque]) {
// Gecko refused to draw, but we've claimed to be opaque, so we have to
// draw something--fill with white.
@ -2813,9 +3094,10 @@ NSEvent* gLastDragMouseDownEvent = nil;
CGContextFillRect(aContext, NSRectToCGRect(aRect));
}
// note that the cairo surface *MUST* be destroyed at this point,
// or bad things will happen (since we can't keep the cgContext around
// beyond this drawRect message handler)
if ([self isCoveringTitlebar]) {
[self drawTitlebarHighlight];
[self maskTopCornersInContext:aContext];
}
#ifdef DEBUG_UPDATE
fprintf (stderr, "---- update done ----\n");
@ -2900,39 +3182,92 @@ NSEvent* gLastDragMouseDownEvent = nil;
[[self window] bottomCornerRounded];
}
- (CGFloat)bottomCornerRadius
- (CGFloat)cornerRadius
{
if (![self hasRoundedBottomCorners])
return 0.0f;
NSView* borderView = [[[self window] contentView] superview];
if (!borderView || ![borderView respondsToSelector:@selector(roundedCornerRadius)])
NSView* frameView = [[[self window] contentView] superview];
if (!frameView || ![frameView respondsToSelector:@selector(roundedCornerRadius)])
return 4.0f;
return [borderView roundedCornerRadius];
return [frameView roundedCornerRadius];
}
// Accelerated windows have two NSSurfaces:
// (1) The window's pixel buffer in the back and
// (2) the OpenGL view in the front.
// These two surfaces are composited by the window manager. Drawing with the
// usual CGContext functions ends up in (1).
// When our window has rounded bottom corners, the OpenGL view has transparent
// pixels in the corners. In these places the contents of the window's pixel
// buffer can show through. So we need to make sure that the pixel buffer is
// These two surfaces are composited by the window manager. Drawing into the
// CGContext which is provided by drawRect ends up in (1).
// When our window has rounded corners, the OpenGL view has transparent pixels
// in the corners. In these places the contents of the window's pixel buffer
// can show through. So we need to make sure that the pixel buffer is
// transparent in the corners so that no garbage reaches the screen.
// The contents of the pixel buffer in the rest of the window don't matter
// because they're covered by the OpenGL view.
// Making the bottom corners transparent works even though our window is
// because they're covered by opaque pixels of the OpenGL context.
// Making the corners transparent works even though our window is
// declared "opaque" (in the NSWindow's isOpaque method).
- (void)maybeClearBottomCorners
- (void)clearCorners
{
if (![self hasRoundedBottomCorners])
return;
int radius = [self bottomCornerRadius];
int w = [self bounds].size.width, h = [self bounds].size.height;
CGFloat radius = [self cornerRadius];
CGFloat w = [self bounds].size.width, h = [self bounds].size.height;
[[NSColor clearColor] set];
NSRectFill(NSMakeRect(0, h - radius, radius, radius));
NSRectFill(NSMakeRect(w - radius, h - radius, radius, radius));
if ([self isCoveringTitlebar]) {
NSRectFill(NSMakeRect(0, 0, radius, radius));
NSRectFill(NSMakeRect(w - radius, 0, radius, radius));
}
if ([self hasRoundedBottomCorners]) {
NSRectFill(NSMakeRect(0, h - radius, radius, radius));
NSRectFill(NSMakeRect(w - radius, h - radius, radius, radius));
}
}
// This is the analog of nsChildView::MaybeDrawRoundedCorners for CGContexts.
// We only need to mask the top corners here because Cocoa does the masking
// for the window's bottom corners automatically (starting with 10.7).
- (void)maskTopCornersInContext:(CGContextRef)aContext
{
CGFloat radius = [self cornerRadius];
int32_t devPixelCornerRadius = mGeckoChild->CocoaPointsToDevPixels(radius);
// First make sure that mTopLeftCornerMask is set up.
if (!mTopLeftCornerMask ||
int32_t(CGImageGetWidth(mTopLeftCornerMask)) != devPixelCornerRadius) {
CGImageRelease(mTopLeftCornerMask);
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
CGContextRef imgCtx = CGBitmapContextCreate(NULL,
devPixelCornerRadius,
devPixelCornerRadius,
8, devPixelCornerRadius * 4,
rgb, kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(rgb);
DrawTopLeftCornerMask(imgCtx, devPixelCornerRadius);
mTopLeftCornerMask = CGBitmapContextCreateImage(imgCtx);
CGContextRelease(imgCtx);
}
// kCGBlendModeDestinationIn is the secret sauce which allows us to erase
// already painted pixels. It's defined as R = D * Sa: multiply all channels
// of the destination pixel with the alpha of the source pixel. In our case,
// the source is mTopLeftCornerMask.
CGContextSaveGState(aContext);
CGContextSetBlendMode(aContext, kCGBlendModeDestinationIn);
CGRect destRect = CGRectMake(0, 0, radius, radius);
// Erase the top left corner...
CGContextDrawImage(aContext, destRect, mTopLeftCornerMask);
// ... and the top right corner.
CGContextTranslateCTM(aContext, [self bounds].size.width, 0);
CGContextScaleCTM(aContext, -1, 1);
CGContextDrawImage(aContext, destRect, mTopLeftCornerMask);
CGContextRestoreGState(aContext);
}
- (void)drawTitlebarHighlight
{
DrawTitlebarHighlight([self bounds].size, [self cornerRadius],
mGeckoChild->DevPixelsToCocoaPoints(1));
}
- (void)releaseWidgets:(NSArray*)aWidgetArray

View File

@ -97,6 +97,8 @@ typedef struct _nsCocoaWindowList {
- (ChildView*)mainChildView;
- (NSArray*)titlebarControls;
@end
@interface NSWindow (Undocumented)

View File

@ -2716,6 +2716,15 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
[frameView addSubview:aView positioned:NSWindowBelow relativeTo:nil];
}
- (NSArray*)titlebarControls
{
// Return all subviews of the frameView which are not the content view.
NSView* frameView = [[self contentView] superview];
NSMutableArray* array = [[[frameView subviews] mutableCopy] autorelease];
[array removeObject:[self contentView]];
return array;
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
// Claim the window doesn't respond to this so that the system