Chromium Code Reviews| Index: chrome/browser/ui/cocoa/hover_close_button.mm |
| diff --git a/chrome/browser/ui/cocoa/hover_close_button.mm b/chrome/browser/ui/cocoa/hover_close_button.mm |
| index 32a5c80d6ee00f6de82001982ef36add487749de..2f6314863fa647808dc726cccf9595580c9d2cd9 100644 |
| --- a/chrome/browser/ui/cocoa/hover_close_button.mm |
| +++ b/chrome/browser/ui/cocoa/hover_close_button.mm |
| @@ -4,26 +4,97 @@ |
| #import "chrome/browser/ui/cocoa/hover_close_button.h" |
| +#import <QuartzCore/QuartzCore.h> |
| + |
| #include "base/memory/scoped_nsobject.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#import "chrome/browser/ui/cocoa/animation_utils.h" |
| #include "grit/generated_resources.h" |
| #import "third_party/molokocacao/NSBezierPath+MCAdditions.h" |
| #include "ui/base/l10n/l10n_util.h" |
| namespace { |
| -const CGFloat kCircleRadius = 0.415 * 16; |
| +const CGFloat kButtonWidth = 16; |
| +const CGFloat kCircleRadius = 0.415 * kButtonWidth; |
| const CGFloat kCircleHoverWhite = 0.565; |
| const CGFloat kCircleClickWhite = 0.396; |
| const CGFloat kXShadowAlpha = 0.75; |
| const CGFloat kXShadowCircleAlpha = 0.1; |
| + |
| +// Images that are used for all close buttons. Set up in +initialize. |
| +CGImageRef gHoverNoneImage = NULL; |
| +CGImageRef gHoverMouseOverImage = NULL; |
| +CGImageRef gHoverMouseDownImage = NULL; |
| + |
| +// Strings that are used for all close buttons. Set up in +initialize. |
| +NSString* gTooltip = nil; |
| +NSString* gDescription = nil; |
| } // namespace |
| -@interface HoverCloseButton(Private) |
| -- (void)updatePaths; |
| -- (void)setUpDrawingPaths; |
| +@interface HoverCloseButton () |
| +// Sets up the button's tracking areas and accessibility info when instantiated |
| +// via initWithFrame or awakeFromNib. |
| +- (void)commonInit; |
| + |
| +// Draws the close button into a CGImageRef in a given state. |
| ++ (CGImageRef)imageForBounds:(NSRect)bounds |
| + xPath:(NSBezierPath*)xPath |
| + circlePath:(NSBezierPath*)circlePath |
| + hoverState:(HoverState)hoverState; |
| @end |
| @implementation HoverCloseButton |
| ++ (void)initialize { |
| + if ([self class] == [HoverCloseButton class]) { |
| + // Set up the paths for our images. They are centered around the origin. |
| + NSRect bounds = NSMakeRect(0, 0, kButtonWidth, kButtonWidth); |
| + NSBezierPath* circlePath = [NSBezierPath bezierPath]; |
| + [circlePath appendBezierPathWithArcWithCenter:NSZeroPoint |
| + radius:kCircleRadius |
| + startAngle:0.0 |
| + endAngle:365.0]; |
| + |
| + // Construct an 'x' by drawing two intersecting rectangles in the shape of a |
| + // cross and then rotating the path by 45 degrees. |
| + NSBezierPath* xPath = [NSBezierPath bezierPath]; |
| + [xPath appendBezierPathWithRect:NSMakeRect(-4.5, -1.0, 9.0, 2.0)]; |
| + [xPath appendBezierPathWithRect:NSMakeRect(-1.0, -4.5, 2.0, 9.0)]; |
| + |
| + NSAffineTransform* transform = [NSAffineTransform transform]; |
| + [transform rotateByDegrees:45.0]; |
| + [xPath transformUsingAffineTransform:transform]; |
| + |
| + // Move the paths into the center of the given bounds rectangle. |
| + transform = [NSAffineTransform transform]; |
| + NSPoint xCenter = NSMakePoint(NSWidth(bounds) / 2.0, |
| + NSHeight(bounds) / 2.0); |
| + [transform translateXBy:xCenter.x yBy:xCenter.y]; |
| + [circlePath transformUsingAffineTransform:transform]; |
| + [xPath transformUsingAffineTransform:transform]; |
| + |
| + CGImageRef image = [self imageForBounds:bounds |
| + xPath:xPath |
| + circlePath:circlePath |
| + hoverState:kHoverStateNone]; |
| + gHoverNoneImage = CGImageRetain(image); |
| + image = [self imageForBounds:bounds |
| + xPath:xPath |
| + circlePath:circlePath |
| + hoverState:kHoverStateMouseOver]; |
| + gHoverMouseOverImage = CGImageRetain(image); |
| + image = [self imageForBounds:bounds |
| + xPath:xPath |
| + circlePath:circlePath |
| + hoverState:kHoverStateMouseDown]; |
| + gHoverMouseDownImage = CGImageRetain(image); |
| + |
| + // Grab some strings that are used by all close buttons. |
| + gDescription = [l10n_util::GetNSStringWithFixup(IDS_ACCNAME_CLOSE) copy]; |
| + gTooltip = [l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_CLOSE_TAB) copy]; |
| + } |
| +} |
| + |
| - (id)initWithFrame:(NSRect)frameRect { |
| if ((self = [super initWithFrame:frameRect])) { |
| [self commonInit]; |
| @@ -36,100 +107,120 @@ const CGFloat kXShadowCircleAlpha = 0.1; |
| [self commonInit]; |
| } |
| -- (void)drawRect:(NSRect)rect { |
| - if (!circlePath_.get() || !xPath_.get()) |
| - [self setUpDrawingPaths]; |
| +- (void)setHoverState:(HoverState)state { |
| + [super setHoverState:state]; |
| + |
| + // Only animate the HoverStateNone case. |
| + scoped_ptr<ScopedCAActionDisabler> disabler; |
| + if (state != kHoverStateNone) { |
| + disabler.reset(new ScopedCAActionDisabler); |
| + } |
| + [hoverNoneLayer_ setHidden:state != kHoverStateNone]; |
| + [hoverMouseDownLayer_ setHidden:state != kHoverStateMouseDown]; |
| + [hoverMouseOverLayer_ setHidden:state != kHoverStateMouseOver]; |
| +} |
| + |
| +- (void)commonInit { |
| + // Set accessibility description. |
| + NSCell* cell = [self cell]; |
| + [cell accessibilitySetOverrideValue:gDescription |
| + forAttribute:NSAccessibilityDescriptionAttribute]; |
| + |
| + // Add a tooltip. |
| + [self setToolTip:gTooltip]; |
| + |
| + // Set up layers |
| + CALayer* viewLayer = [self layer]; |
| + |
| + // Layers will be contrained to be 8 pixels from left edge horizontally, |
| + // and centered vertically. |
|
Miranda Callahan
2011/07/12 13:44:35
Is it really 8 pixels from the left edge? I don't
|
| + viewLayer.layoutManager = [CAConstraintLayoutManager layoutManager]; |
| + CAConstraint* xConstraint = |
| + [CAConstraint constraintWithAttribute:kCAConstraintMinX |
| + relativeTo:@"superlayer" |
| + attribute:kCAConstraintMinX |
| + offset:0]; |
| + CAConstraint* yConstraint = |
| + [CAConstraint constraintWithAttribute:kCAConstraintMidY |
| + relativeTo:@"superlayer" |
| + attribute:kCAConstraintMidY]; |
| + |
| + hoverNoneLayer_.reset([[CALayer alloc] init]); |
| + [hoverNoneLayer_ setDelegate:self]; |
| + [hoverNoneLayer_.get() setFrame:viewLayer.frame]; |
| + [viewLayer addSublayer:hoverNoneLayer_]; |
| + [hoverNoneLayer_ addConstraint:xConstraint]; |
| + [hoverNoneLayer_ addConstraint:yConstraint]; |
| + [hoverNoneLayer_ setContents:reinterpret_cast<id>(gHoverNoneImage)]; |
| + [hoverNoneLayer_ setHidden:NO]; |
| + |
| + hoverMouseOverLayer_.reset([[CALayer alloc] init]); |
| + [hoverMouseOverLayer_.get() setFrame:viewLayer.frame]; |
| + [viewLayer addSublayer:hoverMouseOverLayer_]; |
| + [hoverMouseOverLayer_ addConstraint:xConstraint]; |
| + [hoverMouseOverLayer_ addConstraint:yConstraint]; |
| + [hoverMouseOverLayer_ setContents:reinterpret_cast<id>(gHoverMouseOverImage)]; |
| + [hoverMouseOverLayer_ setHidden:YES]; |
| + |
| + hoverMouseDownLayer_.reset([[CALayer alloc] init]); |
| + [hoverMouseDownLayer_.get() setFrame:viewLayer.frame]; |
| + [viewLayer addSublayer:hoverMouseDownLayer_]; |
| + [hoverMouseDownLayer_ addConstraint:xConstraint]; |
| + [hoverMouseDownLayer_ addConstraint:yConstraint]; |
| + [hoverMouseDownLayer_ setContents:reinterpret_cast<id>(gHoverMouseDownImage)]; |
| + [hoverMouseDownLayer_ setHidden:YES]; |
| +} |
| - // Only call updatePaths if the size changed. |
| - if (!NSEqualSizes(oldSize_, [self bounds].size)) |
| - [self updatePaths]; |
| ++ (CGImageRef)imageForBounds:(NSRect)bounds |
| + xPath:(NSBezierPath*)xPath |
| + circlePath:(NSBezierPath*)circlePath |
| + hoverState:(HoverState)hoverState { |
| + [NSGraphicsContext saveGraphicsState]; |
| + scoped_nsobject<NSBitmapImageRep> imageRep( |
| + [[NSBitmapImageRep alloc] |
| + initWithBitmapDataPlanes:NULL |
| + pixelsWide:NSWidth(bounds) |
| + pixelsHigh:NSHeight(bounds) |
| + bitsPerSample:8 |
| + samplesPerPixel:4 |
| + hasAlpha:YES |
| + isPlanar:NO |
| + colorSpaceName:NSCalibratedRGBColorSpace |
| + bitmapFormat:0 |
| + bytesPerRow:kButtonWidth * 4 |
| + bitsPerPixel:32]); |
| + NSGraphicsContext* gc = |
| + [NSGraphicsContext graphicsContextWithBitmapImageRep:imageRep]; |
| + [NSGraphicsContext setCurrentContext:gc]; |
| + |
| + [[NSColor clearColor] set]; |
| + NSRectFill(bounds); |
| // If the user is hovering over the button, a light/dark gray circle is drawn |
| // behind the 'x'. |
| - if (hoverState_ != kHoverStateNone) { |
| + if (hoverState != kHoverStateNone) { |
| // Adjust the darkness of the circle depending on whether it is being |
| // clicked. |
| - CGFloat white = (hoverState_ == kHoverStateMouseOver) ? |
| + CGFloat white = (hoverState == kHoverStateMouseOver) ? |
| kCircleHoverWhite : kCircleClickWhite; |
| [[NSColor colorWithCalibratedWhite:white alpha:1.0] set]; |
| - [circlePath_ fill]; |
| + [circlePath fill]; |
| } |
| [[NSColor whiteColor] set]; |
| - [xPath_ fill]; |
| + [xPath fill]; |
| // Give the 'x' an inner shadow for depth. If the button is in a hover state |
| // (circle behind it), then adjust the shadow accordingly (not as harsh). |
| NSShadow* shadow = [[[NSShadow alloc] init] autorelease]; |
| - CGFloat alpha = (hoverState_ != kHoverStateNone) ? |
| + CGFloat alpha = (hoverState != kHoverStateNone) ? |
| kXShadowCircleAlpha : kXShadowAlpha; |
| - [shadow setShadowColor:[NSColor colorWithCalibratedWhite:0.15 |
| - alpha:alpha]]; |
| + [shadow setShadowColor:[NSColor colorWithCalibratedWhite:0.15 alpha:alpha]]; |
| [shadow setShadowOffset:NSMakeSize(0.0, 0.0)]; |
| [shadow setShadowBlurRadius:2.5]; |
| - [xPath_ fillWithInnerShadow:shadow]; |
| -} |
| - |
| -- (void)commonInit { |
| - // Set accessibility description. |
| - NSString* description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_CLOSE); |
| - [[self cell] |
| - accessibilitySetOverrideValue:description |
| - forAttribute:NSAccessibilityDescriptionAttribute]; |
| - |
| - // Add a tooltip. |
| - [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_CLOSE_TAB)]; |
| -} |
| - |
| -- (void)setUpDrawingPaths { |
| - // Keep the paths centered around the origin in this function. It is then |
| - // translated in -updatePaths. |
| - NSPoint xCenter = NSZeroPoint; |
| - |
| - circlePath_.reset([[NSBezierPath bezierPath] retain]); |
| - [circlePath_ moveToPoint:xCenter]; |
| - CGFloat radius = kCircleRadius; |
| - [circlePath_ appendBezierPathWithArcWithCenter:xCenter |
| - radius:radius |
| - startAngle:0.0 |
| - endAngle:365.0]; |
| - |
| - // Construct an 'x' by drawing two intersecting rectangles in the shape of a |
| - // cross and then rotating the path by 45 degrees. |
| - xPath_.reset([[NSBezierPath bezierPath] retain]); |
| - [xPath_ appendBezierPathWithRect:NSMakeRect(3.5, 7.0, 9.0, 2.0)]; |
| - [xPath_ appendBezierPathWithRect:NSMakeRect(7.0, 3.5, 2.0, 9.0)]; |
| - |
| - NSRect pathBounds = [xPath_ bounds]; |
| - NSPoint pathCenter = NSMakePoint(NSMidX(pathBounds), NSMidY(pathBounds)); |
| - |
| - NSAffineTransform* transform = [NSAffineTransform transform]; |
| - [transform translateXBy:xCenter.x yBy:xCenter.y]; |
| - [transform rotateByDegrees:45.0]; |
| - [transform translateXBy:-pathCenter.x yBy:-pathCenter.y]; |
| - |
| - [xPath_ transformUsingAffineTransform:transform]; |
| -} |
| - |
| -- (void)updatePaths { |
| - oldSize_ = [self bounds].size; |
| - |
| - // Revert the current transform for the two points. |
| - if (transform_.get()) { |
| - [transform_.get() invert]; |
| - [circlePath_.get() transformUsingAffineTransform:transform_.get()]; |
| - [xPath_.get() transformUsingAffineTransform:transform_.get()]; |
| - } |
| - |
| - // Create the new transform. [self bounds] is prefered in case aRect wasn't |
| - // literally taken as bounds (e.g. cropped). |
| - NSPoint xCenter = NSMakePoint(8, oldSize_.height / 2.0f); |
| - |
| - // Retain here, as scoped_* don't retain. |
| - transform_.reset([[NSAffineTransform transform] retain]); |
| - [transform_.get() translateXBy:xCenter.x yBy:xCenter.y]; |
| - [circlePath_.get() transformUsingAffineTransform:transform_.get()]; |
| - [xPath_.get() transformUsingAffineTransform:transform_.get()]; |
| + [xPath fillWithInnerShadow:shadow]; |
| + [NSGraphicsContext restoreGraphicsState]; |
| + return [imageRep CGImage]; |
| } |
| @end |