blob: 48833e8fbb2671ebd8220d34a14b26a8847b571b [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ui/base/cocoa/underlay_opengl_hosting_window.h"
#import <objc/runtime.h>
#include "base/logging.h"
#include "base/mac/mac_util.h"
#include "base/mac/scoped_nsautorelease_pool.h"
#include "base/memory/scoped_nsobject.h"
@interface NSWindow (UndocumentedAPI)
// Normally, punching a hole in a window by painting a subview with a
// transparent color causes the shadow for that area to also not be present.
// That feature is "content has shadow", which means that shadows are effective
// even in the content area of the window. If, however, "content has shadow" is
// turned off, then the transparent area of the content casts a shadow. The one
// tricky part is that even if "content has shadow" is turned off, "the content"
// is defined as being the scanline from the leftmost opaque part to the
// rightmost opaque part. Therefore, to force the entire window to have a
// shadow, make sure that for the entire content region, there is an opaque area
// on the right and left edge of the window.
- (void)_setContentHasShadow:(BOOL)shadow;
@end
@interface OpaqueView : NSView
@end
@implementation OpaqueView
- (void)drawRect:(NSRect)r {
[[NSColor blackColor] set];
NSRectFill(r);
}
@end
namespace {
NSComparisonResult OpaqueViewsOnTop(id view1, id view2, void* context) {
BOOL view_1_is_opaque_view = [view1 isKindOfClass:[OpaqueView class]];
BOOL view_2_is_opaque_view = [view2 isKindOfClass:[OpaqueView class]];
if (view_1_is_opaque_view && view_2_is_opaque_view)
return NSOrderedSame;
if (view_1_is_opaque_view)
return NSOrderedDescending;
if (view_2_is_opaque_view)
return NSOrderedAscending;
return NSOrderedSame;
}
void RootDidAddSubview(id self, SEL _cmd, NSView* subview) {
if (![[self window] isKindOfClass:[UnderlayOpenGLHostingWindow class]])
return;
// Make sure the opaques are on top.
[self sortSubviewsUsingFunction:OpaqueViewsOnTop context:NULL];
}
} // namespace
@implementation UnderlayOpenGLHostingWindow
+ (void)load {
base::mac::ScopedNSAutoreleasePool pool;
// On 10.8+ the background for textured windows are no longer drawn by
// NSGrayFrame, and NSThemeFrame is used instead <http://crbug.com/114745>.
Class borderViewClass = NSClassFromString(
base::mac::IsOSMountainLionOrLater() ? @"NSThemeFrame" : @"NSGrayFrame");
DCHECK(borderViewClass);
if (!borderViewClass) return;
// Install callback for added views.
Method m = class_getInstanceMethod([NSView class], @selector(didAddSubview:));
DCHECK(m);
if (m) {
BOOL didAdd = class_addMethod(borderViewClass,
@selector(didAddSubview:),
reinterpret_cast<IMP>(&RootDidAddSubview),
method_getTypeEncoding(m));
DCHECK(didAdd);
}
}
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle
backing:(NSBackingStoreType)bufferingType
defer:(BOOL)deferCreation {
// It is invalid to create windows with zero width or height. It screws things
// up royally:
// - It causes console spew: <http://crbug.com/78973>
// - It breaks Expose: <http://sourceforge.net/projects/heat-meteo/forums/forum/268087/topic/4582610>
//
// This is a banned practice
// <http://www.chromium.org/developers/coding-style/cocoa-dos-and-donts>. Do
// not do this. Use kWindowSizeDeterminedLater in
// ui/base/cocoa/window_size_constants.h instead.
//
// (This is checked here because UnderlayOpenGLHostingWindow is the base of
// most Chromium windows, not because this is related to its functionality.)
DCHECK(!NSIsEmptyRect(contentRect));
if ((self = [super initWithContentRect:contentRect
styleMask:windowStyle
backing:bufferingType
defer:deferCreation])) {
// OpenGL-accelerated content works by punching holes in windows. Therefore
// all windows hosting OpenGL content must not be opaque.
[self setOpaque:NO];
if (windowStyle & NSTitledWindowMask) {
// Only fiddle with shadows if the window is a proper window with a
// title bar and all.
[self _setContentHasShadow:NO];
NSView* rootView = [[self contentView] superview];
const NSRect rootBounds = [rootView bounds];
// On 10.7/8, the bottom corners of the window are rounded by magic at a
// deeper level than the NSThemeFrame, so it is OK to have the opaques
// go all the way to the bottom.
const CGFloat kTopEdgeInset = 16;
const CGFloat kAlphaValueJustOpaqueEnough = 0.002;
scoped_nsobject<NSView> leftOpaque([[OpaqueView alloc] initWithFrame:
NSMakeRect(NSMinX(rootBounds), NSMinY(rootBounds),
1, NSHeight(rootBounds) - kTopEdgeInset)]);
[leftOpaque setAutoresizingMask:NSViewMaxXMargin |
NSViewHeightSizable];
[leftOpaque setAlphaValue:kAlphaValueJustOpaqueEnough];
[rootView addSubview:leftOpaque];
scoped_nsobject<NSView> rightOpaque([[OpaqueView alloc] initWithFrame:
NSMakeRect(NSMaxX(rootBounds) - 1, NSMinY(rootBounds),
1, NSHeight(rootBounds) - kTopEdgeInset)]);
[rightOpaque setAutoresizingMask:NSViewMinXMargin |
NSViewHeightSizable];
[rightOpaque setAlphaValue:kAlphaValueJustOpaqueEnough];
[rootView addSubview:rightOpaque];
}
}
return self;
}
@end