Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "chrome/browser/ui/cocoa/hover_close_button.h" | 5 #import "chrome/browser/ui/cocoa/hover_close_button.h" |
| 6 | 6 |
| 7 #import <QuartzCore/QuartzCore.h> | |
| 8 | |
| 7 #include "base/memory/scoped_nsobject.h" | 9 #include "base/memory/scoped_nsobject.h" |
| 10 #include "base/memory/scoped_ptr.h" | |
| 11 #import "chrome/browser/ui/cocoa/animation_utils.h" | |
| 8 #include "grit/generated_resources.h" | 12 #include "grit/generated_resources.h" |
| 9 #import "third_party/molokocacao/NSBezierPath+MCAdditions.h" | 13 #import "third_party/molokocacao/NSBezierPath+MCAdditions.h" |
| 10 #include "ui/base/l10n/l10n_util.h" | 14 #include "ui/base/l10n/l10n_util.h" |
| 11 | 15 |
| 12 namespace { | 16 namespace { |
| 13 const CGFloat kCircleRadius = 0.415 * 16; | 17 const CGFloat kButtonWidth = 16; |
| 18 const CGFloat kCircleRadius = 0.415 * kButtonWidth; | |
| 14 const CGFloat kCircleHoverWhite = 0.565; | 19 const CGFloat kCircleHoverWhite = 0.565; |
| 15 const CGFloat kCircleClickWhite = 0.396; | 20 const CGFloat kCircleClickWhite = 0.396; |
| 16 const CGFloat kXShadowAlpha = 0.75; | 21 const CGFloat kXShadowAlpha = 0.75; |
| 17 const CGFloat kXShadowCircleAlpha = 0.1; | 22 const CGFloat kXShadowCircleAlpha = 0.1; |
| 23 | |
| 24 // Images that are used for all close buttons. Set up in +initialize. | |
| 25 CGImageRef gHoverNoneImage = NULL; | |
| 26 CGImageRef gHoverMouseOverImage = NULL; | |
| 27 CGImageRef gHoverMouseDownImage = NULL; | |
| 28 | |
| 29 // Strings that are used for all close buttons. Set up in +initialize. | |
| 30 NSString* gTooltip = nil; | |
| 31 NSString* gDescription = nil; | |
| 18 } // namespace | 32 } // namespace |
| 19 | 33 |
| 20 @interface HoverCloseButton(Private) | 34 @interface HoverCloseButton () |
| 21 - (void)updatePaths; | 35 // Sets up the button's tracking areas and accessibility info when instantiated |
| 22 - (void)setUpDrawingPaths; | 36 // via initWithFrame or awakeFromNib. |
| 37 - (void)commonInit; | |
| 38 | |
| 39 // Draws the close button into a CGImageRef in a given state. | |
| 40 + (CGImageRef)imageForBounds:(NSRect)bounds | |
| 41 xPath:(NSBezierPath*)xPath | |
| 42 circlePath:(NSBezierPath*)circlePath | |
| 43 hoverState:(HoverState)hoverState; | |
| 23 @end | 44 @end |
| 24 | 45 |
| 25 @implementation HoverCloseButton | 46 @implementation HoverCloseButton |
| 26 | 47 |
| 48 + (void)initialize { | |
| 49 if ([self class] == [HoverCloseButton class]) { | |
| 50 // Set up the paths for our images. They are centered around the origin. | |
| 51 NSRect bounds = NSMakeRect(0, 0, kButtonWidth, kButtonWidth); | |
| 52 NSBezierPath* circlePath = [NSBezierPath bezierPath]; | |
| 53 [circlePath appendBezierPathWithArcWithCenter:NSZeroPoint | |
| 54 radius:kCircleRadius | |
| 55 startAngle:0.0 | |
| 56 endAngle:365.0]; | |
| 57 | |
| 58 // Construct an 'x' by drawing two intersecting rectangles in the shape of a | |
| 59 // cross and then rotating the path by 45 degrees. | |
| 60 NSBezierPath* xPath = [NSBezierPath bezierPath]; | |
| 61 [xPath appendBezierPathWithRect:NSMakeRect(-4.5, -1.0, 9.0, 2.0)]; | |
| 62 [xPath appendBezierPathWithRect:NSMakeRect(-1.0, -4.5, 2.0, 9.0)]; | |
| 63 | |
| 64 NSAffineTransform* transform = [NSAffineTransform transform]; | |
| 65 [transform rotateByDegrees:45.0]; | |
| 66 [xPath transformUsingAffineTransform:transform]; | |
| 67 | |
| 68 // Move the paths into the center of the given bounds rectangle. | |
| 69 transform = [NSAffineTransform transform]; | |
| 70 NSPoint xCenter = NSMakePoint(NSWidth(bounds) / 2.0, | |
| 71 NSHeight(bounds) / 2.0); | |
| 72 [transform translateXBy:xCenter.x yBy:xCenter.y]; | |
| 73 [circlePath transformUsingAffineTransform:transform]; | |
| 74 [xPath transformUsingAffineTransform:transform]; | |
| 75 | |
| 76 CGImageRef image = [self imageForBounds:bounds | |
| 77 xPath:xPath | |
| 78 circlePath:circlePath | |
| 79 hoverState:kHoverStateNone]; | |
| 80 gHoverNoneImage = CGImageRetain(image); | |
| 81 image = [self imageForBounds:bounds | |
| 82 xPath:xPath | |
| 83 circlePath:circlePath | |
| 84 hoverState:kHoverStateMouseOver]; | |
| 85 gHoverMouseOverImage = CGImageRetain(image); | |
| 86 image = [self imageForBounds:bounds | |
| 87 xPath:xPath | |
| 88 circlePath:circlePath | |
| 89 hoverState:kHoverStateMouseDown]; | |
| 90 gHoverMouseDownImage = CGImageRetain(image); | |
| 91 | |
| 92 // Grab some strings that are used by all close buttons. | |
| 93 gDescription = [l10n_util::GetNSStringWithFixup(IDS_ACCNAME_CLOSE) copy]; | |
| 94 gTooltip = [l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_CLOSE_TAB) copy]; | |
| 95 } | |
| 96 } | |
| 97 | |
| 27 - (id)initWithFrame:(NSRect)frameRect { | 98 - (id)initWithFrame:(NSRect)frameRect { |
| 28 if ((self = [super initWithFrame:frameRect])) { | 99 if ((self = [super initWithFrame:frameRect])) { |
| 29 [self commonInit]; | 100 [self commonInit]; |
| 30 } | 101 } |
| 31 return self; | 102 return self; |
| 32 } | 103 } |
| 33 | 104 |
| 34 - (void)awakeFromNib { | 105 - (void)awakeFromNib { |
| 35 [super awakeFromNib]; | 106 [super awakeFromNib]; |
| 36 [self commonInit]; | 107 [self commonInit]; |
| 37 } | 108 } |
| 38 | 109 |
| 39 - (void)drawRect:(NSRect)rect { | 110 - (void)setHoverState:(HoverState)state { |
| 40 if (!circlePath_.get() || !xPath_.get()) | 111 [super setHoverState:state]; |
| 41 [self setUpDrawingPaths]; | |
| 42 | 112 |
| 43 // Only call updatePaths if the size changed. | 113 // Only animate the HoverStateNone case. |
| 44 if (!NSEqualSizes(oldSize_, [self bounds].size)) | 114 scoped_ptr<ScopedCAActionDisabler> disabler; |
| 45 [self updatePaths]; | 115 if (state != kHoverStateNone) { |
| 116 disabler.reset(new ScopedCAActionDisabler); | |
| 117 } | |
| 118 [hoverNoneLayer_ setHidden:state != kHoverStateNone]; | |
| 119 [hoverMouseDownLayer_ setHidden:state != kHoverStateMouseDown]; | |
| 120 [hoverMouseOverLayer_ setHidden:state != kHoverStateMouseOver]; | |
| 121 } | |
| 122 | |
| 123 - (void)commonInit { | |
| 124 // Set accessibility description. | |
| 125 NSCell* cell = [self cell]; | |
| 126 [cell accessibilitySetOverrideValue:gDescription | |
| 127 forAttribute:NSAccessibilityDescriptionAttribute]; | |
| 128 | |
| 129 // Add a tooltip. | |
| 130 [self setToolTip:gTooltip]; | |
| 131 | |
| 132 // Set up layers | |
| 133 CALayer* viewLayer = [self layer]; | |
| 134 | |
| 135 // Layers will be contrained to be 8 pixels from left edge horizontally, | |
| 136 // and centered vertically. | |
|
Miranda Callahan
2011/07/12 13:44:35
Is it really 8 pixels from the left edge? I don't
| |
| 137 viewLayer.layoutManager = [CAConstraintLayoutManager layoutManager]; | |
| 138 CAConstraint* xConstraint = | |
| 139 [CAConstraint constraintWithAttribute:kCAConstraintMinX | |
| 140 relativeTo:@"superlayer" | |
| 141 attribute:kCAConstraintMinX | |
| 142 offset:0]; | |
| 143 CAConstraint* yConstraint = | |
| 144 [CAConstraint constraintWithAttribute:kCAConstraintMidY | |
| 145 relativeTo:@"superlayer" | |
| 146 attribute:kCAConstraintMidY]; | |
| 147 | |
| 148 hoverNoneLayer_.reset([[CALayer alloc] init]); | |
| 149 [hoverNoneLayer_ setDelegate:self]; | |
| 150 [hoverNoneLayer_.get() setFrame:viewLayer.frame]; | |
| 151 [viewLayer addSublayer:hoverNoneLayer_]; | |
| 152 [hoverNoneLayer_ addConstraint:xConstraint]; | |
| 153 [hoverNoneLayer_ addConstraint:yConstraint]; | |
| 154 [hoverNoneLayer_ setContents:reinterpret_cast<id>(gHoverNoneImage)]; | |
| 155 [hoverNoneLayer_ setHidden:NO]; | |
| 156 | |
| 157 hoverMouseOverLayer_.reset([[CALayer alloc] init]); | |
| 158 [hoverMouseOverLayer_.get() setFrame:viewLayer.frame]; | |
| 159 [viewLayer addSublayer:hoverMouseOverLayer_]; | |
| 160 [hoverMouseOverLayer_ addConstraint:xConstraint]; | |
| 161 [hoverMouseOverLayer_ addConstraint:yConstraint]; | |
| 162 [hoverMouseOverLayer_ setContents:reinterpret_cast<id>(gHoverMouseOverImage)]; | |
| 163 [hoverMouseOverLayer_ setHidden:YES]; | |
| 164 | |
| 165 hoverMouseDownLayer_.reset([[CALayer alloc] init]); | |
| 166 [hoverMouseDownLayer_.get() setFrame:viewLayer.frame]; | |
| 167 [viewLayer addSublayer:hoverMouseDownLayer_]; | |
| 168 [hoverMouseDownLayer_ addConstraint:xConstraint]; | |
| 169 [hoverMouseDownLayer_ addConstraint:yConstraint]; | |
| 170 [hoverMouseDownLayer_ setContents:reinterpret_cast<id>(gHoverMouseDownImage)]; | |
| 171 [hoverMouseDownLayer_ setHidden:YES]; | |
| 172 } | |
| 173 | |
| 174 + (CGImageRef)imageForBounds:(NSRect)bounds | |
| 175 xPath:(NSBezierPath*)xPath | |
| 176 circlePath:(NSBezierPath*)circlePath | |
| 177 hoverState:(HoverState)hoverState { | |
| 178 [NSGraphicsContext saveGraphicsState]; | |
| 179 scoped_nsobject<NSBitmapImageRep> imageRep( | |
| 180 [[NSBitmapImageRep alloc] | |
| 181 initWithBitmapDataPlanes:NULL | |
| 182 pixelsWide:NSWidth(bounds) | |
| 183 pixelsHigh:NSHeight(bounds) | |
| 184 bitsPerSample:8 | |
| 185 samplesPerPixel:4 | |
| 186 hasAlpha:YES | |
| 187 isPlanar:NO | |
| 188 colorSpaceName:NSCalibratedRGBColorSpace | |
| 189 bitmapFormat:0 | |
| 190 bytesPerRow:kButtonWidth * 4 | |
| 191 bitsPerPixel:32]); | |
| 192 NSGraphicsContext* gc = | |
| 193 [NSGraphicsContext graphicsContextWithBitmapImageRep:imageRep]; | |
| 194 [NSGraphicsContext setCurrentContext:gc]; | |
| 195 | |
| 196 [[NSColor clearColor] set]; | |
| 197 NSRectFill(bounds); | |
| 46 | 198 |
| 47 // If the user is hovering over the button, a light/dark gray circle is drawn | 199 // If the user is hovering over the button, a light/dark gray circle is drawn |
| 48 // behind the 'x'. | 200 // behind the 'x'. |
| 49 if (hoverState_ != kHoverStateNone) { | 201 if (hoverState != kHoverStateNone) { |
| 50 // Adjust the darkness of the circle depending on whether it is being | 202 // Adjust the darkness of the circle depending on whether it is being |
| 51 // clicked. | 203 // clicked. |
| 52 CGFloat white = (hoverState_ == kHoverStateMouseOver) ? | 204 CGFloat white = (hoverState == kHoverStateMouseOver) ? |
| 53 kCircleHoverWhite : kCircleClickWhite; | 205 kCircleHoverWhite : kCircleClickWhite; |
| 54 [[NSColor colorWithCalibratedWhite:white alpha:1.0] set]; | 206 [[NSColor colorWithCalibratedWhite:white alpha:1.0] set]; |
| 55 [circlePath_ fill]; | 207 [circlePath fill]; |
| 56 } | 208 } |
| 57 | 209 |
| 58 [[NSColor whiteColor] set]; | 210 [[NSColor whiteColor] set]; |
| 59 [xPath_ fill]; | 211 [xPath fill]; |
| 60 | 212 |
| 61 // Give the 'x' an inner shadow for depth. If the button is in a hover state | 213 // Give the 'x' an inner shadow for depth. If the button is in a hover state |
| 62 // (circle behind it), then adjust the shadow accordingly (not as harsh). | 214 // (circle behind it), then adjust the shadow accordingly (not as harsh). |
| 63 NSShadow* shadow = [[[NSShadow alloc] init] autorelease]; | 215 NSShadow* shadow = [[[NSShadow alloc] init] autorelease]; |
| 64 CGFloat alpha = (hoverState_ != kHoverStateNone) ? | 216 CGFloat alpha = (hoverState != kHoverStateNone) ? |
| 65 kXShadowCircleAlpha : kXShadowAlpha; | 217 kXShadowCircleAlpha : kXShadowAlpha; |
| 66 [shadow setShadowColor:[NSColor colorWithCalibratedWhite:0.15 | 218 [shadow setShadowColor:[NSColor colorWithCalibratedWhite:0.15 alpha:alpha]]; |
| 67 alpha:alpha]]; | |
| 68 [shadow setShadowOffset:NSMakeSize(0.0, 0.0)]; | 219 [shadow setShadowOffset:NSMakeSize(0.0, 0.0)]; |
| 69 [shadow setShadowBlurRadius:2.5]; | 220 [shadow setShadowBlurRadius:2.5]; |
| 70 [xPath_ fillWithInnerShadow:shadow]; | 221 [xPath fillWithInnerShadow:shadow]; |
| 71 } | 222 [NSGraphicsContext restoreGraphicsState]; |
| 72 | 223 return [imageRep CGImage]; |
| 73 - (void)commonInit { | |
| 74 // Set accessibility description. | |
| 75 NSString* description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_CLOSE); | |
| 76 [[self cell] | |
| 77 accessibilitySetOverrideValue:description | |
| 78 forAttribute:NSAccessibilityDescriptionAttribute]; | |
| 79 | |
| 80 // Add a tooltip. | |
| 81 [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_CLOSE_TAB)]; | |
| 82 } | |
| 83 | |
| 84 - (void)setUpDrawingPaths { | |
| 85 // Keep the paths centered around the origin in this function. It is then | |
| 86 // translated in -updatePaths. | |
| 87 NSPoint xCenter = NSZeroPoint; | |
| 88 | |
| 89 circlePath_.reset([[NSBezierPath bezierPath] retain]); | |
| 90 [circlePath_ moveToPoint:xCenter]; | |
| 91 CGFloat radius = kCircleRadius; | |
| 92 [circlePath_ appendBezierPathWithArcWithCenter:xCenter | |
| 93 radius:radius | |
| 94 startAngle:0.0 | |
| 95 endAngle:365.0]; | |
| 96 | |
| 97 // Construct an 'x' by drawing two intersecting rectangles in the shape of a | |
| 98 // cross and then rotating the path by 45 degrees. | |
| 99 xPath_.reset([[NSBezierPath bezierPath] retain]); | |
| 100 [xPath_ appendBezierPathWithRect:NSMakeRect(3.5, 7.0, 9.0, 2.0)]; | |
| 101 [xPath_ appendBezierPathWithRect:NSMakeRect(7.0, 3.5, 2.0, 9.0)]; | |
| 102 | |
| 103 NSRect pathBounds = [xPath_ bounds]; | |
| 104 NSPoint pathCenter = NSMakePoint(NSMidX(pathBounds), NSMidY(pathBounds)); | |
| 105 | |
| 106 NSAffineTransform* transform = [NSAffineTransform transform]; | |
| 107 [transform translateXBy:xCenter.x yBy:xCenter.y]; | |
| 108 [transform rotateByDegrees:45.0]; | |
| 109 [transform translateXBy:-pathCenter.x yBy:-pathCenter.y]; | |
| 110 | |
| 111 [xPath_ transformUsingAffineTransform:transform]; | |
| 112 } | |
| 113 | |
| 114 - (void)updatePaths { | |
| 115 oldSize_ = [self bounds].size; | |
| 116 | |
| 117 // Revert the current transform for the two points. | |
| 118 if (transform_.get()) { | |
| 119 [transform_.get() invert]; | |
| 120 [circlePath_.get() transformUsingAffineTransform:transform_.get()]; | |
| 121 [xPath_.get() transformUsingAffineTransform:transform_.get()]; | |
| 122 } | |
| 123 | |
| 124 // Create the new transform. [self bounds] is prefered in case aRect wasn't | |
| 125 // literally taken as bounds (e.g. cropped). | |
| 126 NSPoint xCenter = NSMakePoint(8, oldSize_.height / 2.0f); | |
| 127 | |
| 128 // Retain here, as scoped_* don't retain. | |
| 129 transform_.reset([[NSAffineTransform transform] retain]); | |
| 130 [transform_.get() translateXBy:xCenter.x yBy:xCenter.y]; | |
| 131 [circlePath_.get() transformUsingAffineTransform:transform_.get()]; | |
| 132 [xPath_.get() transformUsingAffineTransform:transform_.get()]; | |
| 133 } | 224 } |
| 134 | 225 |
| 135 @end | 226 @end |
| OLD | NEW |