| // Copyright (c) 2011 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" |
| |
| @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]) { |
| [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 ? 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, 2 * 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]; |
| |
| [NSGraphicsContext saveGraphicsState]; |
| |
| 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); |
| |
| [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, 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]; |
| } |
| } |
| |
| // 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; |
| } |
| |
| - (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 |