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 |