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 |