OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #import "chrome/browser/ui/cocoa/website_settings/split_block_button.h" | |
6 | |
7 #include <cmath> | |
8 | |
9 #include "base/logging.h" | |
10 #include "base/mac/scoped_nsobject.h" | |
11 #include "chrome/grit/generated_resources.h" | |
12 #include "skia/ext/skia_utils_mac.h" | |
13 #import "ui/base/cocoa/menu_controller.h" | |
14 #include "ui/base/l10n/l10n_util_mac.h" | |
15 #include "ui/base/models/simple_menu_model.h" | |
16 | |
17 namespace { | |
18 | |
19 enum MouseLocation { | |
20 kInsideLeftCell, | |
21 kInsideRightCell, | |
22 kNotInside, | |
23 }; | |
24 | |
25 enum CornerType { | |
26 kRounded, | |
27 kAngled, | |
28 }; | |
29 | |
30 NSBezierPath* PathWithCornerStyles(NSRect frame, | |
31 CornerType leftCornerStyle, | |
32 CornerType rightCornerStyle) { | |
33 base::scoped_nsobject<NSBezierPath> path([[NSBezierPath bezierPath] retain]); | |
34 const CGFloat x0 = NSMinX(frame); | |
35 const CGFloat x1 = NSMaxX(frame); | |
36 const CGFloat y0 = NSMinY(frame); | |
37 const CGFloat y1 = NSMaxY(frame); | |
38 const CGFloat radius = 2; | |
39 | |
40 // Start at the center bottom. Draw left and up, including both left corners. | |
41 [path moveToPoint:NSMakePoint(std::floor((x1 - x0) * .5), y0)]; | |
42 if (leftCornerStyle == kAngled) { | |
43 [path lineToPoint:NSMakePoint(x0, y0)]; | |
44 [path lineToPoint:NSMakePoint(x0, y1)]; | |
45 } else { | |
46 [path appendBezierPathWithArcFromPoint:NSMakePoint(x0, y0) | |
47 toPoint:NSMakePoint(x0, y0 + radius) | |
48 radius:radius]; | |
49 [path appendBezierPathWithArcFromPoint:NSMakePoint(x0, y1) | |
50 toPoint:NSMakePoint(x0 + radius, y1) | |
51 radius:radius]; | |
52 } | |
53 // Draw through the upper right-hand and lower-right-hand corners. | |
54 if (rightCornerStyle == kAngled) { | |
55 [path lineToPoint:NSMakePoint(x1, y1)]; | |
56 [path lineToPoint:NSMakePoint(x1, y0)]; | |
57 } else { | |
58 [path appendBezierPathWithArcFromPoint:NSMakePoint(x1, y1) | |
59 toPoint:NSMakePoint(x1, y1 - radius) | |
60 radius:radius]; | |
61 [path appendBezierPathWithArcFromPoint:NSMakePoint(x1, y0) | |
62 toPoint:NSMakePoint(x1 - radius, y0) | |
63 radius:radius]; | |
64 } | |
65 return path.autorelease(); | |
66 } | |
67 | |
68 void DrawBezel(id<ConstrainedWindowButtonDrawableCell>cell, | |
69 CornerType leftCorners, | |
70 CornerType rightCorners, | |
71 NSRect frame, | |
72 NSView* view) { | |
73 if ([cell isMouseInside]) { | |
74 base::scoped_nsobject<NSBezierPath> path( | |
75 [PathWithCornerStyles(frame, leftCorners, rightCorners) retain]); | |
76 [ConstrainedWindowButton DrawBackgroundAndShadowForPath:path | |
77 withCell:cell | |
78 inView:view]; | |
79 [ConstrainedWindowButton DrawInnerHighlightForPath:path | |
80 withCell:cell | |
81 inView:view]; | |
82 } | |
83 } | |
84 | |
85 } // namespace | |
86 | |
87 // A button cell used by SplitBlockButton, containing the title. | |
88 @interface SplitButtonTitleCell : ConstrainedWindowButtonCell | |
89 - (NSRect)rect; | |
90 @end | |
91 | |
92 // A button cell used by SplitBlockButton, containing the popup menu. | |
93 @interface SplitButtonPopUpCell : | |
94 NSPopUpButtonCell<ConstrainedWindowButtonDrawableCell> { | |
95 @private | |
96 BOOL isMouseInside_; | |
97 base::scoped_nsobject<MenuController> menuController_; | |
98 std::unique_ptr<ui::SimpleMenuModel> menuModel_; | |
99 } | |
100 | |
101 // Designated initializer. | |
102 - (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate; | |
103 | |
104 - (NSRect)rect; | |
105 | |
106 @end | |
107 | |
108 @implementation SplitBlockButton | |
109 | |
110 - (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate { | |
111 if (self = [super initWithFrame:NSZeroRect]) { | |
112 leftCell_.reset([[SplitButtonTitleCell alloc] init]); | |
113 rightCell_.reset([[SplitButtonPopUpCell alloc] | |
114 initWithMenuDelegate:menuDelegate]); | |
115 [leftCell_ setTitle:l10n_util::GetNSString(IDS_PERMISSION_DENY)]; | |
116 [leftCell_ setEnabled:YES]; | |
117 [rightCell_ setEnabled:YES]; | |
118 } | |
119 return self; | |
120 } | |
121 | |
122 + (Class)cellClass { | |
123 return nil; | |
124 } | |
125 | |
126 - (NSString*)title { | |
127 return [leftCell_ title]; | |
128 } | |
129 | |
130 - (void)setAction:(SEL)action { | |
131 [leftCell_ setAction:action]; | |
132 } | |
133 | |
134 - (void)setTarget:(id)target { | |
135 [leftCell_ setTarget:target]; | |
136 } | |
137 | |
138 - (void)drawRect:(NSRect)rect { | |
139 // Copy the base class: inset to leave room for the shadow. | |
140 --rect.size.height; | |
141 | |
142 // This function assumes that |rect| is always the same as [self frame]. | |
143 // If that changes, the drawing functions will need to be adjusted. | |
144 const CGFloat radius = 2; | |
145 NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:rect | |
146 xRadius:radius | |
147 yRadius:radius]; | |
148 [ConstrainedWindowButton DrawBackgroundAndShadowForPath:path | |
149 withCell:nil | |
150 inView:self]; | |
151 | |
152 // Use intersection rects for the cell drawing, to ensure the height | |
153 // adjustment is honored. | |
154 [leftCell_ setControlView:self]; | |
155 [leftCell_ drawWithFrame:NSIntersectionRect(rect, [self leftCellRect]) | |
156 inView:self]; | |
157 | |
158 [rightCell_ setControlView:self]; | |
159 [rightCell_ drawWithFrame:NSIntersectionRect(rect, [self rightCellRect]) | |
160 inView:self]; | |
161 | |
162 // Draw the border. | |
163 path = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(rect, 0.5, 0.5) | |
164 xRadius:radius | |
165 yRadius:radius]; | |
166 [ConstrainedWindowButton DrawBorderForPath:path | |
167 withCell:nil | |
168 inView:self]; | |
169 } | |
170 | |
171 - (void)updateTrackingAreas { | |
172 [self updateTrackingArea:&leftTrackingArea_ | |
173 forCell:leftCell_ | |
174 withRect:[self leftCellRect]]; | |
175 | |
176 [self updateTrackingArea:&rightTrackingArea_ | |
177 forCell:rightCell_ | |
178 withRect:[self rightCellRect]]; | |
179 } | |
180 | |
181 - (void)updateTrackingArea:(ui::ScopedCrTrackingArea*)trackingArea | |
182 forCell:(id<ConstrainedWindowButtonDrawableCell>)cell | |
183 withRect:(NSRect)rect { | |
184 DCHECK(trackingArea); | |
185 NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | | |
186 NSTrackingActiveInActiveApp; | |
187 [self removeTrackingArea:trackingArea->get()]; | |
188 trackingArea->reset([[CrTrackingArea alloc] initWithRect:rect | |
189 options:options | |
190 owner:self | |
191 userInfo:nil]); | |
192 [self addTrackingArea:trackingArea->get()]; | |
193 } | |
194 | |
195 - (void)mouseEntered:(NSEvent*)theEvent { | |
196 [self mouseMoved:theEvent]; | |
197 } | |
198 | |
199 - (void)mouseExited:(NSEvent*)theEvent { | |
200 [self mouseMoved:theEvent]; | |
201 } | |
202 | |
203 - (void)mouseMoved:(NSEvent*)theEvent { | |
204 MouseLocation location = [self mouseLocationForEvent:theEvent]; | |
205 [rightCell_ setIsMouseInside:NO]; | |
206 [leftCell_ setIsMouseInside:NO]; | |
207 if (location == kInsideLeftCell) | |
208 [leftCell_ setIsMouseInside:YES]; | |
209 else if (location == kInsideRightCell) | |
210 [rightCell_ setIsMouseInside:YES]; | |
211 [self setNeedsDisplay:YES]; | |
212 } | |
213 | |
214 - (void)mouseDown:(NSEvent*)theEvent { | |
215 MouseLocation downLocation = [self mouseLocationForEvent:theEvent]; | |
216 NSCell* focusCell = nil; | |
217 NSRect rect; | |
218 if (downLocation == kInsideLeftCell) { | |
219 focusCell = leftCell_.get(); | |
220 rect = [self leftCellRect]; | |
221 } else if (downLocation == kInsideRightCell) { | |
222 focusCell = rightCell_.get(); | |
223 rect = [self rightCellRect]; | |
224 } | |
225 | |
226 do { | |
227 MouseLocation location = [self mouseLocationForEvent:theEvent]; | |
228 if (location != kNotInside) { | |
229 [focusCell setHighlighted:YES]; | |
230 [self setNeedsDisplay:YES]; | |
231 | |
232 if ([focusCell trackMouse:theEvent | |
233 inRect:rect | |
234 ofView:self | |
235 untilMouseUp:NO]) { | |
236 [focusCell setState:![focusCell state]]; | |
237 [self setNeedsDisplay:YES]; | |
238 break; | |
239 } else { | |
240 // The above -trackMouse call returned NO, so we know that | |
241 // the mouse left the cell before a mouse up event occurred. | |
242 [focusCell setHighlighted:NO]; | |
243 [self setNeedsDisplay:YES]; | |
244 } | |
245 } | |
246 const NSUInteger mask = NSLeftMouseUpMask | NSLeftMouseDraggedMask; | |
247 theEvent = [[self window] nextEventMatchingMask:mask]; | |
248 } while ([theEvent type] != NSLeftMouseUp); | |
249 } | |
250 | |
251 - (MouseLocation)mouseLocationForEvent:(NSEvent*)theEvent { | |
252 MouseLocation location = kNotInside; | |
253 NSPoint mousePoint = [self convertPoint:[theEvent locationInWindow] | |
254 fromView:nil]; | |
255 if ([self mouse:mousePoint inRect:[leftCell_ rect]]) | |
256 location = kInsideLeftCell; | |
257 else if ([self mouse:mousePoint inRect:[self rightCellRect]]) | |
258 location = kInsideRightCell; | |
259 return location; | |
260 } | |
261 | |
262 - (void)sizeToFit { | |
263 NSSize leftSize = [leftCell_ cellSize]; | |
264 NSSize rightSize = [rightCell_ cellSize]; | |
265 NSSize size = NSMakeSize( | |
266 std::ceil(std::max(leftSize.width + rightSize.width, | |
267 constrained_window_button::kButtonMinWidth)), | |
268 std::ceil(std::max(leftSize.height, rightSize.height))); | |
269 [self setFrameSize:size]; | |
270 } | |
271 | |
272 - (NSRect)leftCellRect { | |
273 return [leftCell_ rect]; | |
274 } | |
275 | |
276 - (NSRect)rightCellRect { | |
277 NSRect leftFrame, rightFrame; | |
278 NSDivideRect([self bounds], &leftFrame, &rightFrame, | |
279 NSWidth([self leftCellRect]), NSMinXEdge); | |
280 return rightFrame; | |
281 } | |
282 | |
283 // Accessor for Testing. | |
284 - (NSMenu*)menu { | |
285 return [rightCell_ menu]; | |
286 } | |
287 | |
288 @end | |
289 | |
290 @implementation SplitButtonTitleCell | |
291 | |
292 - (void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView { | |
293 DrawBezel(self, kRounded, kAngled, frame, controlView); | |
294 } | |
295 | |
296 - (NSRect)rect { | |
297 NSSize size = [self cellSize]; | |
298 return NSMakeRect(0, 0, std::ceil(size.width), std::ceil(size.height)); | |
299 } | |
300 | |
301 @end | |
302 | |
303 @implementation SplitButtonPopUpCell | |
304 | |
305 @synthesize isMouseInside = isMouseInside_; | |
306 | |
307 - (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate { | |
308 if (self = [super initTextCell:@"" pullsDown:YES]) { | |
309 [self setControlSize:NSSmallControlSize]; | |
310 [self setArrowPosition:NSPopUpArrowAtCenter]; | |
311 [self setBordered:NO]; | |
312 [self setBackgroundColor:[NSColor clearColor]]; | |
313 menuModel_.reset(new ui::SimpleMenuModel(menuDelegate)); | |
314 menuModel_->AddItemWithStringId(0, IDS_PERMISSION_CUSTOMIZE); | |
315 menuController_.reset( | |
316 [[MenuController alloc] initWithModel:menuModel_.get() | |
317 useWithPopUpButtonCell:NO]); | |
318 [self setMenu:[menuController_ menu]]; | |
319 [self setUsesItemFromMenu:NO]; | |
320 } | |
321 return self; | |
322 } | |
323 | |
324 - (void)drawBorderAndBackgroundWithFrame:(NSRect)frame | |
325 inView:(NSView*)controlView { | |
326 // The arrow, which is what should be drawn by the base class, is drawn | |
327 // during -drawBezelWithFrame. The only way to draw our own border with | |
328 // the default arrow is to make the cell unbordered, and draw the border | |
329 // from -drawBorderAndBackgroundWithFrame, rather than simply overriding | |
330 // -drawBezelWithFrame. | |
331 DrawBezel(self, kAngled, kRounded, frame, controlView); | |
332 [super drawBorderAndBackgroundWithFrame:NSOffsetRect(frame, -4, 0) | |
333 inView:controlView]; | |
334 } | |
335 | |
336 - (NSRect)rect { | |
337 NSSize size = [self cellSize]; | |
338 return NSMakeRect(0, 0, std::ceil(size.width), std::ceil(size.height)); | |
339 } | |
340 | |
341 @end | |
OLD | NEW |