blob: 1935761888d51e580c76592702afb5502256b6a7 [file] [log] [blame]
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001// 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#include "chrome/browser/ui/cocoa/gradient_button_cell.h"
6
7#include <cmath>
8
9#include "base/logging.h"
Ben Murdocheb525c52013-07-10 11:40:50 +010010#import "base/mac/scoped_nsobject.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000011#import "chrome/browser/themes/theme_properties.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000012#import "chrome/browser/themes/theme_service.h"
13#import "chrome/browser/ui/cocoa/nsview_additions.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000014#import "chrome/browser/ui/cocoa/rect_path_utils.h"
Ben Murdocheb525c52013-07-10 11:40:50 +010015#import "chrome/browser/ui/cocoa/themed_window.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000016#include "grit/theme_resources.h"
17#import "third_party/GTM/AppKit/GTMNSColor+Luminance.h"
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +010018#import "ui/base/cocoa/nsgraphics_context_additions.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000019#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
20
21@interface GradientButtonCell (Private)
22- (void)sharedInit;
23
24// Get drawing parameters for a given cell frame in a given view. The inner
25// frame is the one required by |-drawInteriorWithFrame:inView:|. The inner and
26// outer paths are the ones required by |-drawBorderAndFillForTheme:...|. The
27// outer path also gives the area in which to clip. Any of the |return...|
28// arguments may be NULL (in which case the given parameter won't be returned).
29// If |returnInnerPath| or |returnOuterPath|, |*returnInnerPath| or
30// |*returnOuterPath| should be nil, respectively.
31- (void)getDrawParamsForFrame:(NSRect)cellFrame
32 inView:(NSView*)controlView
33 innerFrame:(NSRect*)returnInnerFrame
34 innerPath:(NSBezierPath**)returnInnerPath
35 clipPath:(NSBezierPath**)returnClipPath;
36
37- (void)updateTrackingAreas;
38
39@end
40
41
42static const NSTimeInterval kAnimationShowDuration = 0.2;
43
44// Note: due to a bug (?), drawWithFrame:inView: does not call
45// drawBorderAndFillForTheme::::: unless the mouse is inside. The net
46// effect is that our "fade out" when the mouse leaves becaumes
47// instantaneous. When I "fixed" it things looked horrible; the
48// hover-overed bookmark button would stay highlit for 0.4 seconds
49// which felt like latency/lag. I'm leaving the "bug" in place for
50// now so we don't suck. -jrg
51static const NSTimeInterval kAnimationHideDuration = 0.4;
52
53static const NSTimeInterval kAnimationContinuousCycleDuration = 0.4;
54
55@implementation GradientButtonCell
56
57@synthesize hoverAlpha = hoverAlpha_;
58
59// For nib instantiations
60- (id)initWithCoder:(NSCoder*)decoder {
61 if ((self = [super initWithCoder:decoder])) {
62 [self sharedInit];
63 }
64 return self;
65}
66
67// For programmatic instantiations
68- (id)initTextCell:(NSString*)string {
69 if ((self = [super initTextCell:string])) {
70 [self sharedInit];
71 }
72 return self;
73}
74
75- (void)dealloc {
76 if (trackingArea_) {
77 [[self controlView] removeTrackingArea:trackingArea_];
78 trackingArea_.reset();
79 }
80 [super dealloc];
81}
82
83// Return YES if we are pulsing (towards another state or continuously).
84- (BOOL)pulsing {
85 if ((pulseState_ == gradient_button_cell::kPulsingOn) ||
86 (pulseState_ == gradient_button_cell::kPulsingOff) ||
87 (pulseState_ == gradient_button_cell::kPulsingContinuous))
88 return YES;
89 return NO;
90}
91
92// Perform one pulse step when animating a pulse.
93- (void)performOnePulseStep {
94 NSTimeInterval thisUpdate = [NSDate timeIntervalSinceReferenceDate];
95 NSTimeInterval elapsed = thisUpdate - lastHoverUpdate_;
96 CGFloat opacity = [self hoverAlpha];
97
98 // Update opacity based on state.
99 // Adjust state if we have finished.
100 switch (pulseState_) {
101 case gradient_button_cell::kPulsingOn:
102 opacity += elapsed / kAnimationShowDuration;
103 if (opacity > 1.0) {
104 [self setPulseState:gradient_button_cell::kPulsedOn];
105 return;
106 }
107 break;
108 case gradient_button_cell::kPulsingOff:
109 opacity -= elapsed / kAnimationHideDuration;
110 if (opacity < 0.0) {
111 [self setPulseState:gradient_button_cell::kPulsedOff];
112 return;
113 }
114 break;
115 case gradient_button_cell::kPulsingContinuous:
116 opacity += elapsed / kAnimationContinuousCycleDuration * pulseMultiplier_;
117 if (opacity > 1.0) {
118 opacity = 1.0;
119 pulseMultiplier_ *= -1.0;
120 } else if (opacity < 0.0) {
121 opacity = 0.0;
122 pulseMultiplier_ *= -1.0;
123 }
124 outerStrokeAlphaMult_ = opacity;
125 break;
126 default:
127 NOTREACHED() << "unknown pulse state";
128 }
129
130 // Update our control.
131 lastHoverUpdate_ = thisUpdate;
132 [self setHoverAlpha:opacity];
133 [[self controlView] setNeedsDisplay:YES];
134
135 // If our state needs it, keep going.
136 if ([self pulsing]) {
137 [self performSelector:_cmd withObject:nil afterDelay:0.02];
138 }
139}
140
141- (gradient_button_cell::PulseState)pulseState {
142 return pulseState_;
143}
144
145// Set the pulsing state. This can either set the pulse to on or off
146// immediately (e.g. kPulsedOn, kPulsedOff) or initiate an animated
147// state change.
148- (void)setPulseState:(gradient_button_cell::PulseState)pstate {
149 pulseState_ = pstate;
150 pulseMultiplier_ = 0.0;
151 [NSObject cancelPreviousPerformRequestsWithTarget:self];
152 lastHoverUpdate_ = [NSDate timeIntervalSinceReferenceDate];
153
154 switch (pstate) {
155 case gradient_button_cell::kPulsedOn:
156 case gradient_button_cell::kPulsedOff:
157 outerStrokeAlphaMult_ = 1.0;
158 [self setHoverAlpha:((pulseState_ == gradient_button_cell::kPulsedOn) ?
159 1.0 : 0.0)];
160 [[self controlView] setNeedsDisplay:YES];
161 break;
162 case gradient_button_cell::kPulsingOn:
163 case gradient_button_cell::kPulsingOff:
164 outerStrokeAlphaMult_ = 1.0;
165 // Set initial value then engage timer.
166 [self setHoverAlpha:((pulseState_ == gradient_button_cell::kPulsingOn) ?
167 0.0 : 1.0)];
168 [self performOnePulseStep];
169 break;
170 case gradient_button_cell::kPulsingContinuous:
171 // Semantics of continuous pulsing are that we pulse independent
172 // of mouse position.
173 pulseMultiplier_ = 1.0;
174 [self performOnePulseStep];
175 break;
176 default:
177 CHECK(0);
178 break;
179 }
180}
181
182- (void)safelyStopPulsing {
183 [NSObject cancelPreviousPerformRequestsWithTarget:self];
184}
185
186- (void)setIsContinuousPulsing:(BOOL)continuous {
187 if (!continuous && pulseState_ != gradient_button_cell::kPulsingContinuous)
188 return;
189 if (continuous) {
190 [self setPulseState:gradient_button_cell::kPulsingContinuous];
191 } else {
192 [self setPulseState:(isMouseInside_ ? gradient_button_cell::kPulsedOn :
193 gradient_button_cell::kPulsedOff)];
194 }
195}
196
197- (BOOL)isContinuousPulsing {
198 return (pulseState_ == gradient_button_cell::kPulsingContinuous) ?
199 YES : NO;
200}
201
202#if 1
203// If we are not continuously pulsing, perform a pulse animation to
204// reflect our new state.
205- (void)setMouseInside:(BOOL)flag animate:(BOOL)animated {
206 isMouseInside_ = flag;
207 if (pulseState_ != gradient_button_cell::kPulsingContinuous) {
208 if (animated) {
209 [self setPulseState:(isMouseInside_ ? gradient_button_cell::kPulsingOn :
210 gradient_button_cell::kPulsingOff)];
211 } else {
212 [self setPulseState:(isMouseInside_ ? gradient_button_cell::kPulsedOn :
213 gradient_button_cell::kPulsedOff)];
214 }
215 }
216}
217#else
218
219- (void)adjustHoverValue {
220 NSTimeInterval thisUpdate = [NSDate timeIntervalSinceReferenceDate];
221
222 NSTimeInterval elapsed = thisUpdate - lastHoverUpdate_;
223
224 CGFloat opacity = [self hoverAlpha];
225 if (isMouseInside_) {
226 opacity += elapsed / kAnimationShowDuration;
227 } else {
228 opacity -= elapsed / kAnimationHideDuration;
229 }
230
231 if (!isMouseInside_ && opacity < 0) {
232 opacity = 0;
233 } else if (isMouseInside_ && opacity > 1) {
234 opacity = 1;
235 } else {
236 [self performSelector:_cmd withObject:nil afterDelay:0.02];
237 }
238 lastHoverUpdate_ = thisUpdate;
239 [self setHoverAlpha:opacity];
240
241 [[self controlView] setNeedsDisplay:YES];
242}
243
244- (void)setMouseInside:(BOOL)flag animate:(BOOL)animated {
245 isMouseInside_ = flag;
246 if (animated) {
247 lastHoverUpdate_ = [NSDate timeIntervalSinceReferenceDate];
248 [self adjustHoverValue];
249 } else {
250 [NSObject cancelPreviousPerformRequestsWithTarget:self];
251 [self setHoverAlpha:flag ? 1.0 : 0.0];
252 }
253 [[self controlView] setNeedsDisplay:YES];
254}
255
256
257
258#endif
259
260- (NSGradient*)gradientForHoverAlpha:(CGFloat)hoverAlpha
261 isThemed:(BOOL)themed {
262 CGFloat startAlpha = 0.6 + 0.3 * hoverAlpha;
263 CGFloat endAlpha = 0.333 * hoverAlpha;
264
265 if (themed) {
266 startAlpha = 0.2 + 0.35 * hoverAlpha;
267 endAlpha = 0.333 * hoverAlpha;
268 }
269
270 NSColor* startColor =
271 [NSColor colorWithCalibratedWhite:1.0
272 alpha:startAlpha];
273 NSColor* endColor =
274 [NSColor colorWithCalibratedWhite:1.0 - 0.15 * hoverAlpha
275 alpha:endAlpha];
276 NSGradient* gradient = [[NSGradient alloc] initWithColorsAndLocations:
277 startColor, hoverAlpha * 0.33,
278 endColor, 1.0, nil];
279
280 return [gradient autorelease];
281}
282
283- (void)sharedInit {
284 shouldTheme_ = YES;
285 pulseState_ = gradient_button_cell::kPulsedOff;
286 pulseMultiplier_ = 1.0;
287 outerStrokeAlphaMult_ = 1.0;
288 gradient_.reset([[self gradientForHoverAlpha:0.0 isThemed:NO] retain]);
289}
290
291- (void)setShouldTheme:(BOOL)shouldTheme {
292 shouldTheme_ = shouldTheme;
293}
294
295- (NSImage*)overlayImage {
296 return overlayImage_.get();
297}
298
299- (void)setOverlayImage:(NSImage*)image {
300 overlayImage_.reset([image retain]);
301 [[self controlView] setNeedsDisplay:YES];
302}
303
304- (NSBackgroundStyle)interiorBackgroundStyle {
305 // Never lower the interior, since that just leads to a weird shadow which can
306 // often interact badly with the theme.
307 return NSBackgroundStyleRaised;
308}
309
310- (void)mouseEntered:(NSEvent*)theEvent {
311 [self setMouseInside:YES animate:YES];
312}
313
314- (void)mouseExited:(NSEvent*)theEvent {
315 [self setMouseInside:NO animate:YES];
316}
317
318- (BOOL)isMouseInside {
319 return trackingArea_ && isMouseInside_;
320}
321
322// Since we have our own drawWithFrame:, we need to also have our own
323// logic for determining when the mouse is inside for honoring this
324// request.
325- (void)setShowsBorderOnlyWhileMouseInside:(BOOL)showOnly {
326 [super setShowsBorderOnlyWhileMouseInside:showOnly];
327 if (showOnly) {
328 [self updateTrackingAreas];
329 } else {
330 if (trackingArea_) {
331 [[self controlView] removeTrackingArea:trackingArea_];
332 trackingArea_.reset(nil);
333 if (isMouseInside_) {
334 isMouseInside_ = NO;
335 [[self controlView] setNeedsDisplay:YES];
336 }
337 }
338 }
339}
340
341// TODO(viettrungluu): clean up/reorganize.
342- (void)drawBorderAndFillForTheme:(ui::ThemeProvider*)themeProvider
343 controlView:(NSView*)controlView
344 innerPath:(NSBezierPath*)innerPath
345 showClickedGradient:(BOOL)showClickedGradient
346 showHighlightGradient:(BOOL)showHighlightGradient
347 hoverAlpha:(CGFloat)hoverAlpha
348 active:(BOOL)active
349 cellFrame:(NSRect)cellFrame
350 defaultGradient:(NSGradient*)defaultGradient {
351 BOOL isFlatButton = [self showsBorderOnlyWhileMouseInside];
352
353 // For flat (unbordered when not hovered) buttons, never use the toolbar
354 // button background image, but the modest gradient used for themed buttons.
355 // To make things even more modest, scale the hover alpha down by 40 percent
356 // unless clicked.
357 NSColor* backgroundImageColor;
358 BOOL useThemeGradient;
359 if (isFlatButton) {
360 backgroundImageColor = nil;
361 useThemeGradient = YES;
362 if (!showClickedGradient)
363 hoverAlpha *= 0.6;
364 } else {
Ben Murdochbb1529c2013-08-08 10:24:53 +0100365 backgroundImageColor = nil;
366 if (themeProvider &&
367 themeProvider->HasCustomImage(IDR_THEME_BUTTON_BACKGROUND)) {
368 backgroundImageColor =
369 themeProvider->GetNSImageColorNamed(IDR_THEME_BUTTON_BACKGROUND);
370 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000371 useThemeGradient = backgroundImageColor ? YES : NO;
372 }
373
374 // The basic gradient shown inside; see above.
375 NSGradient* gradient;
376 if (hoverAlpha == 0 && !useThemeGradient) {
377 gradient = defaultGradient ? defaultGradient
378 : gradient_;
379 } else {
380 gradient = [self gradientForHoverAlpha:hoverAlpha
381 isThemed:useThemeGradient];
382 }
383
384 // If we're drawing a background image, show that; else possibly show the
385 // clicked gradient.
386 if (backgroundImageColor) {
387 [backgroundImageColor set];
388 // Set the phase to match window.
389 NSRect trueRect = [controlView convertRect:cellFrame toView:nil];
390 [[NSGraphicsContext currentContext]
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100391 cr_setPatternPhase:NSMakePoint(NSMinX(trueRect), NSMaxY(trueRect))
392 forView:controlView];
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000393 [innerPath fill];
394 } else {
395 if (showClickedGradient) {
396 NSGradient* clickedGradient = nil;
397 if (isFlatButton &&
398 [self tag] == kStandardButtonTypeWithLimitedClickFeedback) {
399 clickedGradient = gradient;
400 } else {
401 clickedGradient = themeProvider ? themeProvider->GetNSGradient(
402 active ?
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000403 ThemeProperties::GRADIENT_TOOLBAR_BUTTON_PRESSED :
404 ThemeProperties::GRADIENT_TOOLBAR_BUTTON_PRESSED_INACTIVE) :
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000405 nil;
406 }
407 [clickedGradient drawInBezierPath:innerPath angle:90.0];
408 }
409 }
410
411 // Visually indicate unclicked, enabled buttons.
412 if (!showClickedGradient && [self isEnabled]) {
413 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
414 [innerPath addClip];
415
416 // Draw the inner glow.
417 if (hoverAlpha > 0) {
418 [innerPath setLineWidth:2];
419 [[NSColor colorWithCalibratedWhite:1.0 alpha:0.2 * hoverAlpha] setStroke];
420 [innerPath stroke];
421 }
422
423 // Draw the top inner highlight.
424 NSAffineTransform* highlightTransform = [NSAffineTransform transform];
425 [highlightTransform translateXBy:1 yBy:1];
Ben Murdocheb525c52013-07-10 11:40:50 +0100426 base::scoped_nsobject<NSBezierPath> highlightPath([innerPath copy]);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000427 [highlightPath transformUsingAffineTransform:highlightTransform];
428 [[NSColor colorWithCalibratedWhite:1.0 alpha:0.2] setStroke];
429 [highlightPath stroke];
430
431 // Draw the gradient inside.
432 [gradient drawInBezierPath:innerPath angle:90.0];
433 }
434
435 // Don't draw anything else for disabled flat buttons.
436 if (isFlatButton && ![self isEnabled])
437 return;
438
439 // Draw the outer stroke.
440 NSColor* strokeColor = nil;
441 if (showClickedGradient) {
442 strokeColor = [NSColor
443 colorWithCalibratedWhite:0.0
444 alpha:0.3 * outerStrokeAlphaMult_];
445 } else {
446 strokeColor = themeProvider ? themeProvider->GetNSColor(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000447 active ? ThemeProperties::COLOR_TOOLBAR_BUTTON_STROKE :
Ben Murdochbb1529c2013-08-08 10:24:53 +0100448 ThemeProperties::COLOR_TOOLBAR_BUTTON_STROKE_INACTIVE) :
449 [NSColor colorWithCalibratedWhite:0.0
450 alpha:0.3 * outerStrokeAlphaMult_];
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000451 }
452 [strokeColor setStroke];
453
454 [innerPath setLineWidth:1];
455 [innerPath stroke];
456}
457
458// TODO(viettrungluu): clean this up.
459// (Private)
460- (void)getDrawParamsForFrame:(NSRect)cellFrame
461 inView:(NSView*)controlView
462 innerFrame:(NSRect*)returnInnerFrame
463 innerPath:(NSBezierPath**)returnInnerPath
464 clipPath:(NSBezierPath**)returnClipPath {
465 const CGFloat lineWidth = [controlView cr_lineWidth];
466 const CGFloat halfLineWidth = lineWidth / 2.0;
467
468 // Constants from Cole. Will kConstant them once the feedback loop
469 // is complete.
470 NSRect drawFrame = NSInsetRect(cellFrame, 1.5 * lineWidth, 1.5 * lineWidth);
471 NSRect innerFrame = NSInsetRect(cellFrame, lineWidth, lineWidth);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000472 const CGFloat radius = 3;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000473
474 ButtonType type = [[(NSControl*)controlView cell] tag];
475 switch (type) {
476 case kMiddleButtonType:
477 drawFrame.size.width += 20;
478 innerFrame.size.width += 2;
479 // Fallthrough
480 case kRightButtonType:
481 drawFrame.origin.x -= 20;
482 innerFrame.origin.x -= 2;
483 // Fallthrough
484 case kLeftButtonType:
485 case kLeftButtonWithShadowType:
486 drawFrame.size.width += 20;
487 innerFrame.size.width += 2;
488 default:
489 break;
490 }
491 if (type == kLeftButtonWithShadowType)
492 innerFrame.size.width -= 1.0;
493
494 // Return results if |return...| not null.
495 if (returnInnerFrame)
496 *returnInnerFrame = innerFrame;
497 if (returnInnerPath) {
498 DCHECK(*returnInnerPath == nil);
499 *returnInnerPath = [NSBezierPath bezierPathWithRoundedRect:drawFrame
500 xRadius:radius
501 yRadius:radius];
502 [*returnInnerPath setLineWidth:lineWidth];
503 }
504 if (returnClipPath) {
505 DCHECK(*returnClipPath == nil);
506 NSRect clipPathRect =
507 NSInsetRect(drawFrame, -halfLineWidth, -halfLineWidth);
508 *returnClipPath = [NSBezierPath
509 bezierPathWithRoundedRect:clipPathRect
510 xRadius:radius + halfLineWidth
511 yRadius:radius + halfLineWidth];
512 }
513}
514
515// TODO(viettrungluu): clean this up.
516- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
517 NSRect innerFrame;
518 NSBezierPath* innerPath = nil;
519 [self getDrawParamsForFrame:cellFrame
520 inView:controlView
521 innerFrame:&innerFrame
522 innerPath:&innerPath
523 clipPath:NULL];
524
525 BOOL pressed = ([((NSControl*)[self controlView]) isEnabled] &&
526 [self isHighlighted]);
527 NSWindow* window = [controlView window];
528 ui::ThemeProvider* themeProvider = [window themeProvider];
529 BOOL active = [window isKeyWindow] || [window isMainWindow];
530
531 // Stroke the borders and appropriate fill gradient. If we're borderless, the
532 // only time we want to draw the inner gradient is if we're highlighted or if
533 // we're the first responder (when "Full Keyboard Access" is turned on).
534 if (([self isBordered] && ![self showsBorderOnlyWhileMouseInside]) ||
535 pressed ||
536 [self isMouseInside] ||
537 [self isContinuousPulsing] ||
538 [self showsFirstResponder]) {
539
540 // When pulsing we want the bookmark to stand out a little more.
541 BOOL showClickedGradient = pressed ||
542 (pulseState_ == gradient_button_cell::kPulsingContinuous);
543
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000544 [self drawBorderAndFillForTheme:themeProvider
545 controlView:controlView
546 innerPath:innerPath
547 showClickedGradient:showClickedGradient
548 showHighlightGradient:[self isHighlighted]
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000549 hoverAlpha:[self hoverAlpha]
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000550 active:active
551 cellFrame:cellFrame
552 defaultGradient:nil];
553 }
554
555 // If this is the left side of a segmented button, draw a slight shadow.
556 ButtonType type = [[(NSControl*)controlView cell] tag];
557 if (type == kLeftButtonWithShadowType) {
558 const CGFloat lineWidth = [controlView cr_lineWidth];
559 NSRect borderRect, contentRect;
560 NSDivideRect(cellFrame, &borderRect, &contentRect, lineWidth, NSMaxXEdge);
561 NSColor* stroke = themeProvider ? themeProvider->GetNSColor(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000562 active ? ThemeProperties::COLOR_TOOLBAR_BUTTON_STROKE :
Ben Murdochbb1529c2013-08-08 10:24:53 +0100563 ThemeProperties::COLOR_TOOLBAR_BUTTON_STROKE_INACTIVE) :
564 [NSColor blackColor];
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000565
566 [[stroke colorWithAlphaComponent:0.2] set];
567 NSRectFillUsingOperation(NSInsetRect(borderRect, 0, 2),
568 NSCompositeSourceOver);
569 }
570 [self drawInteriorWithFrame:innerFrame inView:controlView];
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000571
572 // Draws the blue focus ring.
573 if ([self showsFirstResponder]) {
574 gfx::ScopedNSGraphicsContextSaveGState scoped_state;
575 const CGFloat lineWidth = [controlView cr_lineWidth];
576 // insetX = 1.0 is used for the drawing of blue highlight so that this
577 // highlight won't be too near the bookmark toolbar itself, in case we
578 // draw bookmark buttons in bookmark toolbar.
579 rect_path_utils::FrameRectWithInset(rect_path_utils::RoundedCornerAll,
580 NSInsetRect(cellFrame, 0, lineWidth),
581 1.0, // insetX
582 0.0, // insetY
583 3.0, // outerRadius
584 lineWidth * 2, // lineWidth
585 [controlView
586 cr_keyboardFocusIndicatorColor]);
587 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000588}
589
590- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
591 const CGFloat lineWidth = [controlView cr_lineWidth];
592
593 if (shouldTheme_) {
594 BOOL isTemplate = [[self image] isTemplate];
595
596 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
597
598 CGContextRef context =
599 (CGContextRef)([[NSGraphicsContext currentContext] graphicsPort]);
600
601 ThemeService* themeProvider = static_cast<ThemeService*>(
602 [[controlView window] themeProvider]);
603 NSColor* color = themeProvider ?
Ben Murdochbb1529c2013-08-08 10:24:53 +0100604 themeProvider->GetNSColorTint(ThemeProperties::TINT_BUTTONS) :
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000605 [NSColor blackColor];
606
607 if (isTemplate && themeProvider && themeProvider->UsingDefaultTheme()) {
Ben Murdocheb525c52013-07-10 11:40:50 +0100608 base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000609 [shadow.get() setShadowColor:themeProvider->GetNSColor(
Ben Murdochbb1529c2013-08-08 10:24:53 +0100610 ThemeProperties::COLOR_TOOLBAR_BEZEL)];
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000611 [shadow.get() setShadowOffset:NSMakeSize(0.0, -lineWidth)];
612 [shadow setShadowBlurRadius:lineWidth];
613 [shadow set];
614 }
615
616 CGContextBeginTransparencyLayer(context, 0);
617 NSRect imageRect = NSZeroRect;
618 imageRect.size = [[self image] size];
619 NSRect drawRect = [self imageRectForBounds:cellFrame];
620 [[self image] drawInRect:drawRect
621 fromRect:imageRect
622 operation:NSCompositeSourceOver
623 fraction:[self isEnabled] ? 1.0 : 0.5
624 respectFlipped:YES
625 hints:nil];
626 if (isTemplate && color) {
627 [color set];
628 NSRectFillUsingOperation(cellFrame, NSCompositeSourceAtop);
629 }
630 CGContextEndTransparencyLayer(context);
631 } else {
632 // NSCell draws these off-center for some reason, probably because of the
633 // positioning of the control in the xib.
634 [super drawInteriorWithFrame:NSOffsetRect(cellFrame, 0, lineWidth)
635 inView:controlView];
636 }
637
638 if (overlayImage_) {
639 NSRect imageRect = NSZeroRect;
640 imageRect.size = [overlayImage_ size];
641 [overlayImage_ drawInRect:[self imageRectForBounds:cellFrame]
642 fromRect:imageRect
643 operation:NSCompositeSourceOver
644 fraction:[self isEnabled] ? 1.0 : 0.5
645 respectFlipped:YES
646 hints:nil];
647 }
648}
649
650- (int)verticalTextOffset {
651 return 1;
652}
653
654// Overriden from NSButtonCell so we can display a nice fadeout effect for
655// button titles that overflow.
656// This method is copied in the most part from GTMFadeTruncatingTextFieldCell,
657// the only difference is that here we draw the text ourselves rather than
658// calling the super to do the work.
659// We can't use GTMFadeTruncatingTextFieldCell because there's no easy way to
660// get it to work with NSButtonCell.
661// TODO(jeremy): Move this to GTM.
662- (NSRect)drawTitle:(NSAttributedString*)title
663 withFrame:(NSRect)cellFrame
664 inView:(NSView*)controlView {
665 NSSize size = [title size];
666
667 // Empirically, Cocoa will draw an extra 2 pixels past NSWidth(cellFrame)
668 // before it clips the text.
669 const CGFloat kOverflowBeforeClip = 2;
670 BOOL clipping = YES;
671 if (std::floor(size.width) <= (NSWidth(cellFrame) + kOverflowBeforeClip)) {
672 cellFrame.origin.y += ([self verticalTextOffset] - 1);
673 clipping = NO;
674 }
675
676 // Gradient is about twice our line height long.
677 CGFloat gradientWidth = MIN(size.height * 2, NSWidth(cellFrame) / 4);
678
679 NSRect solidPart, gradientPart;
680 NSDivideRect(cellFrame, &gradientPart, &solidPart, gradientWidth, NSMaxXEdge);
681
682 // Draw non-gradient part without transparency layer, as light text on a dark
683 // background looks bad with a gradient layer.
684 NSPoint textOffset = NSZeroPoint;
685 {
686 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
687 if (clipping)
688 [NSBezierPath clipRect:solidPart];
689
690 // 11 is the magic number needed to make this match the native
691 // NSButtonCell's label display.
692 CGFloat textLeft = [[self image] size].width + 11;
693
694 // For some reason, the height of cellFrame as passed in is totally bogus.
695 // For vertical centering purposes, we need the bounds of the containing
696 // view.
697 NSRect buttonFrame = [[self controlView] frame];
698
699 // Call the vertical offset to match native NSButtonCell's version.
700 textOffset = NSMakePoint(textLeft,
701 (NSHeight(buttonFrame) - size.height) / 2 +
702 [self verticalTextOffset]);
703 [title drawAtPoint:textOffset];
704 }
705
706 if (!clipping)
707 return cellFrame;
708
709 // Draw the gradient part with a transparency layer. This makes the text look
710 // suboptimal, but since it fades out, that's ok.
711 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
712 [NSBezierPath clipRect:gradientPart];
713 CGContextRef context = static_cast<CGContextRef>(
714 [[NSGraphicsContext currentContext] graphicsPort]);
715 CGContextBeginTransparencyLayerWithRect(context,
716 NSRectToCGRect(gradientPart), 0);
717 [title drawAtPoint:textOffset];
718
719 NSColor *color = [NSColor textColor];
720 NSColor *alphaColor = [color colorWithAlphaComponent:0.0];
721 NSGradient *mask = [[NSGradient alloc] initWithStartingColor:color
722 endingColor:alphaColor];
723
724 // Draw the gradient mask
725 CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
726 [mask drawFromPoint:NSMakePoint(NSMaxX(cellFrame) - gradientWidth,
727 NSMinY(cellFrame))
728 toPoint:NSMakePoint(NSMaxX(cellFrame),
729 NSMinY(cellFrame))
730 options:NSGradientDrawsBeforeStartingLocation];
731 [mask release];
732 CGContextEndTransparencyLayer(context);
733
734 return cellFrame;
735}
736
737- (NSBezierPath*)clipPathForFrame:(NSRect)cellFrame
738 inView:(NSView*)controlView {
739 NSBezierPath* boundingPath = nil;
740 [self getDrawParamsForFrame:cellFrame
741 inView:controlView
742 innerFrame:NULL
743 innerPath:NULL
744 clipPath:&boundingPath];
745 return boundingPath;
746}
747
748- (void)resetCursorRect:(NSRect)cellFrame inView:(NSView*)controlView {
749 [super resetCursorRect:cellFrame inView:controlView];
750 if (trackingArea_)
751 [self updateTrackingAreas];
752}
753
754- (BOOL)isMouseReallyInside {
755 BOOL mouseInView = NO;
756 NSView* controlView = [self controlView];
757 NSWindow* window = [controlView window];
758 NSRect bounds = [controlView bounds];
759 if (window) {
760 NSPoint mousePoint = [window mouseLocationOutsideOfEventStream];
761 mousePoint = [controlView convertPoint:mousePoint fromView:nil];
762 mouseInView = [controlView mouse:mousePoint inRect:bounds];
763 }
764 return mouseInView;
765}
766
767- (void)updateTrackingAreas {
768 NSView* controlView = [self controlView];
769 BOOL mouseInView = [self isMouseReallyInside];
770
771 if (trackingArea_.get())
772 [controlView removeTrackingArea:trackingArea_];
773
774 NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
775 NSTrackingActiveInActiveApp;
776 if (mouseInView)
777 options |= NSTrackingAssumeInside;
778
779 trackingArea_.reset([[NSTrackingArea alloc]
780 initWithRect:[controlView bounds]
781 options:options
782 owner:self
783 userInfo:nil]);
784 if (isMouseInside_ != mouseInView) {
785 [self setMouseInside:mouseInView animate:NO];
786 [controlView setNeedsDisplay:YES];
787 }
788}
789
790@end