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