summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/cocoa/gradient_button_cell.mm
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/ui/cocoa/gradient_button_cell.mm')
-rw-r--r--chrome/browser/ui/cocoa/gradient_button_cell.mm719
1 files changed, 719 insertions, 0 deletions
diff --git a/chrome/browser/ui/cocoa/gradient_button_cell.mm b/chrome/browser/ui/cocoa/gradient_button_cell.mm
new file mode 100644
index 0000000..205e139
--- /dev/null
+++ b/chrome/browser/ui/cocoa/gradient_button_cell.mm
@@ -0,0 +1,719 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/cocoa/gradient_button_cell.h"
+
+#include "base/logging.h"
+#import "base/scoped_nsobject.h"
+#import "chrome/browser/themes/browser_theme_provider.h"
+#import "chrome/browser/ui/cocoa/image_utils.h"
+#import "chrome/browser/ui/cocoa/themed_window.h"
+#include "grit/theme_resources.h"
+#import "third_party/GTM/AppKit/GTMNSColor+Luminance.h"
+
+@interface GradientButtonCell (Private)
+- (void)sharedInit;
+
+// Get drawing parameters for a given cell frame in a given view. The inner
+// frame is the one required by |-drawInteriorWithFrame:inView:|. The inner and
+// outer paths are the ones required by |-drawBorderAndFillForTheme:...|. The
+// outer path also gives the area in which to clip. Any of the |return...|
+// arguments may be NULL (in which case the given parameter won't be returned).
+// If |returnInnerPath| or |returnOuterPath|, |*returnInnerPath| or
+// |*returnOuterPath| should be nil, respectively.
+- (void)getDrawParamsForFrame:(NSRect)cellFrame
+ inView:(NSView*)controlView
+ innerFrame:(NSRect*)returnInnerFrame
+ innerPath:(NSBezierPath**)returnInnerPath
+ clipPath:(NSBezierPath**)returnClipPath;
+
+
+@end
+
+
+static const NSTimeInterval kAnimationShowDuration = 0.2;
+
+// Note: due to a bug (?), drawWithFrame:inView: does not call
+// drawBorderAndFillForTheme::::: unless the mouse is inside. The net
+// effect is that our "fade out" when the mouse leaves becaumes
+// instantaneous. When I "fixed" it things looked horrible; the
+// hover-overed bookmark button would stay highlit for 0.4 seconds
+// which felt like latency/lag. I'm leaving the "bug" in place for
+// now so we don't suck. -jrg
+static const NSTimeInterval kAnimationHideDuration = 0.4;
+
+static const NSTimeInterval kAnimationContinuousCycleDuration = 0.4;
+
+@implementation GradientButtonCell
+
+@synthesize hoverAlpha = hoverAlpha_;
+
+// For nib instantiations
+- (id)initWithCoder:(NSCoder*)decoder {
+ if ((self = [super initWithCoder:decoder])) {
+ [self sharedInit];
+ }
+ return self;
+}
+
+// For programmatic instantiations
+- (id)initTextCell:(NSString*)string {
+ if ((self = [super initTextCell:string])) {
+ [self sharedInit];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ if (trackingArea_) {
+ [[self controlView] removeTrackingArea:trackingArea_];
+ trackingArea_.reset();
+ }
+ [super dealloc];
+}
+
+// Return YES if we are pulsing (towards another state or continuously).
+- (BOOL)pulsing {
+ if ((pulseState_ == gradient_button_cell::kPulsingOn) ||
+ (pulseState_ == gradient_button_cell::kPulsingOff) ||
+ (pulseState_ == gradient_button_cell::kPulsingContinuous))
+ return YES;
+ return NO;
+}
+
+// Perform one pulse step when animating a pulse.
+- (void)performOnePulseStep {
+ NSTimeInterval thisUpdate = [NSDate timeIntervalSinceReferenceDate];
+ NSTimeInterval elapsed = thisUpdate - lastHoverUpdate_;
+ CGFloat opacity = [self hoverAlpha];
+
+ // Update opacity based on state.
+ // Adjust state if we have finished.
+ switch (pulseState_) {
+ case gradient_button_cell::kPulsingOn:
+ opacity += elapsed / kAnimationShowDuration;
+ if (opacity > 1.0) {
+ [self setPulseState:gradient_button_cell::kPulsedOn];
+ return;
+ }
+ break;
+ case gradient_button_cell::kPulsingOff:
+ opacity -= elapsed / kAnimationHideDuration;
+ if (opacity < 0.0) {
+ [self setPulseState:gradient_button_cell::kPulsedOff];
+ return;
+ }
+ break;
+ case gradient_button_cell::kPulsingContinuous:
+ opacity += elapsed / kAnimationContinuousCycleDuration * pulseMultiplier_;
+ if (opacity > 1.0) {
+ opacity = 1.0;
+ pulseMultiplier_ *= -1.0;
+ } else if (opacity < 0.0) {
+ opacity = 0.0;
+ pulseMultiplier_ *= -1.0;
+ }
+ outerStrokeAlphaMult_ = opacity;
+ break;
+ default:
+ NOTREACHED() << "unknown pulse state";
+ }
+
+ // Update our control.
+ lastHoverUpdate_ = thisUpdate;
+ [self setHoverAlpha:opacity];
+ [[self controlView] setNeedsDisplay:YES];
+
+ // If our state needs it, keep going.
+ if ([self pulsing]) {
+ [self performSelector:_cmd withObject:nil afterDelay:0.02];
+ }
+}
+
+- (gradient_button_cell::PulseState)pulseState {
+ return pulseState_;
+}
+
+// Set the pulsing state. This can either set the pulse to on or off
+// immediately (e.g. kPulsedOn, kPulsedOff) or initiate an animated
+// state change.
+- (void)setPulseState:(gradient_button_cell::PulseState)pstate {
+ pulseState_ = pstate;
+ pulseMultiplier_ = 0.0;
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
+ lastHoverUpdate_ = [NSDate timeIntervalSinceReferenceDate];
+
+ switch (pstate) {
+ case gradient_button_cell::kPulsedOn:
+ case gradient_button_cell::kPulsedOff:
+ outerStrokeAlphaMult_ = 1.0;
+ [self setHoverAlpha:((pulseState_ == gradient_button_cell::kPulsedOn) ?
+ 1.0 : 0.0)];
+ [[self controlView] setNeedsDisplay:YES];
+ break;
+ case gradient_button_cell::kPulsingOn:
+ case gradient_button_cell::kPulsingOff:
+ outerStrokeAlphaMult_ = 1.0;
+ // Set initial value then engage timer.
+ [self setHoverAlpha:((pulseState_ == gradient_button_cell::kPulsingOn) ?
+ 0.0 : 1.0)];
+ [self performOnePulseStep];
+ break;
+ case gradient_button_cell::kPulsingContinuous:
+ // Semantics of continuous pulsing are that we pulse independent
+ // of mouse position.
+ pulseMultiplier_ = 1.0;
+ [self performOnePulseStep];
+ break;
+ default:
+ CHECK(0);
+ break;
+ }
+}
+
+- (void)safelyStopPulsing {
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
+}
+
+- (void)setIsContinuousPulsing:(BOOL)continuous {
+ if (!continuous && pulseState_ != gradient_button_cell::kPulsingContinuous)
+ return;
+ if (continuous) {
+ [self setPulseState:gradient_button_cell::kPulsingContinuous];
+ } else {
+ [self setPulseState:(isMouseInside_ ? gradient_button_cell::kPulsedOn :
+ gradient_button_cell::kPulsedOff)];
+ }
+}
+
+- (BOOL)isContinuousPulsing {
+ return (pulseState_ == gradient_button_cell::kPulsingContinuous) ?
+ YES : NO;
+}
+
+#if 1
+// If we are not continuously pulsing, perform a pulse animation to
+// reflect our new state.
+- (void)setMouseInside:(BOOL)flag animate:(BOOL)animated {
+ isMouseInside_ = flag;
+ if (pulseState_ != gradient_button_cell::kPulsingContinuous) {
+ if (animated) {
+ [self setPulseState:(isMouseInside_ ? gradient_button_cell::kPulsingOn :
+ gradient_button_cell::kPulsingOff)];
+ } else {
+ [self setPulseState:(isMouseInside_ ? gradient_button_cell::kPulsedOn :
+ gradient_button_cell::kPulsedOff)];
+ }
+ }
+}
+#else
+
+- (void)adjustHoverValue {
+ NSTimeInterval thisUpdate = [NSDate timeIntervalSinceReferenceDate];
+
+ NSTimeInterval elapsed = thisUpdate - lastHoverUpdate_;
+
+ CGFloat opacity = [self hoverAlpha];
+ if (isMouseInside_) {
+ opacity += elapsed / kAnimationShowDuration;
+ } else {
+ opacity -= elapsed / kAnimationHideDuration;
+ }
+
+ if (!isMouseInside_ && opacity < 0) {
+ opacity = 0;
+ } else if (isMouseInside_ && opacity > 1) {
+ opacity = 1;
+ } else {
+ [self performSelector:_cmd withObject:nil afterDelay:0.02];
+ }
+ lastHoverUpdate_ = thisUpdate;
+ [self setHoverAlpha:opacity];
+
+ [[self controlView] setNeedsDisplay:YES];
+}
+
+- (void)setMouseInside:(BOOL)flag animate:(BOOL)animated {
+ isMouseInside_ = flag;
+ if (animated) {
+ lastHoverUpdate_ = [NSDate timeIntervalSinceReferenceDate];
+ [self adjustHoverValue];
+ } else {
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
+ [self setHoverAlpha:flag ? 1.0 : 0.0];
+ }
+ [[self controlView] setNeedsDisplay:YES];
+}
+
+
+
+#endif
+
+- (NSGradient*)gradientForHoverAlpha:(CGFloat)hoverAlpha
+ isThemed:(BOOL)themed {
+ CGFloat startAlpha = 0.6 + 0.3 * hoverAlpha;
+ CGFloat endAlpha = 0.333 * hoverAlpha;
+
+ if (themed) {
+ startAlpha = 0.2 + 0.35 * hoverAlpha;
+ endAlpha = 0.333 * hoverAlpha;
+ }
+
+ NSColor* startColor =
+ [NSColor colorWithCalibratedWhite:1.0
+ alpha:startAlpha];
+ NSColor* endColor =
+ [NSColor colorWithCalibratedWhite:1.0 - 0.15 * hoverAlpha
+ alpha:endAlpha];
+ NSGradient* gradient = [[NSGradient alloc] initWithColorsAndLocations:
+ startColor, hoverAlpha * 0.33,
+ endColor, 1.0, nil];
+
+ return [gradient autorelease];
+}
+
+- (void)sharedInit {
+ shouldTheme_ = YES;
+ pulseState_ = gradient_button_cell::kPulsedOff;
+ pulseMultiplier_ = 1.0;
+ outerStrokeAlphaMult_ = 1.0;
+ gradient_.reset([[self gradientForHoverAlpha:0.0 isThemed:NO] retain]);
+}
+
+- (void)setShouldTheme:(BOOL)shouldTheme {
+ shouldTheme_ = shouldTheme;
+}
+
+- (NSImage*)overlayImage {
+ return overlayImage_.get();
+}
+
+- (void)setOverlayImage:(NSImage*)image {
+ overlayImage_.reset([image retain]);
+ [[self controlView] setNeedsDisplay:YES];
+}
+
+- (NSBackgroundStyle)interiorBackgroundStyle {
+ // Never lower the interior, since that just leads to a weird shadow which can
+ // often interact badly with the theme.
+ return NSBackgroundStyleRaised;
+}
+
+- (void)mouseEntered:(NSEvent*)theEvent {
+ [self setMouseInside:YES animate:YES];
+}
+
+- (void)mouseExited:(NSEvent*)theEvent {
+ [self setMouseInside:NO animate:YES];
+}
+
+- (BOOL)isMouseInside {
+ return trackingArea_ && isMouseInside_;
+}
+
+// Since we have our own drawWithFrame:, we need to also have our own
+// logic for determining when the mouse is inside for honoring this
+// request.
+- (void)setShowsBorderOnlyWhileMouseInside:(BOOL)showOnly {
+ [super setShowsBorderOnlyWhileMouseInside:showOnly];
+ if (showOnly) {
+ if (trackingArea_.get()) {
+ [self setShowsBorderOnlyWhileMouseInside:NO];
+ [[self controlView] removeTrackingArea:trackingArea_];
+ }
+ trackingArea_.reset([[NSTrackingArea alloc]
+ initWithRect:[[self controlView]
+ bounds]
+ options:(NSTrackingMouseEnteredAndExited |
+ NSTrackingActiveInActiveApp)
+ owner:self
+ userInfo:nil]);
+ [[self controlView] addTrackingArea:trackingArea_];
+ } else {
+ if (trackingArea_) {
+ [[self controlView] removeTrackingArea:trackingArea_];
+ trackingArea_.reset(nil);
+ isMouseInside_ = NO;
+ }
+ }
+}
+
+// TODO(viettrungluu): clean up/reorganize.
+- (void)drawBorderAndFillForTheme:(ThemeProvider*)themeProvider
+ controlView:(NSView*)controlView
+ innerPath:(NSBezierPath*)innerPath
+ showClickedGradient:(BOOL)showClickedGradient
+ showHighlightGradient:(BOOL)showHighlightGradient
+ hoverAlpha:(CGFloat)hoverAlpha
+ active:(BOOL)active
+ cellFrame:(NSRect)cellFrame
+ defaultGradient:(NSGradient*)defaultGradient {
+ BOOL isFlatButton = [self showsBorderOnlyWhileMouseInside];
+
+ // For flat (unbordered when not hovered) buttons, never use the toolbar
+ // button background image, but the modest gradient used for themed buttons.
+ // To make things even more modest, scale the hover alpha down by 40 percent
+ // unless clicked.
+ NSColor* backgroundImageColor;
+ BOOL useThemeGradient;
+ if (isFlatButton) {
+ backgroundImageColor = nil;
+ useThemeGradient = YES;
+ if (!showClickedGradient)
+ hoverAlpha *= 0.6;
+ } else {
+ backgroundImageColor =
+ themeProvider ?
+ themeProvider->GetNSImageColorNamed(IDR_THEME_BUTTON_BACKGROUND,
+ false) :
+ nil;
+ useThemeGradient = backgroundImageColor ? YES : NO;
+ }
+
+ // The basic gradient shown inside; see above.
+ NSGradient* gradient;
+ if (hoverAlpha == 0 && !useThemeGradient) {
+ gradient = defaultGradient ? defaultGradient
+ : gradient_;
+ } else {
+ gradient = [self gradientForHoverAlpha:hoverAlpha
+ isThemed:useThemeGradient];
+ }
+
+ // If we're drawing a background image, show that; else possibly show the
+ // clicked gradient.
+ if (backgroundImageColor) {
+ [backgroundImageColor set];
+ // Set the phase to match window.
+ NSRect trueRect = [controlView convertRect:cellFrame toView:nil];
+ [[NSGraphicsContext currentContext]
+ setPatternPhase:NSMakePoint(NSMinX(trueRect), NSMaxY(trueRect))];
+ [innerPath fill];
+ } else {
+ if (showClickedGradient) {
+ NSGradient* clickedGradient = nil;
+ if (isFlatButton &&
+ [self tag] == kStandardButtonTypeWithLimitedClickFeedback) {
+ clickedGradient = gradient;
+ } else {
+ clickedGradient = themeProvider ? themeProvider->GetNSGradient(
+ active ?
+ BrowserThemeProvider::GRADIENT_TOOLBAR_BUTTON_PRESSED :
+ BrowserThemeProvider::GRADIENT_TOOLBAR_BUTTON_PRESSED_INACTIVE) :
+ nil;
+ }
+ [clickedGradient drawInBezierPath:innerPath angle:90.0];
+ }
+ }
+
+ // Visually indicate unclicked, enabled buttons.
+ if (!showClickedGradient && [self isEnabled]) {
+ [NSGraphicsContext saveGraphicsState];
+ [innerPath addClip];
+
+ // Draw the inner glow.
+ if (hoverAlpha > 0) {
+ [innerPath setLineWidth:2];
+ [[NSColor colorWithCalibratedWhite:1.0 alpha:0.2 * hoverAlpha] setStroke];
+ [innerPath stroke];
+ }
+
+ // Draw the top inner highlight.
+ NSAffineTransform* highlightTransform = [NSAffineTransform transform];
+ [highlightTransform translateXBy:1 yBy:1];
+ scoped_nsobject<NSBezierPath> highlightPath([innerPath copy]);
+ [highlightPath transformUsingAffineTransform:highlightTransform];
+ [[NSColor colorWithCalibratedWhite:1.0 alpha:0.2] setStroke];
+ [highlightPath stroke];
+
+ // Draw the gradient inside.
+ [gradient drawInBezierPath:innerPath angle:90.0];
+
+ [NSGraphicsContext restoreGraphicsState];
+ }
+
+ // Don't draw anything else for disabled flat buttons.
+ if (isFlatButton && ![self isEnabled])
+ return;
+
+ // Draw the outer stroke.
+ NSColor* strokeColor = nil;
+ if (showClickedGradient) {
+ strokeColor = [NSColor
+ colorWithCalibratedWhite:0.0
+ alpha:0.3 * outerStrokeAlphaMult_];
+ } else {
+ strokeColor = themeProvider ? themeProvider->GetNSColor(
+ active ? BrowserThemeProvider::COLOR_TOOLBAR_BUTTON_STROKE :
+ BrowserThemeProvider::COLOR_TOOLBAR_BUTTON_STROKE_INACTIVE,
+ true) : [NSColor colorWithCalibratedWhite:0.0
+ alpha:0.3 * outerStrokeAlphaMult_];
+ }
+ [strokeColor setStroke];
+
+ [innerPath setLineWidth:1];
+ [innerPath stroke];
+}
+
+// TODO(viettrungluu): clean this up.
+// (Private)
+- (void)getDrawParamsForFrame:(NSRect)cellFrame
+ inView:(NSView*)controlView
+ innerFrame:(NSRect*)returnInnerFrame
+ innerPath:(NSBezierPath**)returnInnerPath
+ clipPath:(NSBezierPath**)returnClipPath {
+ // Constants from Cole. Will kConstant them once the feedback loop
+ // is complete.
+ NSRect drawFrame = NSInsetRect(cellFrame, 1.5, 1.5);
+ NSRect innerFrame = NSInsetRect(cellFrame, 2, 1);
+ const CGFloat radius = 3.5;
+
+ ButtonType type = [[(NSControl*)controlView cell] tag];
+ switch (type) {
+ case kMiddleButtonType:
+ drawFrame.size.width += 20;
+ innerFrame.size.width += 2;
+ // Fallthrough
+ case kRightButtonType:
+ drawFrame.origin.x -= 20;
+ innerFrame.origin.x -= 2;
+ // Fallthrough
+ case kLeftButtonType:
+ case kLeftButtonWithShadowType:
+ drawFrame.size.width += 20;
+ innerFrame.size.width += 2;
+ default:
+ break;
+ }
+ if (type == kLeftButtonWithShadowType)
+ innerFrame.size.width -= 1.0;
+
+ // Return results if |return...| not null.
+ if (returnInnerFrame)
+ *returnInnerFrame = innerFrame;
+ if (returnInnerPath) {
+ DCHECK(*returnInnerPath == nil);
+ *returnInnerPath = [NSBezierPath bezierPathWithRoundedRect:drawFrame
+ xRadius:radius
+ yRadius:radius];
+ }
+ if (returnClipPath) {
+ DCHECK(*returnClipPath == nil);
+ NSRect clipPathRect = NSInsetRect(drawFrame, -0.5, -0.5);
+ *returnClipPath = [NSBezierPath bezierPathWithRoundedRect:clipPathRect
+ xRadius:radius + 0.5
+ yRadius:radius + 0.5];
+ }
+}
+
+// TODO(viettrungluu): clean this up.
+- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
+ NSRect innerFrame;
+ NSBezierPath* innerPath = nil;
+ [self getDrawParamsForFrame:cellFrame
+ inView:controlView
+ innerFrame:&innerFrame
+ innerPath:&innerPath
+ clipPath:NULL];
+
+ BOOL pressed = ([((NSControl*)[self controlView]) isEnabled] &&
+ [self isHighlighted]);
+ NSWindow* window = [controlView window];
+ ThemeProvider* themeProvider = [window themeProvider];
+ BOOL active = [window isKeyWindow] || [window isMainWindow];
+
+ // Stroke the borders and appropriate fill gradient. If we're borderless, the
+ // only time we want to draw the inner gradient is if we're highlighted or if
+ // we're the first responder (when "Full Keyboard Access" is turned on).
+ if (([self isBordered] && ![self showsBorderOnlyWhileMouseInside]) ||
+ pressed ||
+ [self isMouseInside] ||
+ [self isContinuousPulsing] ||
+ [self showsFirstResponder]) {
+
+ // When pulsing we want the bookmark to stand out a little more.
+ BOOL showClickedGradient = pressed ||
+ (pulseState_ == gradient_button_cell::kPulsingContinuous);
+
+ // When first responder, turn the hover alpha all the way up.
+ CGFloat hoverAlpha = [self hoverAlpha];
+ if ([self showsFirstResponder])
+ hoverAlpha = 1.0;
+
+ [self drawBorderAndFillForTheme:themeProvider
+ controlView:controlView
+ innerPath:innerPath
+ showClickedGradient:showClickedGradient
+ showHighlightGradient:[self isHighlighted]
+ hoverAlpha:hoverAlpha
+ active:active
+ cellFrame:cellFrame
+ defaultGradient:nil];
+ }
+
+ // If this is the left side of a segmented button, draw a slight shadow.
+ ButtonType type = [[(NSControl*)controlView cell] tag];
+ if (type == kLeftButtonWithShadowType) {
+ NSRect borderRect, contentRect;
+ NSDivideRect(cellFrame, &borderRect, &contentRect, 1.0, NSMaxXEdge);
+ NSColor* stroke = themeProvider ? themeProvider->GetNSColor(
+ active ? BrowserThemeProvider::COLOR_TOOLBAR_BUTTON_STROKE :
+ BrowserThemeProvider::COLOR_TOOLBAR_BUTTON_STROKE_INACTIVE,
+ true) : [NSColor blackColor];
+
+ [[stroke colorWithAlphaComponent:0.2] set];
+ NSRectFillUsingOperation(NSInsetRect(borderRect, 0, 2),
+ NSCompositeSourceOver);
+ }
+ [self drawInteriorWithFrame:innerFrame inView:controlView];
+}
+
+- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
+ if (shouldTheme_) {
+ BOOL isTemplate = [[self image] isTemplate];
+
+ [NSGraphicsContext saveGraphicsState];
+
+ CGContextRef context =
+ (CGContextRef)([[NSGraphicsContext currentContext] graphicsPort]);
+
+ BrowserThemeProvider* themeProvider = static_cast<BrowserThemeProvider*>(
+ [[controlView window] themeProvider]);
+ NSColor* color = themeProvider ?
+ themeProvider->GetNSColorTint(BrowserThemeProvider::TINT_BUTTONS,
+ true) :
+ [NSColor blackColor];
+
+ if (isTemplate && themeProvider && themeProvider->UsingDefaultTheme()) {
+ scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
+ [shadow.get() setShadowColor:themeProvider->GetNSColor(
+ BrowserThemeProvider::COLOR_TOOLBAR_BEZEL, true)];
+ [shadow.get() setShadowOffset:NSMakeSize(0.0, -1.0)];
+ [shadow setShadowBlurRadius:1.0];
+ [shadow set];
+ }
+
+ CGContextBeginTransparencyLayer(context, 0);
+ NSRect imageRect = NSZeroRect;
+ imageRect.size = [[self image] size];
+ NSRect drawRect = [self imageRectForBounds:cellFrame];
+ [[self image] drawInRect:drawRect
+ fromRect:imageRect
+ operation:NSCompositeSourceOver
+ fraction:[self isEnabled] ? 1.0 : 0.5
+ neverFlipped:YES];
+ if (isTemplate && color) {
+ [color set];
+ NSRectFillUsingOperation(cellFrame, NSCompositeSourceAtop);
+ }
+ CGContextEndTransparencyLayer(context);
+
+ [NSGraphicsContext restoreGraphicsState];
+ } else {
+ // NSCell draws these off-center for some reason, probably because of the
+ // positioning of the control in the xib.
+ [super drawInteriorWithFrame:NSOffsetRect(cellFrame, 0, 1)
+ inView:controlView];
+ }
+
+ if (overlayImage_) {
+ NSRect imageRect = NSZeroRect;
+ imageRect.size = [overlayImage_ size];
+ [overlayImage_ drawInRect:[self imageRectForBounds:cellFrame]
+ fromRect:imageRect
+ operation:NSCompositeSourceOver
+ fraction:[self isEnabled] ? 1.0 : 0.5
+ neverFlipped:YES];
+ }
+}
+
+// Overriden from NSButtonCell so we can display a nice fadeout effect for
+// button titles that overflow.
+// This method is copied in the most part from GTMFadeTruncatingTextFieldCell,
+// the only difference is that here we draw the text ourselves rather than
+// calling the super to do the work.
+// We can't use GTMFadeTruncatingTextFieldCell because there's no easy way to
+// get it to work with NSButtonCell.
+// TODO(jeremy): Move this to GTM.
+- (NSRect)drawTitle:(NSAttributedString *)title
+ withFrame:(NSRect)cellFrame
+ inView:(NSView *)controlView {
+ NSSize size = [title size];
+
+ // Empirically, Cocoa will draw an extra 2 pixels past NSWidth(cellFrame)
+ // before it clips the text.
+ const CGFloat kOverflowBeforeClip = 2;
+ // Don't complicate drawing unless we need to clip.
+ if (floor(size.width) <= (NSWidth(cellFrame) + kOverflowBeforeClip)) {
+ return [super drawTitle:title withFrame:cellFrame inView:controlView];
+ }
+
+ // Gradient is about twice our line height long.
+ CGFloat gradientWidth = MIN(size.height * 2, NSWidth(cellFrame) / 4);
+
+ NSRect solidPart, gradientPart;
+ NSDivideRect(cellFrame, &gradientPart, &solidPart, gradientWidth, NSMaxXEdge);
+
+ // Draw non-gradient part without transparency layer, as light text on a dark
+ // background looks bad with a gradient layer.
+ [[NSGraphicsContext currentContext] saveGraphicsState];
+ [NSBezierPath clipRect:solidPart];
+
+ // 11 is the magic number needed to make this match the native NSButtonCell's
+ // label display.
+ CGFloat textLeft = [[self image] size].width + 11;
+
+ // For some reason, the height of cellFrame as passed in is totally bogus.
+ // For vertical centering purposes, we need the bounds of the containing
+ // view.
+ NSRect buttonFrame = [[self controlView] frame];
+
+ // Off-by-one to match native NSButtonCell's version.
+ NSPoint textOffset = NSMakePoint(textLeft,
+ (NSHeight(buttonFrame) - size.height)/2 + 1);
+ [title drawAtPoint:textOffset];
+ [[NSGraphicsContext currentContext] restoreGraphicsState];
+
+ // Draw the gradient part with a transparency layer. This makes the text look
+ // suboptimal, but since it fades out, that's ok.
+ [[NSGraphicsContext currentContext] saveGraphicsState];
+ [NSBezierPath clipRect:gradientPart];
+ CGContextRef context = static_cast<CGContextRef>(
+ [[NSGraphicsContext currentContext] graphicsPort]);
+ CGContextBeginTransparencyLayerWithRect(context,
+ NSRectToCGRect(gradientPart), 0);
+ [title drawAtPoint:textOffset];
+
+ // TODO(alcor): switch this to GTMLinearRGBShading if we ever need on 10.4
+ NSColor *color = [NSColor textColor]; //[self textColor];
+ NSColor *alphaColor = [color colorWithAlphaComponent:0.0];
+ NSGradient *mask = [[NSGradient alloc] initWithStartingColor:color
+ endingColor:alphaColor];
+
+ // Draw the gradient mask
+ CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
+ [mask drawFromPoint:NSMakePoint(NSMaxX(cellFrame) - gradientWidth,
+ NSMinY(cellFrame))
+ toPoint:NSMakePoint(NSMaxX(cellFrame),
+ NSMinY(cellFrame))
+ options:NSGradientDrawsBeforeStartingLocation];
+ [mask release];
+ CGContextEndTransparencyLayer(context);
+ [[NSGraphicsContext currentContext] restoreGraphicsState];
+
+ return cellFrame;
+}
+
+- (NSBezierPath*)clipPathForFrame:(NSRect)cellFrame
+ inView:(NSView*)controlView {
+ NSBezierPath* boundingPath = nil;
+ [self getDrawParamsForFrame:cellFrame
+ inView:controlView
+ innerFrame:NULL
+ innerPath:NULL
+ clipPath:&boundingPath];
+ return boundingPath;
+}
+
+@end