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) { |
Robert Sesek
2011/07/12 18:11:40
optional nit: no braces for single-line if's
dmac
2011/07/12 19:04:42
ignored... I hate that braces are optional ;-)
| |
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 [hoverNoneLayer_.get() setFrame:viewLayer.frame]; | |
Robert Sesek
2011/07/12 18:11:40
(3x) why .get() here and not elsewhere?
dmac
2011/07/12 19:04:42
commented.
| |
152 [viewLayer addSublayer:hoverNoneLayer_]; | |
153 [hoverNoneLayer_ addConstraint:xConstraint]; | |
154 [hoverNoneLayer_ addConstraint:yConstraint]; | |
155 [hoverNoneLayer_ setContents:reinterpret_cast<id>(gHoverNoneImage)]; | |
156 [hoverNoneLayer_ setHidden:NO]; | |
157 | |
158 hoverMouseOverLayer_.reset([[CALayer alloc] init]); | |
159 [hoverMouseOverLayer_.get() setFrame:viewLayer.frame]; | |
160 [viewLayer addSublayer:hoverMouseOverLayer_]; | |
161 [hoverMouseOverLayer_ addConstraint:xConstraint]; | |
162 [hoverMouseOverLayer_ addConstraint:yConstraint]; | |
163 [hoverMouseOverLayer_ setContents:reinterpret_cast<id>(gHoverMouseOverImage)]; | |
164 [hoverMouseOverLayer_ setHidden:YES]; | |
165 | |
166 hoverMouseDownLayer_.reset([[CALayer alloc] init]); | |
167 [hoverMouseDownLayer_.get() setFrame:viewLayer.frame]; | |
168 [viewLayer addSublayer:hoverMouseDownLayer_]; | |
169 [hoverMouseDownLayer_ addConstraint:xConstraint]; | |
170 [hoverMouseDownLayer_ addConstraint:yConstraint]; | |
171 [hoverMouseDownLayer_ setContents:reinterpret_cast<id>(gHoverMouseDownImage)]; | |
172 [hoverMouseDownLayer_ setHidden:YES]; | |
173 } | |
174 | |
175 + (CGImageRef)imageForBounds:(NSRect)bounds | |
176 xPath:(NSBezierPath*)xPath | |
177 circlePath:(NSBezierPath*)circlePath | |
178 hoverState:(HoverState)hoverState { | |
179 gfx::ScopedNSGraphicsContextSaveGState graphicsStateSaver; | |
180 | |
181 scoped_nsobject<NSBitmapImageRep> imageRep( | |
182 [[NSBitmapImageRep alloc] | |
183 initWithBitmapDataPlanes:NULL | |
184 pixelsWide:NSWidth(bounds) | |
185 pixelsHigh:NSHeight(bounds) | |
186 bitsPerSample:8 | |
187 samplesPerPixel:4 | |
188 hasAlpha:YES | |
189 isPlanar:NO | |
190 colorSpaceName:NSCalibratedRGBColorSpace | |
191 bitmapFormat:0 | |
192 bytesPerRow:kButtonWidth * 4 | |
193 bitsPerPixel:32]); | |
194 NSGraphicsContext* gc = | |
195 [NSGraphicsContext graphicsContextWithBitmapImageRep:imageRep]; | |
196 [NSGraphicsContext setCurrentContext:gc]; | |
197 | |
198 [[NSColor clearColor] set]; | |
199 NSRectFill(bounds); | |
46 | 200 |
47 // If the user is hovering over the button, a light/dark gray circle is drawn | 201 // If the user is hovering over the button, a light/dark gray circle is drawn |
48 // behind the 'x'. | 202 // behind the 'x'. |
49 if (hoverState_ != kHoverStateNone) { | 203 if (hoverState != kHoverStateNone) { |
50 // Adjust the darkness of the circle depending on whether it is being | 204 // Adjust the darkness of the circle depending on whether it is being |
51 // clicked. | 205 // clicked. |
52 CGFloat white = (hoverState_ == kHoverStateMouseOver) ? | 206 CGFloat white = (hoverState == kHoverStateMouseOver) ? |
53 kCircleHoverWhite : kCircleClickWhite; | 207 kCircleHoverWhite : kCircleClickWhite; |
54 [[NSColor colorWithCalibratedWhite:white alpha:1.0] set]; | 208 [[NSColor colorWithCalibratedWhite:white alpha:1.0] set]; |
55 [circlePath_ fill]; | 209 [circlePath fill]; |
56 } | 210 } |
57 | 211 |
58 [[NSColor whiteColor] set]; | 212 [[NSColor whiteColor] set]; |
59 [xPath_ fill]; | 213 [xPath fill]; |
60 | 214 |
61 // Give the 'x' an inner shadow for depth. If the button is in a hover state | 215 // 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). | 216 // (circle behind it), then adjust the shadow accordingly (not as harsh). |
63 NSShadow* shadow = [[[NSShadow alloc] init] autorelease]; | 217 NSShadow* shadow = [[[NSShadow alloc] init] autorelease]; |
64 CGFloat alpha = (hoverState_ != kHoverStateNone) ? | 218 CGFloat alpha = (hoverState != kHoverStateNone) ? |
65 kXShadowCircleAlpha : kXShadowAlpha; | 219 kXShadowCircleAlpha : kXShadowAlpha; |
66 [shadow setShadowColor:[NSColor colorWithCalibratedWhite:0.15 | 220 [shadow setShadowColor:[NSColor colorWithCalibratedWhite:0.15 alpha:alpha]]; |
67 alpha:alpha]]; | |
68 [shadow setShadowOffset:NSMakeSize(0.0, 0.0)]; | 221 [shadow setShadowOffset:NSMakeSize(0.0, 0.0)]; |
69 [shadow setShadowBlurRadius:2.5]; | 222 [shadow setShadowBlurRadius:2.5]; |
70 [xPath_ fillWithInnerShadow:shadow]; | 223 [xPath fillWithInnerShadow:shadow]; |
71 } | 224 return [imageRep CGImage]; |
Robert Sesek
2011/07/12 18:11:40
Memory management here may warrant a function-leve
dmac
2011/07/12 19:04:42
Done.
| |
72 | |
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 } | 225 } |
134 | 226 |
135 @end | 227 @end |
OLD | NEW |