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