| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 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/profile_menu_button.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/logging.h" | |
| 10 #include "chrome/browser/ui/profile_menu_model.h" | |
| 11 #import "third_party/GTM/AppKit/GTMFadeTruncatingTextFieldCell.h" | |
| 12 | |
| 13 namespace { | |
| 14 | |
| 15 const CGFloat kTabWidth = 24; | |
| 16 const CGFloat kTabHeight = 13; | |
| 17 const CGFloat kTabArrowWidth = 7; | |
| 18 const CGFloat kTabArrowHeight = 4; | |
| 19 const CGFloat kTabRoundRectRadius = 5; | |
| 20 const CGFloat kTabDisplayNameMarginY = 3; | |
| 21 | |
| 22 NSColor* GetWhiteWithAlpha(CGFloat alpha) { | |
| 23 return [NSColor colorWithCalibratedWhite:1.0 alpha:alpha]; | |
| 24 } | |
| 25 | |
| 26 NSColor* GetBlackWithAlpha(CGFloat alpha) { | |
| 27 return [NSColor colorWithCalibratedWhite:0.0 alpha:alpha]; | |
| 28 } | |
| 29 | |
| 30 } // namespace | |
| 31 | |
| 32 @interface ProfileMenuButton (Private) | |
| 33 - (void)commonInit; | |
| 34 - (NSPoint)popUpMenuPosition; | |
| 35 - (NSImage*)tabImage; | |
| 36 - (NSRect)textFieldRect; | |
| 37 - (NSBezierPath*)tabPathWithRect:(NSRect)rect | |
| 38 radius:(CGFloat)radius; | |
| 39 - (NSBezierPath*)downArrowPathWithRect:(NSRect)rect; | |
| 40 - (NSImage*)tabImageWithSize:(NSSize)tabSize | |
| 41 fillColor:(NSColor*)fillColor | |
| 42 isPressed:(BOOL)isPressed; | |
| 43 @end | |
| 44 | |
| 45 @implementation ProfileMenuButton | |
| 46 | |
| 47 @synthesize shouldShowProfileDisplayName = shouldShowProfileDisplayName_; | |
| 48 | |
| 49 - (void)commonInit { | |
| 50 textFieldCell_.reset( | |
| 51 [[GTMFadeTruncatingTextFieldCell alloc] initTextCell:@""]); | |
| 52 [textFieldCell_ setBackgroundStyle:NSBackgroundStyleRaised]; | |
| 53 [textFieldCell_ setAlignment:NSRightTextAlignment]; | |
| 54 [textFieldCell_ setFont:[NSFont systemFontOfSize: | |
| 55 [NSFont smallSystemFontSize]]]; | |
| 56 | |
| 57 [self setOpenMenuOnClick:YES]; | |
| 58 | |
| 59 profile_menu_model_.reset(new ProfileMenuModel); | |
| 60 menu_.reset([[MenuController alloc] initWithModel:profile_menu_model_.get() | |
| 61 useWithPopUpButtonCell:NO]); | |
| 62 } | |
| 63 | |
| 64 - (id)initWithFrame:(NSRect)frame { | |
| 65 if ((self = [super initWithFrame:frame])) | |
| 66 [self commonInit]; | |
| 67 return self; | |
| 68 } | |
| 69 | |
| 70 - (id)initWithCoder:(NSCoder*)decoder { | |
| 71 if ((self = [super initWithCoder:decoder])) | |
| 72 [self commonInit]; | |
| 73 return self; | |
| 74 } | |
| 75 | |
| 76 - (void)dealloc { | |
| 77 [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
| 78 [super dealloc]; | |
| 79 } | |
| 80 | |
| 81 - (NSString*)profileDisplayName { | |
| 82 return [textFieldCell_ stringValue]; | |
| 83 } | |
| 84 | |
| 85 - (void)setProfileDisplayName:(NSString*)name { | |
| 86 if (![[textFieldCell_ stringValue] isEqual:name]) { | |
| 87 [textFieldCell_ setStringValue:name]; | |
| 88 [self setNeedsDisplay:YES]; | |
| 89 } | |
| 90 } | |
| 91 | |
| 92 - (void)setShouldShowProfileDisplayName:(BOOL)flag { | |
| 93 shouldShowProfileDisplayName_ = flag; | |
| 94 [self setNeedsDisplay:YES]; | |
| 95 } | |
| 96 | |
| 97 - (BOOL)isFlipped { | |
| 98 return NO; | |
| 99 } | |
| 100 | |
| 101 - (void)viewWillMoveToWindow:(NSWindow*)newWindow { | |
| 102 if ([self window] == newWindow) | |
| 103 return; | |
| 104 | |
| 105 if ([self window]) { | |
| 106 [[NSNotificationCenter defaultCenter] | |
| 107 removeObserver:self | |
| 108 name:NSWindowDidBecomeMainNotification | |
| 109 object:[self window]]; | |
| 110 [[NSNotificationCenter defaultCenter] | |
| 111 removeObserver:self | |
| 112 name:NSWindowDidResignMainNotification | |
| 113 object:[self window]]; | |
| 114 } | |
| 115 | |
| 116 if (newWindow) { | |
| 117 [[NSNotificationCenter defaultCenter] | |
| 118 addObserver:self | |
| 119 selector:@selector(onWindowFocusChanged:) | |
| 120 name:NSWindowDidBecomeMainNotification | |
| 121 object:newWindow]; | |
| 122 [[NSNotificationCenter defaultCenter] | |
| 123 addObserver:self | |
| 124 selector:@selector(onWindowFocusChanged:) | |
| 125 name:NSWindowDidResignMainNotification | |
| 126 object:newWindow]; | |
| 127 } | |
| 128 } | |
| 129 | |
| 130 - (void)onWindowFocusChanged:(NSNotification*)note { | |
| 131 [self setNeedsDisplay:YES]; | |
| 132 } | |
| 133 | |
| 134 - (NSRect)tabRect { | |
| 135 NSRect bounds = [self bounds]; | |
| 136 NSRect tabRect; | |
| 137 tabRect.size.width = kTabWidth; | |
| 138 tabRect.size.height = kTabHeight; | |
| 139 tabRect.origin.x = NSMaxX(bounds) - NSWidth(tabRect); | |
| 140 tabRect.origin.y = NSMaxY(bounds) - NSHeight(tabRect); | |
| 141 return tabRect; | |
| 142 } | |
| 143 | |
| 144 - (NSRect)textFieldRect { | |
| 145 NSRect bounds = [self bounds]; | |
| 146 NSSize desiredSize = [textFieldCell_ cellSize]; | |
| 147 | |
| 148 NSRect textRect = bounds; | |
| 149 textRect.size.height = std::min(desiredSize.height, NSHeight(bounds)); | |
| 150 | |
| 151 // For some reason there's always a 2 pixel gap on the right side of the | |
| 152 // text field. Fix it by moving the text field to the right by 2 pixels. | |
| 153 textRect.origin.x += 2; | |
| 154 | |
| 155 return textRect; | |
| 156 } | |
| 157 | |
| 158 - (NSView*)hitTest:(NSPoint)aPoint { | |
| 159 NSView* probe = [super hitTest:aPoint]; | |
| 160 if (probe != self) | |
| 161 return probe; | |
| 162 | |
| 163 NSPoint viewPoint = [self convertPoint:aPoint fromView:[self superview]]; | |
| 164 BOOL isFlipped = [self isFlipped]; | |
| 165 if (NSMouseInRect(viewPoint, [self tabRect], isFlipped)) | |
| 166 return self; | |
| 167 else | |
| 168 return nil; | |
| 169 } | |
| 170 | |
| 171 - (NSBezierPath*)tabPathWithRect:(NSRect)rect | |
| 172 radius:(CGFloat)radius { | |
| 173 const NSRect innerRect = NSInsetRect(rect, radius, radius); | |
| 174 NSBezierPath* path = [NSBezierPath bezierPath]; | |
| 175 | |
| 176 // Top left | |
| 177 [path moveToPoint:NSMakePoint(NSMinX(rect), NSMaxY(rect))]; | |
| 178 | |
| 179 // Bottom left | |
| 180 [path lineToPoint:NSMakePoint(NSMinX(rect), NSMinY(innerRect))]; | |
| 181 [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(innerRect), | |
| 182 NSMinY(innerRect)) | |
| 183 radius:radius | |
| 184 startAngle:180 | |
| 185 endAngle:270 | |
| 186 clockwise:NO]; | |
| 187 | |
| 188 // Bottom right | |
| 189 [path lineToPoint:NSMakePoint(NSMaxX(innerRect), NSMinY(rect))]; | |
| 190 [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(innerRect), | |
| 191 NSMinY(innerRect)) | |
| 192 radius:radius | |
| 193 startAngle:270 | |
| 194 endAngle:360 | |
| 195 clockwise:NO]; | |
| 196 | |
| 197 // Top right | |
| 198 [path lineToPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect))]; | |
| 199 | |
| 200 [path closePath]; | |
| 201 return path; | |
| 202 } | |
| 203 | |
| 204 - (NSBezierPath*)downArrowPathWithRect:(NSRect)rect { | |
| 205 NSBezierPath* path = [NSBezierPath bezierPath]; | |
| 206 | |
| 207 // Top left | |
| 208 [path moveToPoint:NSMakePoint(NSMinX(rect), NSMaxY(rect))]; | |
| 209 | |
| 210 // Bottom middle | |
| 211 [path lineToPoint:NSMakePoint(NSMidX(rect), NSMinY(rect))]; | |
| 212 | |
| 213 // Top right | |
| 214 [path lineToPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect))]; | |
| 215 | |
| 216 [path closePath]; | |
| 217 return path; | |
| 218 } | |
| 219 | |
| 220 - (NSImage*)tabImageWithSize:(NSSize)tabSize | |
| 221 fillColor:(NSColor*)fillColor | |
| 222 isPressed:(BOOL)isPressed { | |
| 223 NSImage* image = [[[NSImage alloc] initWithSize:tabSize] autorelease]; | |
| 224 [image lockFocus]; | |
| 225 | |
| 226 // White shadow for inset look | |
| 227 [[NSGraphicsContext currentContext] saveGraphicsState]; | |
| 228 scoped_nsobject<NSShadow> tabShadow([[NSShadow alloc] init]); | |
| 229 [tabShadow.get() setShadowOffset:NSMakeSize(0, -1)]; | |
| 230 [tabShadow setShadowBlurRadius:0]; | |
| 231 [tabShadow.get() setShadowColor:GetWhiteWithAlpha(0.6)]; | |
| 232 [tabShadow set]; | |
| 233 | |
| 234 // Gray outline | |
| 235 NSRect tabRect = NSMakeRect(0, 1, tabSize.width, tabSize.height - 1); | |
| 236 NSBezierPath* outlinePath = [self tabPathWithRect:tabRect | |
| 237 radius:kTabRoundRectRadius]; | |
| 238 [[NSColor colorWithCalibratedWhite:0.44 alpha:1.0] set]; | |
| 239 [outlinePath fill]; | |
| 240 | |
| 241 [[NSGraphicsContext currentContext] restoreGraphicsState]; | |
| 242 | |
| 243 // Fill | |
| 244 NSRect fillRect = NSInsetRect(tabRect, 1, 0); | |
| 245 fillRect.size.height -= 1; | |
| 246 fillRect.origin.y += 1; | |
| 247 NSBezierPath* fillPath = [self tabPathWithRect:fillRect | |
| 248 radius:kTabRoundRectRadius - 1]; | |
| 249 [fillColor set]; | |
| 250 [fillPath fill]; | |
| 251 | |
| 252 // Shading for fill to make the bottom of the tab slightly darker. | |
| 253 scoped_nsobject<NSGradient> gradient([[NSGradient alloc] | |
| 254 initWithStartingColor:GetBlackWithAlpha(isPressed ? 0.2 : 0.0) | |
| 255 endingColor:GetBlackWithAlpha(0.2)]); | |
| 256 [gradient drawInBezierPath:fillPath angle:270]; | |
| 257 | |
| 258 // Highlight on top | |
| 259 NSRect highlightRect = NSInsetRect(tabRect, 1, 0); | |
| 260 highlightRect.size.height = 1; | |
| 261 highlightRect.origin.y = NSMaxY(tabRect) - highlightRect.size.height; | |
| 262 [GetWhiteWithAlpha(0.5) set]; | |
| 263 NSRectFillUsingOperation(highlightRect, NSCompositeSourceOver); | |
| 264 | |
| 265 // Arrow shadow | |
| 266 [[NSGraphicsContext currentContext] saveGraphicsState]; | |
| 267 scoped_nsobject<NSShadow> arrowShadow([[NSShadow alloc] init]); | |
| 268 [arrowShadow.get() setShadowOffset:NSMakeSize(0, -1)]; | |
| 269 [arrowShadow setShadowBlurRadius:0]; | |
| 270 [arrowShadow.get() setShadowColor:GetBlackWithAlpha(0.6)]; | |
| 271 [arrowShadow set]; | |
| 272 | |
| 273 // Down arrow | |
| 274 NSRect arrowRect; | |
| 275 arrowRect.size.width = kTabArrowWidth; | |
| 276 arrowRect.size.height = kTabArrowHeight; | |
| 277 arrowRect.origin.x = NSMinX(tabRect) + roundf((NSWidth(tabRect) - | |
| 278 NSWidth(arrowRect)) / 2.0); | |
| 279 arrowRect.origin.y = NSMinY(tabRect) + roundf((tabRect.size.height - | |
| 280 arrowRect.size.height) / 2.0); | |
| 281 NSBezierPath* arrowPath = [self downArrowPathWithRect:arrowRect]; | |
| 282 if (isPressed) | |
| 283 [[NSColor colorWithCalibratedWhite:0.8 alpha:1.0] set]; | |
| 284 else | |
| 285 [[NSColor whiteColor] set]; | |
| 286 [arrowPath fill]; | |
| 287 | |
| 288 [[NSGraphicsContext currentContext] restoreGraphicsState]; | |
| 289 | |
| 290 [image unlockFocus]; | |
| 291 return image; | |
| 292 } | |
| 293 | |
| 294 - (NSImage*)tabImage { | |
| 295 BOOL isPressed = [[self cell] isHighlighted]; | |
| 296 | |
| 297 // Invalidate the cached image if necessary. | |
| 298 if (cachedTabImageIsPressed_ != isPressed) { | |
| 299 cachedTabImageIsPressed_ = isPressed; | |
| 300 cachedTabImage_.reset(); | |
| 301 } | |
| 302 | |
| 303 if (cachedTabImage_) | |
| 304 return cachedTabImage_; | |
| 305 | |
| 306 // TODO: Use different colors for different profiles and tint for | |
| 307 // the current browser theme. | |
| 308 NSColor* fillColor = [NSColor colorWithCalibratedRed:122.0/255.0 | |
| 309 green:177.0/255.0 | |
| 310 blue:252.0/255.0 | |
| 311 alpha:1.0]; | |
| 312 NSRect tabRect = [self tabRect]; | |
| 313 cachedTabImage_.reset([[self tabImageWithSize:tabRect.size | |
| 314 fillColor:fillColor | |
| 315 isPressed:isPressed] retain]); | |
| 316 return cachedTabImage_; | |
| 317 } | |
| 318 | |
| 319 - (void)drawRect:(NSRect)rect { | |
| 320 CGFloat alpha = [[self window] isMainWindow] ? 1.0 : 0.5; | |
| 321 [[self tabImage] drawInRect:[self tabRect] | |
| 322 fromRect:NSZeroRect | |
| 323 operation:NSCompositeSourceOver | |
| 324 fraction:alpha]; | |
| 325 | |
| 326 if (shouldShowProfileDisplayName_) { | |
| 327 NSColor* textColor = [[self window] isMainWindow] ? | |
| 328 GetBlackWithAlpha(0.6) : GetBlackWithAlpha(0.4); | |
| 329 if (![[textFieldCell_ textColor] isEqual:textColor]) | |
| 330 [textFieldCell_ setTextColor:textColor]; | |
| 331 [textFieldCell_ drawWithFrame:[self textFieldRect] inView:self]; | |
| 332 } | |
| 333 } | |
| 334 | |
| 335 - (NSSize)desiredControlSize { | |
| 336 NSSize size = [self tabRect].size; | |
| 337 | |
| 338 if (shouldShowProfileDisplayName_) { | |
| 339 NSSize textFieldSize = [textFieldCell_ cellSize]; | |
| 340 size.width = std::max(size.width, textFieldSize.width); | |
| 341 size.height += textFieldSize.height + kTabDisplayNameMarginY; | |
| 342 } | |
| 343 | |
| 344 size.width = ceil(size.width); | |
| 345 size.height = ceil(size.height); | |
| 346 return size; | |
| 347 } | |
| 348 | |
| 349 - (NSSize)minControlSize { | |
| 350 return [self tabRect].size; | |
| 351 } | |
| 352 | |
| 353 // Overridden from MenuButton. | |
| 354 - (NSMenu*)attachedMenu { | |
| 355 return [menu_.get() menu]; | |
| 356 } | |
| 357 | |
| 358 // Overridden from MenuButton. | |
| 359 - (NSRect)menuRect { | |
| 360 return [self tabRect]; | |
| 361 } | |
| 362 | |
| 363 @end | |
| OLD | NEW |