blob: c8df70bd3a0317e5bb6af5fe48747266b690809f [file] [log] [blame]
// Copyright (c) 2012 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/memory/scoped_nsobject.h"
#import "chrome/browser/themes/theme_service.h"
#import "chrome/browser/ui/cocoa/image_utils.h"
#import "chrome/browser/ui/cocoa/nsview_additions.h"
#import "chrome/browser/ui/cocoa/themed_window.h"
#include "grit/theme_resources.h"
#import "third_party/GTM/AppKit/GTMNSColor+Luminance.h"
#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.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;
- (void)updateTrackingAreas;
@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) {
[self updateTrackingAreas];
} else {
if (trackingArea_) {
[[self controlView] removeTrackingArea:trackingArea_];
trackingArea_.reset(nil);
if (isMouseInside_) {
isMouseInside_ = NO;
[[self controlView] setNeedsDisplay:YES];
}
}
}
}
// TODO(viettrungluu): clean up/reorganize.
- (void)drawBorderAndFillForTheme:(ui::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 ?
ThemeService::GRADIENT_TOOLBAR_BUTTON_PRESSED :
ThemeService::GRADIENT_TOOLBAR_BUTTON_PRESSED_INACTIVE) :
nil;
}
[clickedGradient drawInBezierPath:innerPath angle:90.0];
}
}
// Visually indicate unclicked, enabled buttons.
if (!showClickedGradient && [self isEnabled]) {
gfx::ScopedNSGraphicsContextSaveGState scopedGState;
[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];
}
// 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 ? ThemeService::COLOR_TOOLBAR_BUTTON_STROKE :
ThemeService::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 {
const CGFloat lineWidth = [controlView cr_lineWidth];
const CGFloat halfLineWidth = lineWidth / 2.0;
// Constants from Cole. Will kConstant them once the feedback loop
// is complete.
NSRect drawFrame = NSInsetRect(cellFrame, 1.5 * lineWidth, 1.5 * lineWidth);
NSRect innerFrame = NSInsetRect(cellFrame, lineWidth, lineWidth);
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];
[*returnInnerPath setLineWidth:lineWidth];
}
if (returnClipPath) {
DCHECK(*returnClipPath == nil);
NSRect clipPathRect =
NSInsetRect(drawFrame, -halfLineWidth, -halfLineWidth);
*returnClipPath = [NSBezierPath
bezierPathWithRoundedRect:clipPathRect
xRadius:radius + halfLineWidth
yRadius:radius + halfLineWidth];
}
}
// 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];
ui::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) {
const CGFloat lineWidth = [controlView cr_lineWidth];
NSRect borderRect, contentRect;
NSDivideRect(cellFrame, &borderRect, &contentRect, lineWidth, NSMaxXEdge);
NSColor* stroke = themeProvider ? themeProvider->GetNSColor(
active ? ThemeService::COLOR_TOOLBAR_BUTTON_STROKE :
ThemeService::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 {
const CGFloat lineWidth = [controlView cr_lineWidth];
if (shouldTheme_) {
BOOL isTemplate = [[self image] isTemplate];
gfx::ScopedNSGraphicsContextSaveGState scopedGState;
CGContextRef context =
(CGContextRef)([[NSGraphicsContext currentContext] graphicsPort]);
ThemeService* themeProvider = static_cast<ThemeService*>(
[[controlView window] themeProvider]);
NSColor* color = themeProvider ?
themeProvider->GetNSColorTint(ThemeService::TINT_BUTTONS, true) :
[NSColor blackColor];
if (isTemplate && themeProvider && themeProvider->UsingDefaultTheme()) {
scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
[shadow.get() setShadowColor:themeProvider->GetNSColor(
ThemeService::COLOR_TOOLBAR_BEZEL, true)];
[shadow.get() setShadowOffset:NSMakeSize(0.0, -lineWidth)];
[shadow setShadowBlurRadius:lineWidth];
[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);
} 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, lineWidth)
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];
}
}
- (int)verticalTextOffset {
return 1;
}
// 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)) {
cellFrame.origin.y += ([self verticalTextOffset] - 1);
// The super is called to provide the background shadow "highlight" for
// non-clipping text.
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.
NSPoint textOffset = NSZeroPoint;
{
gfx::ScopedNSGraphicsContextSaveGState scopedGState;
[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];
// Call the vertical offset to match native NSButtonCell's version.
textOffset = NSMakePoint(textLeft,
(NSHeight(buttonFrame) - size.height) / 2 +
[self verticalTextOffset]);
[title drawAtPoint:textOffset];
}
// Draw the gradient part with a transparency layer. This makes the text look
// suboptimal, but since it fades out, that's ok.
gfx::ScopedNSGraphicsContextSaveGState scopedGState;
[NSBezierPath clipRect:gradientPart];
CGContextRef context = static_cast<CGContextRef>(
[[NSGraphicsContext currentContext] graphicsPort]);
CGContextBeginTransparencyLayerWithRect(context,
NSRectToCGRect(gradientPart), 0);
[title drawAtPoint:textOffset];
NSColor *color = [NSColor 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);
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;
}
- (void)resetCursorRect:(NSRect)cellFrame inView:(NSView*)controlView {
[super resetCursorRect:cellFrame inView:controlView];
if (trackingArea_)
[self updateTrackingAreas];
}
- (BOOL)isMouseReallyInside {
BOOL mouseInView = NO;
NSView* controlView = [self controlView];
NSWindow* window = [controlView window];
NSRect bounds = [controlView bounds];
if (window) {
NSPoint mousePoint = [window mouseLocationOutsideOfEventStream];
mousePoint = [controlView convertPointFromBase:mousePoint];
mouseInView = [controlView mouse:mousePoint inRect:bounds];
}
return mouseInView;
}
- (void)updateTrackingAreas {
NSView* controlView = [self controlView];
BOOL mouseInView = [self isMouseReallyInside];
if (trackingArea_.get())
[controlView removeTrackingArea:trackingArea_];
NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
NSTrackingActiveInActiveApp;
if (mouseInView)
options |= NSTrackingAssumeInside;
trackingArea_.reset([[NSTrackingArea alloc]
initWithRect:[controlView bounds]
options:options
owner:self
userInfo:nil]);
if (isMouseInside_ != mouseInView) {
[self setMouseInside:mouseInView animate:NO];
[controlView setNeedsDisplay:YES];
}
}
@end