Home | History | Annotate | Download | only in cocoa
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #import "ui/base/cocoa/underlay_opengl_hosting_window.h"
      6 
      7 #import <objc/runtime.h>
      8 
      9 #include "base/command_line.h"
     10 #include "base/logging.h"
     11 #include "base/mac/mac_util.h"
     12 #include "base/mac/scoped_nsautorelease_pool.h"
     13 #include "base/mac/scoped_nsobject.h"
     14 #include "ui/base/ui_base_switches.h"
     15 
     16 @interface NSWindow (UndocumentedAPI)
     17 // Normally, punching a hole in a window by painting a subview with a
     18 // transparent color causes the shadow for that area to also not be present.
     19 // That feature is "content has shadow", which means that shadows are effective
     20 // even in the content area of the window. If, however, "content has shadow" is
     21 // turned off, then the transparent area of the content casts a shadow. The one
     22 // tricky part is that even if "content has shadow" is turned off, "the content"
     23 // is defined as being the scanline from the leftmost opaque part to the
     24 // rightmost opaque part.  Therefore, to force the entire window to have a
     25 // shadow, make sure that for the entire content region, there is an opaque area
     26 // on the right and left edge of the window.
     27 - (void)_setContentHasShadow:(BOOL)shadow;
     28 @end
     29 
     30 @interface OpaqueView : NSView
     31 @end
     32 
     33 namespace {
     34 
     35 bool CoreAnimationIsEnabled() {
     36   static bool is_enabled = !CommandLine::ForCurrentProcess()->HasSwitch(
     37       switches::kDisableCoreAnimation);
     38   return is_enabled;
     39 }
     40 
     41 NSComparisonResult OpaqueViewsOnTop(id view1, id view2, void* context) {
     42   BOOL view_1_is_opaque_view = [view1 isKindOfClass:[OpaqueView class]];
     43   BOOL view_2_is_opaque_view = [view2 isKindOfClass:[OpaqueView class]];
     44   if (view_1_is_opaque_view && view_2_is_opaque_view)
     45     return NSOrderedSame;
     46   if (view_1_is_opaque_view)
     47     return NSOrderedDescending;
     48   if (view_2_is_opaque_view)
     49     return NSOrderedAscending;
     50   return NSOrderedSame;
     51 }
     52 
     53 }  // namespace
     54 
     55 @implementation OpaqueView
     56 
     57 - (void)drawRect:(NSRect)r {
     58   [[NSColor blackColor] set];
     59   NSRectFill(r);
     60 }
     61 
     62 - (void)resetCursorRects {
     63   // When a view is moved relative to its peers, its cursor rects are reset.
     64   // (This is an undocumented side-effect.) At that time, verify that any
     65   // OpaqueViews are z-ordered in the front, and enforce it if need be.
     66 
     67   NSView* rootView = [self superview];
     68   DCHECK_EQ((NSView*)nil, [rootView superview]);
     69   NSArray* subviews = [rootView subviews];
     70 
     71   // If a window has any opaques, it will have exactly two.
     72   DCHECK_EQ(2U, [[subviews indexesOfObjectsPassingTest:
     73       ^(id el, NSUInteger i, BOOL *stop) {
     74         return [el isKindOfClass:[OpaqueView class]];
     75       }] count]);
     76 
     77   NSUInteger count = [subviews count];
     78   if (count < 2)
     79     return;
     80 
     81   if (![[subviews objectAtIndex:count - 1] isKindOfClass:[OpaqueView class]] ||
     82       ![[subviews objectAtIndex:count - 2] isKindOfClass:[OpaqueView class]]) {
     83     // Do not sort the subviews array here and call -[NSView setSubviews:] as
     84     // that causes a crash on 10.6.
     85     [rootView sortSubviewsUsingFunction:OpaqueViewsOnTop context:NULL];
     86   }
     87 }
     88 
     89 @end
     90 
     91 @implementation UnderlayOpenGLHostingWindow
     92 
     93 - (id)initWithContentRect:(NSRect)contentRect
     94                 styleMask:(NSUInteger)windowStyle
     95                   backing:(NSBackingStoreType)bufferingType
     96                     defer:(BOOL)deferCreation {
     97   // It is invalid to create windows with zero width or height. It screws things
     98   // up royally:
     99   // - It causes console spew: <http://crbug.com/78973>
    100   // - It breaks Expose: <http://sourceforge.net/projects/heat-meteo/forums/forum/268087/topic/4582610>
    101   //
    102   // This is a banned practice
    103   // <http://www.chromium.org/developers/coding-style/cocoa-dos-and-donts>. Do
    104   // not do this. Use kWindowSizeDeterminedLater in
    105   // ui/base/cocoa/window_size_constants.h instead.
    106   //
    107   // (This is checked here because UnderlayOpenGLHostingWindow is the base of
    108   // most Chromium windows, not because this is related to its functionality.)
    109   DCHECK(!NSIsEmptyRect(contentRect));
    110   if ((self = [super initWithContentRect:contentRect
    111                                styleMask:windowStyle
    112                                  backing:bufferingType
    113                                    defer:deferCreation])) {
    114     if (CoreAnimationIsEnabled()) {
    115       // If CoreAnimation is used, then the hole punching technique won't be
    116       // used. Bail now and don't play any special games with the shadow.
    117       // TODO(avi): Rip all this shadow code out once CoreAnimation can't be
    118       // turned off. http://crbug.com/336554
    119       return self;
    120     }
    121 
    122     // OpenGL-accelerated content works by punching holes in windows. Therefore
    123     // all windows hosting OpenGL content must not be opaque.
    124     [self setOpaque:NO];
    125 
    126     if (windowStyle & NSTitledWindowMask) {
    127       // Only fiddle with shadows if the window is a proper window with a
    128       // title bar and all.
    129       [self _setContentHasShadow:NO];
    130 
    131       NSView* rootView = [[self contentView] superview];
    132       const NSRect rootBounds = [rootView bounds];
    133 
    134       // On 10.7/8, the bottom corners of the window are rounded by magic at a
    135       // deeper level than the NSThemeFrame, so it is OK to have the opaques
    136       // go all the way to the bottom.
    137       const CGFloat kTopEdgeInset = 16;
    138       const CGFloat kAlphaValueJustOpaqueEnough = 0.005;
    139 
    140       base::scoped_nsobject<NSView> leftOpaque([[OpaqueView alloc]
    141           initWithFrame:NSMakeRect(NSMinX(rootBounds),
    142                                    NSMinY(rootBounds),
    143                                    1,
    144                                    NSHeight(rootBounds) - kTopEdgeInset)]);
    145       [leftOpaque setAutoresizingMask:NSViewMaxXMargin |
    146                                       NSViewHeightSizable];
    147       [leftOpaque setAlphaValue:kAlphaValueJustOpaqueEnough];
    148       [rootView addSubview:leftOpaque];
    149 
    150       base::scoped_nsobject<NSView> rightOpaque([[OpaqueView alloc]
    151           initWithFrame:NSMakeRect(NSMaxX(rootBounds) - 1,
    152                                    NSMinY(rootBounds),
    153                                    1,
    154                                    NSHeight(rootBounds) - kTopEdgeInset)]);
    155       [rightOpaque setAutoresizingMask:NSViewMinXMargin |
    156                                        NSViewHeightSizable];
    157       [rightOpaque setAlphaValue:kAlphaValueJustOpaqueEnough];
    158       [rootView addSubview:rightOpaque];
    159     }
    160   }
    161 
    162   return self;
    163 }
    164 
    165 @end
    166