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.
5 #include "chrome/browser/ui/cocoa/gradient_button_cell.h"
9 #include "base/logging.h"
10 #include "base/mac/mac_util.h"
11 #import "base/mac/scoped_nsobject.h"
12 #import "chrome/browser/themes/theme_properties.h"
13 #import "chrome/browser/themes/theme_service.h"
14 #import "chrome/browser/ui/cocoa/rect_path_utils.h"
15 #import "chrome/browser/ui/cocoa/themed_window.h"
16 #include "grit/theme_resources.h"
17 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSColor+Luminance.h"
18 #import "ui/base/cocoa/nsgraphics_context_additions.h"
19 #import "ui/base/cocoa/nsview_additions.h"
20 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
22 @interface GradientButtonCell (Private)
25 // Get drawing parameters for a given cell frame in a given view. The inner
26 // frame is the one required by |-drawInteriorWithFrame:inView:|. The inner and
27 // outer paths are the ones required by |-drawBorderAndFillForTheme:...|. The
28 // outer path also gives the area in which to clip. Any of the |return...|
29 // arguments may be NULL (in which case the given parameter won't be returned).
30 // If |returnInnerPath| or |returnOuterPath|, |*returnInnerPath| or
31 // |*returnOuterPath| should be nil, respectively.
32 - (void)getDrawParamsForFrame:(NSRect)cellFrame
33 inView:(NSView*)controlView
34 innerFrame:(NSRect*)returnInnerFrame
35 innerPath:(NSBezierPath**)returnInnerPath
36 clipPath:(NSBezierPath**)returnClipPath;
38 - (void)updateTrackingAreas;
43 static const NSTimeInterval kAnimationShowDuration = 0.2;
45 // Note: due to a bug (?), drawWithFrame:inView: does not call
46 // drawBorderAndFillForTheme::::: unless the mouse is inside. The net
47 // effect is that our "fade out" when the mouse leaves becaumes
48 // instantaneous. When I "fixed" it things looked horrible; the
49 // hover-overed bookmark button would stay highlit for 0.4 seconds
50 // which felt like latency/lag. I'm leaving the "bug" in place for
51 // now so we don't suck. -jrg
52 static const NSTimeInterval kAnimationHideDuration = 0.4;
54 static const NSTimeInterval kAnimationContinuousCycleDuration = 0.4;
56 @implementation GradientButtonCell
58 @synthesize hoverAlpha = hoverAlpha_;
60 // For nib instantiations
61 - (id)initWithCoder:(NSCoder*)decoder {
62 if ((self = [super initWithCoder:decoder])) {
68 // For programmatic instantiations
69 - (id)initTextCell:(NSString*)string {
70 if ((self = [super initTextCell:string])) {
78 [[self controlView] removeTrackingArea:trackingArea_];
79 trackingArea_.reset();
84 // Return YES if we are pulsing (towards another state or continuously).
86 if ((pulseState_ == gradient_button_cell::kPulsingOn) ||
87 (pulseState_ == gradient_button_cell::kPulsingOff) ||
88 (pulseState_ == gradient_button_cell::kPulsingContinuous))
93 // Perform one pulse step when animating a pulse.
94 - (void)performOnePulseStep {
95 NSTimeInterval thisUpdate = [NSDate timeIntervalSinceReferenceDate];
96 NSTimeInterval elapsed = thisUpdate - lastHoverUpdate_;
97 CGFloat opacity = [self hoverAlpha];
99 // Update opacity based on state.
100 // Adjust state if we have finished.
101 switch (pulseState_) {
102 case gradient_button_cell::kPulsingOn:
103 opacity += elapsed / kAnimationShowDuration;
105 [self setPulseState:gradient_button_cell::kPulsedOn];
109 case gradient_button_cell::kPulsingOff:
110 opacity -= elapsed / kAnimationHideDuration;
112 [self setPulseState:gradient_button_cell::kPulsedOff];
116 case gradient_button_cell::kPulsingContinuous:
117 opacity += elapsed / kAnimationContinuousCycleDuration * pulseMultiplier_;
120 pulseMultiplier_ *= -1.0;
121 } else if (opacity < 0.0) {
123 pulseMultiplier_ *= -1.0;
125 outerStrokeAlphaMult_ = opacity;
128 NOTREACHED() << "unknown pulse state";
131 // Update our control.
132 lastHoverUpdate_ = thisUpdate;
133 [self setHoverAlpha:opacity];
134 [[self controlView] setNeedsDisplay:YES];
136 // If our state needs it, keep going.
137 if ([self pulsing]) {
138 [self performSelector:_cmd withObject:nil afterDelay:0.02];
142 - (gradient_button_cell::PulseState)pulseState {
146 // Set the pulsing state. This can either set the pulse to on or off
147 // immediately (e.g. kPulsedOn, kPulsedOff) or initiate an animated
149 - (void)setPulseState:(gradient_button_cell::PulseState)pstate {
150 pulseState_ = pstate;
151 pulseMultiplier_ = 0.0;
152 [NSObject cancelPreviousPerformRequestsWithTarget:self];
153 lastHoverUpdate_ = [NSDate timeIntervalSinceReferenceDate];
156 case gradient_button_cell::kPulsedOn:
157 case gradient_button_cell::kPulsedOff:
158 outerStrokeAlphaMult_ = 1.0;
159 [self setHoverAlpha:((pulseState_ == gradient_button_cell::kPulsedOn) ?
161 [[self controlView] setNeedsDisplay:YES];
163 case gradient_button_cell::kPulsingOn:
164 case gradient_button_cell::kPulsingOff:
165 outerStrokeAlphaMult_ = 1.0;
166 // Set initial value then engage timer.
167 [self setHoverAlpha:((pulseState_ == gradient_button_cell::kPulsingOn) ?
169 [self performOnePulseStep];
171 case gradient_button_cell::kPulsingContinuous:
172 // Semantics of continuous pulsing are that we pulse independent
173 // of mouse position.
174 pulseMultiplier_ = 1.0;
175 [self performOnePulseStep];
183 - (void)safelyStopPulsing {
184 [NSObject cancelPreviousPerformRequestsWithTarget:self];
187 - (void)setIsContinuousPulsing:(BOOL)continuous {
188 if (!continuous && pulseState_ != gradient_button_cell::kPulsingContinuous)
191 [self setPulseState:gradient_button_cell::kPulsingContinuous];
193 [self setPulseState:(isMouseInside_ ? gradient_button_cell::kPulsedOn :
194 gradient_button_cell::kPulsedOff)];
198 - (BOOL)isContinuousPulsing {
199 return (pulseState_ == gradient_button_cell::kPulsingContinuous) ?
204 // If we are not continuously pulsing, perform a pulse animation to
205 // reflect our new state.
206 - (void)setMouseInside:(BOOL)flag animate:(BOOL)animated {
207 isMouseInside_ = flag;
208 if (pulseState_ != gradient_button_cell::kPulsingContinuous) {
210 [self setPulseState:(isMouseInside_ ? gradient_button_cell::kPulsingOn :
211 gradient_button_cell::kPulsingOff)];
213 [self setPulseState:(isMouseInside_ ? gradient_button_cell::kPulsedOn :
214 gradient_button_cell::kPulsedOff)];
220 - (void)adjustHoverValue {
221 NSTimeInterval thisUpdate = [NSDate timeIntervalSinceReferenceDate];
223 NSTimeInterval elapsed = thisUpdate - lastHoverUpdate_;
225 CGFloat opacity = [self hoverAlpha];
226 if (isMouseInside_) {
227 opacity += elapsed / kAnimationShowDuration;
229 opacity -= elapsed / kAnimationHideDuration;
232 if (!isMouseInside_ && opacity < 0) {
234 } else if (isMouseInside_ && opacity > 1) {
237 [self performSelector:_cmd withObject:nil afterDelay:0.02];
239 lastHoverUpdate_ = thisUpdate;
240 [self setHoverAlpha:opacity];
242 [[self controlView] setNeedsDisplay:YES];
245 - (void)setMouseInside:(BOOL)flag animate:(BOOL)animated {
246 isMouseInside_ = flag;
248 lastHoverUpdate_ = [NSDate timeIntervalSinceReferenceDate];
249 [self adjustHoverValue];
251 [NSObject cancelPreviousPerformRequestsWithTarget:self];
252 [self setHoverAlpha:flag ? 1.0 : 0.0];
254 [[self controlView] setNeedsDisplay:YES];
261 - (NSGradient*)gradientForHoverAlpha:(CGFloat)hoverAlpha
262 isThemed:(BOOL)themed {
263 CGFloat startAlpha = 0.6 + 0.3 * hoverAlpha;
264 CGFloat endAlpha = 0.333 * hoverAlpha;
267 startAlpha = 0.2 + 0.35 * hoverAlpha;
268 endAlpha = 0.333 * hoverAlpha;
271 NSColor* startColor =
272 [NSColor colorWithCalibratedWhite:1.0
275 [NSColor colorWithCalibratedWhite:1.0 - 0.15 * hoverAlpha
277 NSGradient* gradient = [[NSGradient alloc] initWithColorsAndLocations:
278 startColor, hoverAlpha * 0.33,
281 return [gradient autorelease];
286 pulseState_ = gradient_button_cell::kPulsedOff;
287 pulseMultiplier_ = 1.0;
288 outerStrokeAlphaMult_ = 1.0;
289 gradient_.reset([[self gradientForHoverAlpha:0.0 isThemed:NO] retain]);
292 - (void)setShouldTheme:(BOOL)shouldTheme {
293 shouldTheme_ = shouldTheme;
296 - (NSImage*)overlayImage {
297 return overlayImage_.get();
300 - (void)setOverlayImage:(NSImage*)image {
301 overlayImage_.reset([image retain]);
302 [[self controlView] setNeedsDisplay:YES];
305 - (NSBackgroundStyle)interiorBackgroundStyle {
306 // Never lower the interior, since that just leads to a weird shadow which can
307 // often interact badly with the theme.
308 return NSBackgroundStyleRaised;
311 - (void)mouseEntered:(NSEvent*)theEvent {
312 [self setMouseInside:YES animate:YES];
315 - (void)mouseExited:(NSEvent*)theEvent {
316 [self setMouseInside:NO animate:YES];
319 - (BOOL)isMouseInside {
320 return trackingArea_ && isMouseInside_;
323 // Since we have our own drawWithFrame:, we need to also have our own
324 // logic for determining when the mouse is inside for honoring this
326 - (void)setShowsBorderOnlyWhileMouseInside:(BOOL)showOnly {
327 [super setShowsBorderOnlyWhileMouseInside:showOnly];
329 [self updateTrackingAreas];
332 [[self controlView] removeTrackingArea:trackingArea_];
333 trackingArea_.reset(nil);
334 if (isMouseInside_) {
336 [[self controlView] setNeedsDisplay:YES];
342 // TODO(viettrungluu): clean up/reorganize.
343 - (void)drawBorderAndFillForTheme:(ui::ThemeProvider*)themeProvider
344 controlView:(NSView*)controlView
345 innerPath:(NSBezierPath*)innerPath
346 showClickedGradient:(BOOL)showClickedGradient
347 showHighlightGradient:(BOOL)showHighlightGradient
348 hoverAlpha:(CGFloat)hoverAlpha
350 cellFrame:(NSRect)cellFrame
351 defaultGradient:(NSGradient*)defaultGradient {
352 BOOL isFlatButton = [self showsBorderOnlyWhileMouseInside];
354 // For flat (unbordered when not hovered) buttons, never use the toolbar
355 // button background image, but the modest gradient used for themed buttons.
356 // To make things even more modest, scale the hover alpha down by 40 percent
358 NSColor* backgroundImageColor;
359 BOOL useThemeGradient;
361 backgroundImageColor = nil;
362 useThemeGradient = YES;
363 if (!showClickedGradient)
366 backgroundImageColor = nil;
368 themeProvider->HasCustomImage(IDR_THEME_BUTTON_BACKGROUND)) {
369 backgroundImageColor =
370 themeProvider->GetNSImageColorNamed(IDR_THEME_BUTTON_BACKGROUND);
372 useThemeGradient = backgroundImageColor ? YES : NO;
375 // The basic gradient shown inside; see above.
376 NSGradient* gradient;
377 if (hoverAlpha == 0 && !useThemeGradient) {
378 gradient = defaultGradient ? defaultGradient
381 gradient = [self gradientForHoverAlpha:hoverAlpha
382 isThemed:useThemeGradient];
385 // If we're drawing a background image, show that; else possibly show the
387 if (backgroundImageColor) {
388 [backgroundImageColor set];
389 // Set the phase to match window.
390 NSRect trueRect = [controlView convertRect:cellFrame toView:nil];
391 [[NSGraphicsContext currentContext]
392 cr_setPatternPhase:NSMakePoint(NSMinX(trueRect), NSMaxY(trueRect))
393 forView:controlView];
396 if (showClickedGradient) {
397 NSGradient* clickedGradient = nil;
399 [self tag] == kStandardButtonTypeWithLimitedClickFeedback) {
400 clickedGradient = gradient;
402 clickedGradient = themeProvider ? themeProvider->GetNSGradient(
404 ThemeProperties::GRADIENT_TOOLBAR_BUTTON_PRESSED :
405 ThemeProperties::GRADIENT_TOOLBAR_BUTTON_PRESSED_INACTIVE) :
408 [clickedGradient drawInBezierPath:innerPath angle:90.0];
412 // Visually indicate unclicked, enabled buttons.
413 if (!showClickedGradient && [self isEnabled]) {
414 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
417 // Draw the inner glow.
418 if (hoverAlpha > 0) {
419 [innerPath setLineWidth:2];
420 [[NSColor colorWithCalibratedWhite:1.0 alpha:0.2 * hoverAlpha] setStroke];
424 // Draw the top inner highlight.
425 NSAffineTransform* highlightTransform = [NSAffineTransform transform];
426 [highlightTransform translateXBy:1 yBy:1];
427 base::scoped_nsobject<NSBezierPath> highlightPath([innerPath copy]);
428 [highlightPath transformUsingAffineTransform:highlightTransform];
429 [[NSColor colorWithCalibratedWhite:1.0 alpha:0.2] setStroke];
430 [highlightPath stroke];
432 // Draw the gradient inside.
433 [gradient drawInBezierPath:innerPath angle:90.0];
436 // Don't draw anything else for disabled flat buttons.
437 if (isFlatButton && ![self isEnabled])
440 // Draw the outer stroke.
441 NSColor* strokeColor = nil;
442 if (showClickedGradient) {
443 strokeColor = [NSColor
444 colorWithCalibratedWhite:0.0
445 alpha:0.3 * outerStrokeAlphaMult_];
447 strokeColor = themeProvider ? themeProvider->GetNSColor(
448 active ? ThemeProperties::COLOR_TOOLBAR_BUTTON_STROKE :
449 ThemeProperties::COLOR_TOOLBAR_BUTTON_STROKE_INACTIVE) :
450 [NSColor colorWithCalibratedWhite:0.0
451 alpha:0.3 * outerStrokeAlphaMult_];
453 [strokeColor setStroke];
455 [innerPath setLineWidth:1];
459 // TODO(viettrungluu): clean this up.
461 - (void)getDrawParamsForFrame:(NSRect)cellFrame
462 inView:(NSView*)controlView
463 innerFrame:(NSRect*)returnInnerFrame
464 innerPath:(NSBezierPath**)returnInnerPath
465 clipPath:(NSBezierPath**)returnClipPath {
466 const CGFloat lineWidth = [controlView cr_lineWidth];
467 const CGFloat halfLineWidth = lineWidth / 2.0;
469 // Constants from Cole. Will kConstant them once the feedback loop
471 NSRect drawFrame = NSInsetRect(cellFrame, 1.5 * lineWidth, 1.5 * lineWidth);
472 NSRect innerFrame = NSInsetRect(cellFrame, lineWidth, lineWidth);
473 const CGFloat radius = 3;
475 ButtonType type = [[(NSControl*)controlView cell] tag];
477 case kMiddleButtonType:
478 drawFrame.size.width += 20;
479 innerFrame.size.width += 2;
481 case kRightButtonType:
482 drawFrame.origin.x -= 20;
483 innerFrame.origin.x -= 2;
485 case kLeftButtonType:
486 case kLeftButtonWithShadowType:
487 drawFrame.size.width += 20;
488 innerFrame.size.width += 2;
492 if (type == kLeftButtonWithShadowType)
493 innerFrame.size.width -= 1.0;
495 // Return results if |return...| not null.
496 if (returnInnerFrame)
497 *returnInnerFrame = innerFrame;
498 if (returnInnerPath) {
499 DCHECK(*returnInnerPath == nil);
500 *returnInnerPath = [NSBezierPath bezierPathWithRoundedRect:drawFrame
503 [*returnInnerPath setLineWidth:lineWidth];
505 if (returnClipPath) {
506 DCHECK(*returnClipPath == nil);
507 NSRect clipPathRect =
508 NSInsetRect(drawFrame, -halfLineWidth, -halfLineWidth);
509 *returnClipPath = [NSBezierPath
510 bezierPathWithRoundedRect:clipPathRect
511 xRadius:radius + halfLineWidth
512 yRadius:radius + halfLineWidth];
516 // TODO(viettrungluu): clean this up.
517 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
519 NSBezierPath* innerPath = nil;
520 [self getDrawParamsForFrame:cellFrame
522 innerFrame:&innerFrame
526 BOOL pressed = ([((NSControl*)[self controlView]) isEnabled] &&
527 [self isHighlighted]);
528 NSWindow* window = [controlView window];
529 ui::ThemeProvider* themeProvider = [window themeProvider];
530 BOOL active = [window isKeyWindow] || [window isMainWindow];
532 // Draw custom focus ring only if AppKit won't draw one automatically.
533 // The new focus ring APIs became available with 10.7, but did not get
534 // applied to buttons (only editable text fields) until 10.8.
535 BOOL shouldDrawFocusRing = base::mac::IsOSLionOrEarlier() &&
536 [self showsFirstResponder];
538 // Stroke the borders and appropriate fill gradient. If we're borderless, the
539 // only time we want to draw the inner gradient is if we're highlighted or if
540 // we're drawing the focus ring manually.
541 if (([self isBordered] && ![self showsBorderOnlyWhileMouseInside]) ||
543 [self isMouseInside] ||
544 [self isContinuousPulsing] ||
545 shouldDrawFocusRing) {
547 // When pulsing we want the bookmark to stand out a little more.
548 BOOL showClickedGradient = pressed ||
549 (pulseState_ == gradient_button_cell::kPulsingContinuous);
551 [self drawBorderAndFillForTheme:themeProvider
552 controlView:controlView
554 showClickedGradient:showClickedGradient
555 showHighlightGradient:[self isHighlighted]
556 hoverAlpha:[self hoverAlpha]
559 defaultGradient:nil];
562 // If this is the left side of a segmented button, draw a slight shadow.
563 ButtonType type = [[(NSControl*)controlView cell] tag];
564 if (type == kLeftButtonWithShadowType) {
565 const CGFloat lineWidth = [controlView cr_lineWidth];
566 NSRect borderRect, contentRect;
567 NSDivideRect(cellFrame, &borderRect, &contentRect, lineWidth, NSMaxXEdge);
568 NSColor* stroke = themeProvider ? themeProvider->GetNSColor(
569 active ? ThemeProperties::COLOR_TOOLBAR_BUTTON_STROKE :
570 ThemeProperties::COLOR_TOOLBAR_BUTTON_STROKE_INACTIVE) :
571 [NSColor blackColor];
573 [[stroke colorWithAlphaComponent:0.2] set];
574 NSRectFillUsingOperation(NSInsetRect(borderRect, 0, 2),
575 NSCompositeSourceOver);
577 [self drawInteriorWithFrame:innerFrame inView:controlView];
579 if (shouldDrawFocusRing) {
580 gfx::ScopedNSGraphicsContextSaveGState scoped_state;
581 const CGFloat lineWidth = [controlView cr_lineWidth];
582 // insetX = 1.0 is used for the drawing of blue highlight so that this
583 // highlight won't be too near the bookmark toolbar itself, in case we
584 // draw bookmark buttons in bookmark toolbar.
585 rect_path_utils::FrameRectWithInset(rect_path_utils::RoundedCornerAll,
586 NSInsetRect(cellFrame, 0, lineWidth),
590 lineWidth * 2, // lineWidth
592 cr_keyboardFocusIndicatorColor]);
596 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
597 const CGFloat lineWidth = [controlView cr_lineWidth];
600 BOOL isTemplate = [[self image] isTemplate];
602 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
604 CGContextRef context =
605 (CGContextRef)([[NSGraphicsContext currentContext] graphicsPort]);
607 ThemeService* themeProvider = static_cast<ThemeService*>(
608 [[controlView window] themeProvider]);
609 NSColor* color = themeProvider ?
610 themeProvider->GetNSColorTint(ThemeProperties::TINT_BUTTONS) :
611 [NSColor blackColor];
613 if (isTemplate && themeProvider && themeProvider->UsingDefaultTheme()) {
614 base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
615 [shadow.get() setShadowColor:themeProvider->GetNSColor(
616 ThemeProperties::COLOR_TOOLBAR_BEZEL)];
617 [shadow.get() setShadowOffset:NSMakeSize(0.0, -lineWidth)];
618 [shadow setShadowBlurRadius:lineWidth];
622 CGContextBeginTransparencyLayer(context, 0);
623 NSRect imageRect = NSZeroRect;
624 imageRect.size = [[self image] size];
625 NSRect drawRect = [self imageRectForBounds:cellFrame];
626 [[self image] drawInRect:drawRect
628 operation:NSCompositeSourceOver
629 fraction:[self isEnabled] ? 1.0 : 0.5
632 if (isTemplate && color) {
634 NSRectFillUsingOperation(cellFrame, NSCompositeSourceAtop);
636 CGContextEndTransparencyLayer(context);
638 // NSCell draws these off-center for some reason, probably because of the
639 // positioning of the control in the xib.
640 [super drawInteriorWithFrame:NSOffsetRect(cellFrame, 0, lineWidth)
645 NSRect imageRect = NSZeroRect;
646 imageRect.size = [overlayImage_ size];
647 [overlayImage_ drawInRect:[self imageRectForBounds:cellFrame]
649 operation:NSCompositeSourceOver
650 fraction:[self isEnabled] ? 1.0 : 0.5
656 - (int)verticalTextOffset {
660 // Overriden from NSButtonCell so we can display a nice fadeout effect for
661 // button titles that overflow.
662 // This method is copied in the most part from GTMFadeTruncatingTextFieldCell,
663 // the only difference is that here we draw the text ourselves rather than
664 // calling the super to do the work.
665 // We can't use GTMFadeTruncatingTextFieldCell because there's no easy way to
666 // get it to work with NSButtonCell.
667 // TODO(jeremy): Move this to GTM.
668 - (NSRect)drawTitle:(NSAttributedString*)title
669 withFrame:(NSRect)cellFrame
670 inView:(NSView*)controlView {
671 NSSize size = [title size];
673 // Empirically, Cocoa will draw an extra 2 pixels past NSWidth(cellFrame)
674 // before it clips the text.
675 const CGFloat kOverflowBeforeClip = 2;
677 if (std::floor(size.width) <= (NSWidth(cellFrame) + kOverflowBeforeClip)) {
678 cellFrame.origin.y += ([self verticalTextOffset] - 1);
682 // Gradient is about twice our line height long.
683 CGFloat gradientWidth = MIN(size.height * 2, NSWidth(cellFrame) / 4);
685 NSRect solidPart, gradientPart;
686 NSDivideRect(cellFrame, &gradientPart, &solidPart, gradientWidth, NSMaxXEdge);
688 // Draw non-gradient part without transparency layer, as light text on a dark
689 // background looks bad with a gradient layer.
690 NSPoint textOffset = NSZeroPoint;
692 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
694 [NSBezierPath clipRect:solidPart];
696 // 11 is the magic number needed to make this match the native
697 // NSButtonCell's label display.
698 CGFloat textLeft = [[self image] size].width + 11;
700 // For some reason, the height of cellFrame as passed in is totally bogus.
701 // For vertical centering purposes, we need the bounds of the containing
703 NSRect buttonFrame = [[self controlView] frame];
705 // Call the vertical offset to match native NSButtonCell's version.
706 textOffset = NSMakePoint(textLeft,
707 (NSHeight(buttonFrame) - size.height) / 2 +
708 [self verticalTextOffset]);
709 [title drawAtPoint:textOffset];
715 // Draw the gradient part with a transparency layer. This makes the text look
716 // suboptimal, but since it fades out, that's ok.
717 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
718 [NSBezierPath clipRect:gradientPart];
719 CGContextRef context = static_cast<CGContextRef>(
720 [[NSGraphicsContext currentContext] graphicsPort]);
721 CGContextBeginTransparencyLayerWithRect(context,
722 NSRectToCGRect(gradientPart), 0);
723 [title drawAtPoint:textOffset];
725 NSColor *color = [NSColor textColor];
726 NSColor *alphaColor = [color colorWithAlphaComponent:0.0];
727 NSGradient *mask = [[NSGradient alloc] initWithStartingColor:color
728 endingColor:alphaColor];
730 // Draw the gradient mask
731 CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
732 [mask drawFromPoint:NSMakePoint(NSMaxX(cellFrame) - gradientWidth,
734 toPoint:NSMakePoint(NSMaxX(cellFrame),
736 options:NSGradientDrawsBeforeStartingLocation];
738 CGContextEndTransparencyLayer(context);
743 - (NSBezierPath*)clipPathForFrame:(NSRect)cellFrame
744 inView:(NSView*)controlView {
745 NSBezierPath* boundingPath = nil;
746 [self getDrawParamsForFrame:cellFrame
750 clipPath:&boundingPath];
754 - (void)resetCursorRect:(NSRect)cellFrame inView:(NSView*)controlView {
755 [super resetCursorRect:cellFrame inView:controlView];
757 [self updateTrackingAreas];
760 - (BOOL)isMouseReallyInside {
761 BOOL mouseInView = NO;
762 NSView* controlView = [self controlView];
763 NSWindow* window = [controlView window];
764 NSRect bounds = [controlView bounds];
766 NSPoint mousePoint = [window mouseLocationOutsideOfEventStream];
767 mousePoint = [controlView convertPoint:mousePoint fromView:nil];
768 mouseInView = [controlView mouse:mousePoint inRect:bounds];
773 - (void)updateTrackingAreas {
774 NSView* controlView = [self controlView];
775 BOOL mouseInView = [self isMouseReallyInside];
777 if (trackingArea_.get())
778 [controlView removeTrackingArea:trackingArea_];
780 NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
781 NSTrackingActiveInActiveApp;
783 options |= NSTrackingAssumeInside;
785 trackingArea_.reset([[NSTrackingArea alloc]
786 initWithRect:[controlView bounds]
790 if (isMouseInside_ != mouseInView) {
791 [self setMouseInside:mouseInView animate:NO];
792 [controlView setNeedsDisplay:YES];