Home | History | Annotate | Download | only in constrained_window
      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 "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h"
      6 
      7 #include "base/mac/scoped_nsobject.h"
      8 #import "chrome/browser/ui/cocoa/key_equivalent_constants.h"
      9 #include "skia/ext/skia_utils_mac.h"
     10 #import "third_party/molokocacao/NSBezierPath+MCAdditions.h"
     11 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
     12 
     13 @interface ConstrainedWindowButton ()
     14 - (BOOL)isMouseReallyInside;
     15 @end
     16 
     17 namespace {
     18 
     19 enum ButtonState {
     20   BUTTON_NORMAL,
     21   BUTTON_HOVER,
     22   BUTTON_PRESSED,
     23   BUTTON_DISABLED,
     24 };
     25 
     26 const CGFloat kButtonHeight = 28;
     27 const CGFloat kButtonPaddingX = 14;
     28 
     29 ButtonState cellButtonState(id<ConstrainedWindowButtonDrawableCell> cell) {
     30   if (!cell)
     31     return BUTTON_NORMAL;
     32   if (![cell isEnabled])
     33     return BUTTON_DISABLED;
     34   if ([cell isHighlighted])
     35     return BUTTON_PRESSED;
     36   if ([cell isMouseInside])
     37     return BUTTON_HOVER;
     38   return BUTTON_NORMAL;
     39 }
     40 
     41 // The functions below use hex color values to make it easier to compare
     42 // the code with the spec. Table of hex values are also more readable
     43 // then tables of NSColor constructors.
     44 
     45 NSGradient* GetButtonGradient(ButtonState button_state) {
     46   const SkColor start[] = {0xFFF0F0F0, 0xFFF4F4F4, 0xFFEBEBEB, 0xFFEDEDED};
     47   const SkColor end[] = {0xFFE0E0E0, 0xFFE4E4E4, 0xFFDBDBDB, 0xFFDEDEDE};
     48 
     49   NSColor* start_color = gfx::SkColorToCalibratedNSColor(start[button_state]);
     50   NSColor* end_color = gfx::SkColorToCalibratedNSColor(end[button_state]);
     51   return [[[NSGradient alloc] initWithColorsAndLocations:
     52       start_color, 0.0,
     53       start_color, 0.38,
     54       end_color, 1.0,
     55       nil] autorelease];
     56 }
     57 
     58 NSShadow* GetButtonHighlight(ButtonState button_state) {
     59   const SkColor color[] = {0xBFFFFFFF, 0xF2FFFFFF, 0x24000000, 0x00000000};
     60 
     61   NSShadow* shadow = [[[NSShadow alloc] init] autorelease];
     62   [shadow setShadowColor:gfx::SkColorToCalibratedNSColor(color[button_state])];
     63   [shadow setShadowOffset:NSMakeSize(0, -1)];
     64   [shadow setShadowBlurRadius:2];
     65   return shadow;
     66 }
     67 
     68 NSShadow* GetButtonShadow(ButtonState button_state) {
     69   const SkColor color[] = {0x14000000, 0x1F000000, 0x00000000, 0x00000000};
     70 
     71   NSShadow* shadow = [[[NSShadow alloc] init] autorelease];
     72   [shadow setShadowColor:gfx::SkColorToCalibratedNSColor(color[button_state])];
     73   [shadow setShadowOffset:NSMakeSize(0, -1)];
     74   [shadow setShadowBlurRadius:0];
     75   return shadow;
     76 }
     77 
     78 NSColor* GetButtonBorderColor(ButtonState button_state) {
     79   const SkColor color[] = {0x40000000, 0x4D000000, 0x4D000000, 0x1F000000};
     80 
     81   return gfx::SkColorToCalibratedNSColor(color[button_state]);
     82 }
     83 
     84 NSAttributedString* GetButtonAttributedString(
     85     NSString* title,
     86     NSString* key_equivalent,
     87     id<ConstrainedWindowButtonDrawableCell> cell) {
     88   const SkColor text_color[] = {0xFF333333, 0XFF000000, 0xFF000000, 0xFFAAAAAA};
     89   // The shadow color should be 0xFFF0F0F0 but that doesn't show up so use
     90   // pure white instead.
     91   const SkColor shadow_color[] =
     92       {0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF};
     93 
     94   base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
     95   [shadow setShadowColor:
     96       gfx::SkColorToCalibratedNSColor(shadow_color[cellButtonState(cell)])];
     97   [shadow setShadowOffset:NSMakeSize(0, -1)];
     98   [shadow setShadowBlurRadius:0];
     99 
    100   base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
    101       [[NSMutableParagraphStyle alloc] init]);
    102   [paragraphStyle setAlignment:NSCenterTextAlignment];
    103 
    104   NSFont* font = nil;
    105   if ([key_equivalent isEqualToString:kKeyEquivalentReturn])
    106     font = [NSFont boldSystemFontOfSize:12];
    107   else
    108     font = [NSFont systemFontOfSize:12];
    109 
    110   NSDictionary* attributes = [NSDictionary dictionaryWithObjectsAndKeys:
    111       font, NSFontAttributeName,
    112       gfx::SkColorToCalibratedNSColor(text_color[cellButtonState(cell)]),
    113       NSForegroundColorAttributeName,
    114       shadow.get(), NSShadowAttributeName,
    115       paragraphStyle.get(), NSParagraphStyleAttributeName,
    116       nil];
    117   return [[[NSAttributedString alloc] initWithString:title
    118                                           attributes:attributes] autorelease];
    119 }
    120 
    121 void DrawBackgroundAndShadow(const NSRect& frame,
    122                              id<ConstrainedWindowButtonDrawableCell> cell,
    123                              NSView* view) {
    124   NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:frame
    125                                                        xRadius:2
    126                                                        yRadius:2];
    127   [ConstrainedWindowButton DrawBackgroundAndShadowForPath:path
    128                                                  withCell:cell
    129                                                    inView:view];
    130 }
    131 
    132 void DrawInnerHighlight(const NSRect& frame,
    133                         id<ConstrainedWindowButtonDrawableCell> cell,
    134                         NSView* view) {
    135   NSBezierPath* path =
    136       [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 1, 1)
    137                                       xRadius:2
    138                                       yRadius:2];
    139   [ConstrainedWindowButton DrawInnerHighlightForPath:path
    140                                             withCell:cell
    141                                               inView:view];
    142 }
    143 
    144 void DrawBorder(const NSRect& frame,
    145                 id<ConstrainedWindowButtonDrawableCell> cell,
    146                 NSView* view) {
    147   NSBezierPath* path =
    148       [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 0.5, 0.5)
    149                                       xRadius:2
    150                                       yRadius:2];
    151   [ConstrainedWindowButton DrawBorderForPath:path
    152                                     withCell:cell
    153                                       inView:view];
    154 }
    155 
    156 }  // namespace
    157 
    158 @implementation ConstrainedWindowButton
    159 
    160 + (Class)cellClass {
    161   return [ConstrainedWindowButtonCell class];
    162 }
    163 
    164 - (void)updateTrackingAreas {
    165   BOOL mouseInView = [self isMouseReallyInside];
    166 
    167   if (trackingArea_.get())
    168     [self removeTrackingArea:trackingArea_.get()];
    169 
    170   NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
    171                                   NSTrackingActiveInActiveApp |
    172                                   NSTrackingInVisibleRect;
    173   if (mouseInView)
    174     options |= NSTrackingAssumeInside;
    175 
    176   trackingArea_.reset([[CrTrackingArea alloc]
    177                         initWithRect:NSZeroRect
    178                              options:options
    179                                owner:self
    180                             userInfo:nil]);
    181   [self addTrackingArea:trackingArea_.get()];
    182   if ([[self cell] isMouseInside] != mouseInView) {
    183     [[self cell] setIsMouseInside:mouseInView];
    184     [self setNeedsDisplay:YES];
    185   }
    186 }
    187 
    188 - (void)mouseEntered:(NSEvent*)theEvent {
    189   [[self cell] setIsMouseInside:YES];
    190   [self setNeedsDisplay:YES];
    191 }
    192 
    193 - (void)mouseExited:(NSEvent*)theEvent {
    194   [[self cell] setIsMouseInside:YES];
    195   [[self cell] setIsMouseInside:NO];
    196   [self setNeedsDisplay:YES];
    197 }
    198 
    199 - (BOOL)isMouseReallyInside {
    200   BOOL mouseInView = NO;
    201   NSWindow* window = [self window];
    202   if (window) {
    203     NSPoint mousePoint = [window mouseLocationOutsideOfEventStream];
    204     mousePoint = [self convertPoint:mousePoint fromView:nil];
    205     mouseInView = [self mouse:mousePoint inRect:[self bounds]];
    206   }
    207   return mouseInView;
    208 }
    209 
    210 - (void)sizeToFit {
    211   [super sizeToFit];
    212   NSSize size = [self frame].size;
    213   if (size.width < constrained_window_button::kButtonMinWidth) {
    214     size.width = constrained_window_button::kButtonMinWidth;
    215     [self setFrameSize:size];
    216   }
    217 }
    218 
    219 + (void)DrawBackgroundAndShadowForPath:(NSBezierPath*)path
    220                           withCell:(id<ConstrainedWindowButtonDrawableCell>)cell
    221                             inView:(NSView*)view {
    222   ButtonState buttonState = cellButtonState(cell);
    223   {
    224     gfx::ScopedNSGraphicsContextSaveGState scopedGState;
    225     [GetButtonShadow(buttonState) set];
    226     [[[view window] backgroundColor] set];
    227     [path fill];
    228   }
    229   [GetButtonGradient(buttonState) drawInBezierPath:path angle:90.0];
    230 }
    231 
    232 + (void)DrawInnerHighlightForPath:(NSBezierPath*)path
    233                          withCell:(id<ConstrainedWindowButtonDrawableCell>)cell
    234                            inView:(NSView*)view {
    235   [path fillWithInnerShadow:GetButtonHighlight(cellButtonState(cell))];
    236 }
    237 
    238 + (void)DrawBorderForPath:(NSBezierPath*)path
    239                  withCell:(id<ConstrainedWindowButtonDrawableCell>)cell
    240                    inView:(NSView*)view {
    241   if ([[[view window] firstResponder] isEqual:view])
    242     [[NSColor colorWithCalibratedRed:0.30 green:0.57 blue:1.0 alpha:1.0] set];
    243   else
    244     [GetButtonBorderColor(cellButtonState(cell)) set];
    245   [path stroke];
    246 }
    247 
    248 @end
    249 
    250 @implementation ConstrainedWindowButtonCell
    251 
    252 @synthesize isMouseInside = isMouseInside_;
    253 
    254 - (void)drawBezelWithFrame:(NSRect)frame inView:(NSView*)controlView {
    255   // Inset to leave room for shadow.
    256   --frame.size.height;
    257 
    258   // Background and shadow
    259   DrawBackgroundAndShadow(frame, self, controlView);
    260 
    261   // Inner highlight
    262   DrawInnerHighlight(frame, self, controlView);
    263 
    264   // Border
    265   DrawBorder(frame, self, controlView);
    266 }
    267 
    268 - (void)drawInteriorWithFrame:(NSRect)frame inView:(NSView*)controlView {
    269   // Inset to leave room for shadow.
    270   --frame.size.height;
    271   NSAttributedString* title = GetButtonAttributedString(
    272      [self title], [self keyEquivalent], self);
    273   [self drawTitle:title withFrame:frame inView:controlView];
    274 }
    275 
    276 - (NSSize)cellSize {
    277   NSAttributedString* title = GetButtonAttributedString(
    278       [self title], [self keyEquivalent], self);
    279   NSSize size = [title size];
    280   size.height = std::max(size.height, kButtonHeight);
    281   size.width += kButtonPaddingX * 2;
    282   return size;
    283 }
    284 
    285 - (NSAttributedString*)getAttributedTitle {
    286   return GetButtonAttributedString(
    287       [self title], [self keyEquivalent], self);
    288 }
    289 
    290 @end
    291