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 #import "base/mac/scoped_nsobject.h"
11 #import "chrome/browser/themes/theme_properties.h"
12 #import "chrome/browser/themes/theme_service.h"
13 #import "chrome/browser/ui/cocoa/rect_path_utils.h"
14 #import "chrome/browser/ui/cocoa/themed_window.h"
15 #include "grit/theme_resources.h"
16 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSColor+Luminance.h"
17 #import "ui/base/cocoa/nsgraphics_context_additions.h"
18 #import "ui/base/cocoa/nsview_additions.h"
19 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
21 @interface GradientButtonCell (Private)
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;
37 - (void)updateTrackingAreas;
42 static const NSTimeInterval kAnimationShowDuration = 0.2;
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
51 static const NSTimeInterval kAnimationHideDuration = 0.4;
53 static const NSTimeInterval kAnimationContinuousCycleDuration = 0.4;
55 @implementation GradientButtonCell
57 @synthesize hoverAlpha = hoverAlpha_;
59 // For nib instantiations
60 - (id)initWithCoder:(NSCoder*)decoder {
61 if ((self = [super initWithCoder:decoder])) {
67 // For programmatic instantiations
68 - (id)initTextCell:(NSString*)string {
69 if ((self = [super initTextCell:string])) {
77 [[self controlView] removeTrackingArea:trackingArea_];
78 trackingArea_.reset();
83 // Return YES if we are pulsing (towards another state or continuously).
85 if ((pulseState_ == gradient_button_cell::kPulsingOn) ||
86 (pulseState_ == gradient_button_cell::kPulsingOff) ||
87 (pulseState_ == gradient_button_cell::kPulsingContinuous))
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];
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;
104 [self setPulseState:gradient_button_cell::kPulsedOn];
108 case gradient_button_cell::kPulsingOff:
109 opacity -= elapsed / kAnimationHideDuration;
111 [self setPulseState:gradient_button_cell::kPulsedOff];
115 case gradient_button_cell::kPulsingContinuous:
116 opacity += elapsed / kAnimationContinuousCycleDuration * pulseMultiplier_;
119 pulseMultiplier_ *= -1.0;
120 } else if (opacity < 0.0) {
122 pulseMultiplier_ *= -1.0;
124 outerStrokeAlphaMult_ = opacity;
127 NOTREACHED() << "unknown pulse state";
130 // Update our control.
131 lastHoverUpdate_ = thisUpdate;
132 [self setHoverAlpha:opacity];
133 [[self controlView] setNeedsDisplay:YES];
135 // If our state needs it, keep going.
136 if ([self pulsing]) {
137 [self performSelector:_cmd withObject:nil afterDelay:0.02];
141 - (gradient_button_cell::PulseState)pulseState {
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
148 - (void)setPulseState:(gradient_button_cell::PulseState)pstate {
149 pulseState_ = pstate;
150 pulseMultiplier_ = 0.0;
151 [NSObject cancelPreviousPerformRequestsWithTarget:self];
152 lastHoverUpdate_ = [NSDate timeIntervalSinceReferenceDate];
155 case gradient_button_cell::kPulsedOn:
156 case gradient_button_cell::kPulsedOff:
157 outerStrokeAlphaMult_ = 1.0;
158 [self setHoverAlpha:((pulseState_ == gradient_button_cell::kPulsedOn) ?
160 [[self controlView] setNeedsDisplay:YES];
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) ?
168 [self performOnePulseStep];
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];
182 - (void)safelyStopPulsing {
183 [NSObject cancelPreviousPerformRequestsWithTarget:self];
186 - (void)setIsContinuousPulsing:(BOOL)continuous {
187 if (!continuous && pulseState_ != gradient_button_cell::kPulsingContinuous)
190 [self setPulseState:gradient_button_cell::kPulsingContinuous];
192 [self setPulseState:(isMouseInside_ ? gradient_button_cell::kPulsedOn :
193 gradient_button_cell::kPulsedOff)];
197 - (BOOL)isContinuousPulsing {
198 return (pulseState_ == gradient_button_cell::kPulsingContinuous) ?
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) {
209 [self setPulseState:(isMouseInside_ ? gradient_button_cell::kPulsingOn :
210 gradient_button_cell::kPulsingOff)];
212 [self setPulseState:(isMouseInside_ ? gradient_button_cell::kPulsedOn :
213 gradient_button_cell::kPulsedOff)];
219 - (void)adjustHoverValue {
220 NSTimeInterval thisUpdate = [NSDate timeIntervalSinceReferenceDate];
222 NSTimeInterval elapsed = thisUpdate - lastHoverUpdate_;
224 CGFloat opacity = [self hoverAlpha];
225 if (isMouseInside_) {
226 opacity += elapsed / kAnimationShowDuration;
228 opacity -= elapsed / kAnimationHideDuration;
231 if (!isMouseInside_ && opacity < 0) {
233 } else if (isMouseInside_ && opacity > 1) {
236 [self performSelector:_cmd withObject:nil afterDelay:0.02];
238 lastHoverUpdate_ = thisUpdate;
239 [self setHoverAlpha:opacity];
241 [[self controlView] setNeedsDisplay:YES];
244 - (void)setMouseInside:(BOOL)flag animate:(BOOL)animated {
245 isMouseInside_ = flag;
247 lastHoverUpdate_ = [NSDate timeIntervalSinceReferenceDate];
248 [self adjustHoverValue];
250 [NSObject cancelPreviousPerformRequestsWithTarget:self];
251 [self setHoverAlpha:flag ? 1.0 : 0.0];
253 [[self controlView] setNeedsDisplay:YES];
260 - (NSGradient*)gradientForHoverAlpha:(CGFloat)hoverAlpha
261 isThemed:(BOOL)themed {
262 CGFloat startAlpha = 0.6 + 0.3 * hoverAlpha;
263 CGFloat endAlpha = 0.333 * hoverAlpha;
266 startAlpha = 0.2 + 0.35 * hoverAlpha;
267 endAlpha = 0.333 * hoverAlpha;
270 NSColor* startColor =
271 [NSColor colorWithCalibratedWhite:1.0
274 [NSColor colorWithCalibratedWhite:1.0 - 0.15 * hoverAlpha
276 NSGradient* gradient = [[NSGradient alloc] initWithColorsAndLocations:
277 startColor, hoverAlpha * 0.33,
280 return [gradient autorelease];
285 pulseState_ = gradient_button_cell::kPulsedOff;
286 pulseMultiplier_ = 1.0;
287 outerStrokeAlphaMult_ = 1.0;
288 gradient_.reset([[self gradientForHoverAlpha:0.0 isThemed:NO] retain]);
291 - (void)setShouldTheme:(BOOL)shouldTheme {
292 shouldTheme_ = shouldTheme;
295 - (NSImage*)overlayImage {
296 return overlayImage_.get();
299 - (void)setOverlayImage:(NSImage*)image {
300 overlayImage_.reset([image retain]);
301 [[self controlView] setNeedsDisplay:YES];
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;
310 - (void)mouseEntered:(NSEvent*)theEvent {
311 [self setMouseInside:YES animate:YES];
314 - (void)mouseExited:(NSEvent*)theEvent {
315 [self setMouseInside:NO animate:YES];
318 - (BOOL)isMouseInside {
319 return trackingArea_ && isMouseInside_;
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
325 - (void)setShowsBorderOnlyWhileMouseInside:(BOOL)showOnly {
326 [super setShowsBorderOnlyWhileMouseInside:showOnly];
328 [self updateTrackingAreas];
331 [[self controlView] removeTrackingArea:trackingArea_];
332 trackingArea_.reset(nil);
333 if (isMouseInside_) {
335 [[self controlView] setNeedsDisplay:YES];
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
349 cellFrame:(NSRect)cellFrame
350 defaultGradient:(NSGradient*)defaultGradient {
351 BOOL isFlatButton = [self showsBorderOnlyWhileMouseInside];
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
357 NSColor* backgroundImageColor;
358 BOOL useThemeGradient;
360 backgroundImageColor = nil;
361 useThemeGradient = YES;
362 if (!showClickedGradient)
365 backgroundImageColor = nil;
367 themeProvider->HasCustomImage(IDR_THEME_BUTTON_BACKGROUND)) {
368 backgroundImageColor =
369 themeProvider->GetNSImageColorNamed(IDR_THEME_BUTTON_BACKGROUND);
371 useThemeGradient = backgroundImageColor ? YES : NO;
374 // The basic gradient shown inside; see above.
375 NSGradient* gradient;
376 if (hoverAlpha == 0 && !useThemeGradient) {
377 gradient = defaultGradient ? defaultGradient
380 gradient = [self gradientForHoverAlpha:hoverAlpha
381 isThemed:useThemeGradient];
384 // If we're drawing a background image, show that; else possibly show the
386 if (backgroundImageColor) {
387 [backgroundImageColor set];
388 // Set the phase to match window.
389 NSRect trueRect = [controlView convertRect:cellFrame toView:nil];
390 [[NSGraphicsContext currentContext]
391 cr_setPatternPhase:NSMakePoint(NSMinX(trueRect), NSMaxY(trueRect))
392 forView:controlView];
395 if (showClickedGradient) {
396 NSGradient* clickedGradient = nil;
398 [self tag] == kStandardButtonTypeWithLimitedClickFeedback) {
399 clickedGradient = gradient;
401 clickedGradient = themeProvider ? themeProvider->GetNSGradient(
403 ThemeProperties::GRADIENT_TOOLBAR_BUTTON_PRESSED :
404 ThemeProperties::GRADIENT_TOOLBAR_BUTTON_PRESSED_INACTIVE) :
407 [clickedGradient drawInBezierPath:innerPath angle:90.0];
411 // Visually indicate unclicked, enabled buttons.
412 if (!showClickedGradient && [self isEnabled]) {
413 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
416 // Draw the inner glow.
417 if (hoverAlpha > 0) {
418 [innerPath setLineWidth:2];
419 [[NSColor colorWithCalibratedWhite:1.0 alpha:0.2 * hoverAlpha] setStroke];
423 // Draw the top inner highlight.
424 NSAffineTransform* highlightTransform = [NSAffineTransform transform];
425 [highlightTransform translateXBy:1 yBy:1];
426 base::scoped_nsobject<NSBezierPath> highlightPath([innerPath copy]);
427 [highlightPath transformUsingAffineTransform:highlightTransform];
428 [[NSColor colorWithCalibratedWhite:1.0 alpha:0.2] setStroke];
429 [highlightPath stroke];
431 // Draw the gradient inside.
432 [gradient drawInBezierPath:innerPath angle:90.0];
435 // Don't draw anything else for disabled flat buttons.
436 if (isFlatButton && ![self isEnabled])
439 // Draw the outer stroke.
440 NSColor* strokeColor = nil;
441 if (showClickedGradient) {
442 strokeColor = [NSColor
443 colorWithCalibratedWhite:0.0
444 alpha:0.3 * outerStrokeAlphaMult_];
446 strokeColor = themeProvider ? themeProvider->GetNSColor(
447 active ? ThemeProperties::COLOR_TOOLBAR_BUTTON_STROKE :
448 ThemeProperties::COLOR_TOOLBAR_BUTTON_STROKE_INACTIVE) :
449 [NSColor colorWithCalibratedWhite:0.0
450 alpha:0.3 * outerStrokeAlphaMult_];
452 [strokeColor setStroke];
454 [innerPath setLineWidth:1];
458 // TODO(viettrungluu): clean this up.
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;
468 // Constants from Cole. Will kConstant them once the feedback loop
470 NSRect drawFrame = NSInsetRect(cellFrame, 1.5 * lineWidth, 1.5 * lineWidth);
471 NSRect innerFrame = NSInsetRect(cellFrame, lineWidth, lineWidth);
472 const CGFloat radius = 3;
474 ButtonType type = [[(NSControl*)controlView cell] tag];
476 case kMiddleButtonType:
477 drawFrame.size.width += 20;
478 innerFrame.size.width += 2;
480 case kRightButtonType:
481 drawFrame.origin.x -= 20;
482 innerFrame.origin.x -= 2;
484 case kLeftButtonType:
485 case kLeftButtonWithShadowType:
486 drawFrame.size.width += 20;
487 innerFrame.size.width += 2;
491 if (type == kLeftButtonWithShadowType)
492 innerFrame.size.width -= 1.0;
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
502 [*returnInnerPath setLineWidth:lineWidth];
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];
515 // TODO(viettrungluu): clean this up.
516 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
518 NSBezierPath* innerPath = nil;
519 [self getDrawParamsForFrame:cellFrame
521 innerFrame:&innerFrame
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];
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]) ||
536 [self isMouseInside] ||
537 [self isContinuousPulsing] ||
538 [self showsFirstResponder]) {
540 // When pulsing we want the bookmark to stand out a little more.
541 BOOL showClickedGradient = pressed ||
542 (pulseState_ == gradient_button_cell::kPulsingContinuous);
544 [self drawBorderAndFillForTheme:themeProvider
545 controlView:controlView
547 showClickedGradient:showClickedGradient
548 showHighlightGradient:[self isHighlighted]
549 hoverAlpha:[self hoverAlpha]
552 defaultGradient:nil];
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(
562 active ? ThemeProperties::COLOR_TOOLBAR_BUTTON_STROKE :
563 ThemeProperties::COLOR_TOOLBAR_BUTTON_STROKE_INACTIVE) :
564 [NSColor blackColor];
566 [[stroke colorWithAlphaComponent:0.2] set];
567 NSRectFillUsingOperation(NSInsetRect(borderRect, 0, 2),
568 NSCompositeSourceOver);
570 [self drawInteriorWithFrame:innerFrame inView:controlView];
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),
584 lineWidth * 2, // lineWidth
586 cr_keyboardFocusIndicatorColor]);
590 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
591 const CGFloat lineWidth = [controlView cr_lineWidth];
594 BOOL isTemplate = [[self image] isTemplate];
596 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
598 CGContextRef context =
599 (CGContextRef)([[NSGraphicsContext currentContext] graphicsPort]);
601 ThemeService* themeProvider = static_cast<ThemeService*>(
602 [[controlView window] themeProvider]);
603 NSColor* color = themeProvider ?
604 themeProvider->GetNSColorTint(ThemeProperties::TINT_BUTTONS) :
605 [NSColor blackColor];
607 if (isTemplate && themeProvider && themeProvider->UsingDefaultTheme()) {
608 base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
609 [shadow.get() setShadowColor:themeProvider->GetNSColor(
610 ThemeProperties::COLOR_TOOLBAR_BEZEL)];
611 [shadow.get() setShadowOffset:NSMakeSize(0.0, -lineWidth)];
612 [shadow setShadowBlurRadius:lineWidth];
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
622 operation:NSCompositeSourceOver
623 fraction:[self isEnabled] ? 1.0 : 0.5
626 if (isTemplate && color) {
628 NSRectFillUsingOperation(cellFrame, NSCompositeSourceAtop);
630 CGContextEndTransparencyLayer(context);
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)
639 NSRect imageRect = NSZeroRect;
640 imageRect.size = [overlayImage_ size];
641 [overlayImage_ drawInRect:[self imageRectForBounds:cellFrame]
643 operation:NSCompositeSourceOver
644 fraction:[self isEnabled] ? 1.0 : 0.5
650 - (int)verticalTextOffset {
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];
667 // Empirically, Cocoa will draw an extra 2 pixels past NSWidth(cellFrame)
668 // before it clips the text.
669 const CGFloat kOverflowBeforeClip = 2;
671 if (std::floor(size.width) <= (NSWidth(cellFrame) + kOverflowBeforeClip)) {
672 cellFrame.origin.y += ([self verticalTextOffset] - 1);
676 // Gradient is about twice our line height long.
677 CGFloat gradientWidth = MIN(size.height * 2, NSWidth(cellFrame) / 4);
679 NSRect solidPart, gradientPart;
680 NSDivideRect(cellFrame, &gradientPart, &solidPart, gradientWidth, NSMaxXEdge);
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;
686 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
688 [NSBezierPath clipRect:solidPart];
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;
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
697 NSRect buttonFrame = [[self controlView] frame];
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];
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];
719 NSColor *color = [NSColor textColor];
720 NSColor *alphaColor = [color colorWithAlphaComponent:0.0];
721 NSGradient *mask = [[NSGradient alloc] initWithStartingColor:color
722 endingColor:alphaColor];
724 // Draw the gradient mask
725 CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
726 [mask drawFromPoint:NSMakePoint(NSMaxX(cellFrame) - gradientWidth,
728 toPoint:NSMakePoint(NSMaxX(cellFrame),
730 options:NSGradientDrawsBeforeStartingLocation];
732 CGContextEndTransparencyLayer(context);
737 - (NSBezierPath*)clipPathForFrame:(NSRect)cellFrame
738 inView:(NSView*)controlView {
739 NSBezierPath* boundingPath = nil;
740 [self getDrawParamsForFrame:cellFrame
744 clipPath:&boundingPath];
748 - (void)resetCursorRect:(NSRect)cellFrame inView:(NSView*)controlView {
749 [super resetCursorRect:cellFrame inView:controlView];
751 [self updateTrackingAreas];
754 - (BOOL)isMouseReallyInside {
755 BOOL mouseInView = NO;
756 NSView* controlView = [self controlView];
757 NSWindow* window = [controlView window];
758 NSRect bounds = [controlView bounds];
760 NSPoint mousePoint = [window mouseLocationOutsideOfEventStream];
761 mousePoint = [controlView convertPoint:mousePoint fromView:nil];
762 mouseInView = [controlView mouse:mousePoint inRect:bounds];
767 - (void)updateTrackingAreas {
768 NSView* controlView = [self controlView];
769 BOOL mouseInView = [self isMouseReallyInside];
771 if (trackingArea_.get())
772 [controlView removeTrackingArea:trackingArea_];
774 NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
775 NSTrackingActiveInActiveApp;
777 options |= NSTrackingAssumeInside;
779 trackingArea_.reset([[NSTrackingArea alloc]
780 initWithRect:[controlView bounds]
784 if (isMouseInside_ != mouseInView) {
785 [self setMouseInside:mouseInView animate:NO];
786 [controlView setNeedsDisplay:YES];