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/draggable_button.h" | 5 #import "chrome/browser/ui/cocoa/draggable_button.h" |
6 | 6 |
7 #include <complex> | |
Mark Mentovai
2011/08/11 00:51:17
<complex> is for std::abs on complex<> values. You
| |
8 | |
7 #include "base/logging.h" | 9 #include "base/logging.h" |
8 #import "base/memory/scoped_nsobject.h" | 10 #import "base/memory/scoped_nsobject.h" |
9 | 11 |
10 namespace { | 12 namespace { |
11 | 13 |
12 // Code taken from <http://codereview.chromium.org/180036/diff/3001/3004>. | 14 // Code taken from <http://codereview.chromium.org/180036/diff/3001/3004>. |
13 // TODO(viettrungluu): Do we want common, standard code for drag hysteresis? | 15 // TODO(viettrungluu): Do we want common, standard code for drag hysteresis? |
14 const CGFloat kWebDragStartHysteresisX = 5.0; | 16 const CGFloat kWebDragStartHysteresisX = 5.0; |
15 const CGFloat kWebDragStartHysteresisY = 5.0; | 17 const CGFloat kWebDragStartHysteresisY = 5.0; |
16 const CGFloat kDragExpirationTimeout = 0.45; | 18 const CGFloat kDragExpirationTimeout = 0.45; |
17 | 19 |
18 } | 20 } |
19 | 21 |
20 @implementation DraggableButton | 22 // Private ///////////////////////////////////////////////////////////////////// |
23 | |
24 @interface DraggableButtonImpl (Private) | |
25 | |
26 - (BOOL)deltaIndicatesDragStartWithXDelta:(float)xDelta | |
27 yDelta:(float)yDelta | |
28 xHysteresis:(float)xHysteresis | |
29 yHysteresis:(float)yHysteresis; | |
30 - (BOOL)deltaIndicatesConclusionReachedWithXDelta:(float)xDelta | |
31 yDelta:(float)yDelta | |
32 xHysteresis:(float)xHysteresis | |
33 yHysteresis:(float)yHysteresis; | |
34 - (void)performMouseDownAction:(NSEvent*)theEvent; | |
35 - (void)endDrag; | |
36 - (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent | |
37 withExpiration:(NSDate*)expiration; | |
38 - (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent | |
39 withExpiration:(NSDate*)expiration | |
40 xHysteresis:(float)xHysteresis | |
41 yHysteresis:(float)yHysteresis; | |
42 | |
43 @end | |
44 | |
45 // Implementation ////////////////////////////////////////////////////////////// | |
46 | |
47 @implementation DraggableButtonImpl | |
21 | 48 |
22 @synthesize draggable = draggable_; | 49 @synthesize draggable = draggable_; |
23 @synthesize actsOnMouseDown = actsOnMouseDown_; | 50 @synthesize actsOnMouseDown = actsOnMouseDown_; |
24 @synthesize durationMouseWasDown = durationMouseWasDown_; | 51 @synthesize durationMouseWasDown = durationMouseWasDown_; |
25 @synthesize actionHasFired = actionHasFired_; | 52 @synthesize actionHasFired = actionHasFired_; |
26 @synthesize whenMouseDown = whenMouseDown_; | 53 @synthesize whenMouseDown = whenMouseDown_; |
27 | 54 |
28 | 55 - (id)initWithButton:(NSButton<DraggableButtonMixin>*)button { |
29 - (id)initWithFrame:(NSRect)frame { | 56 if ((self = [super init])) { |
30 if ((self = [super initWithFrame:frame])) { | 57 button_ = button; |
31 draggable_ = YES; | 58 draggable_ = YES; |
32 actsOnMouseDown_ = NO; | 59 actsOnMouseDown_ = NO; |
33 actionHasFired_ = NO; | 60 actionHasFired_ = NO; |
34 } | 61 } |
35 return self; | 62 return self; |
36 } | 63 } |
37 | 64 |
38 - (id)initWithCoder:(NSCoder*)coder { | 65 // NSButton/NSResponder Implementations //////////////////////////////////////// |
39 if ((self = [super initWithCoder:coder])) { | 66 |
40 draggable_ = YES; | 67 - (DraggableButtonResult)mouseUp:(NSEvent*)theEvent { |
41 actsOnMouseDown_ = NO; | 68 durationMouseWasDown_ = [theEvent timestamp] - whenMouseDown_; |
42 actionHasFired_ = NO; | 69 |
70 if (actionHasFired_) | |
Mark Mentovai
2011/08/11 00:51:17
Here’s where the turd pile begins, but it’s actual
| |
71 return kDraggableButtonImplDidWork; | |
72 | |
73 if (!draggable_) | |
74 return kDraggableButtonMixinCallSuper; | |
75 | |
76 // There are non-drag cases where a |-mouseUp:| may happen (e.g. mouse-down, | |
77 // cmd-tab to another application, move mouse, mouse-up), so check. | |
78 NSPoint viewLocal = [button_ convertPoint:[theEvent locationInWindow] | |
79 fromView:[[button_ window] contentView]]; | |
80 if (NSPointInRect(viewLocal, [button_ bounds])) | |
81 [button_ performClick:self]; | |
82 | |
83 return kDraggableButtonImplDidWork; | |
84 } | |
85 | |
86 // Mimic "begin a click" operation visually. Do NOT follow through with normal | |
87 // button event handling. | |
88 - (DraggableButtonResult)mouseDown:(NSEvent*)theEvent { | |
89 [[NSCursor arrowCursor] set]; | |
90 | |
91 whenMouseDown_ = [theEvent timestamp]; | |
92 actionHasFired_ = NO; | |
93 | |
94 if (draggable_) { | |
95 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:kDragExpirationTimeout]; | |
96 if ([self dragShouldBeginFromMouseDown:theEvent | |
97 withExpiration:date]) { | |
98 [button_ beginDrag:theEvent]; | |
99 [self endDrag]; | |
100 } else { | |
101 if (actsOnMouseDown_) { | |
102 [self performMouseDownAction:theEvent]; | |
103 return kDraggableButtonImplDidWork; | |
104 } else { | |
105 return kDraggableButtonMixinCallSuper; | |
106 } | |
107 } | |
108 } else { | |
109 if (actsOnMouseDown_) { | |
110 [self performMouseDownAction:theEvent]; | |
111 return kDraggableButtonImplDidWork; | |
112 } else { | |
113 return kDraggableButtonMixinCallSuper; | |
114 } | |
43 } | 115 } |
44 return self; | 116 |
117 return kDraggableButtonImplDidWork; | |
45 } | 118 } |
46 | 119 |
120 // Idempotent Helpers ////////////////////////////////////////////////////////// | |
121 | |
47 - (BOOL)deltaIndicatesDragStartWithXDelta:(float)xDelta | 122 - (BOOL)deltaIndicatesDragStartWithXDelta:(float)xDelta |
48 yDelta:(float)yDelta | 123 yDelta:(float)yDelta |
49 xHysteresis:(float)xHysteresis | 124 xHysteresis:(float)xHysteresis |
50 yHysteresis:(float)yHysteresis { | 125 yHysteresis:(float)yHysteresis { |
51 return (ABS(xDelta) >= xHysteresis) || (ABS(yDelta) >= yHysteresis); | 126 if ([button_ respondsToSelector:@selector(deltaIndicatesDragStartWithXDelta: |
127 yDelta: | |
128 xHysteresis: | |
129 yHysteresis: | |
130 indicates:)]) { | |
131 BOOL indicates = NO; | |
132 DraggableButtonResult result = [button_ | |
133 deltaIndicatesDragStartWithXDelta:xDelta | |
134 yDelta:yDelta | |
135 xHysteresis:xHysteresis | |
136 yHysteresis:yHysteresis | |
137 indicates:&indicates]; | |
138 if (result != kDraggableButtonImplUseBase) | |
139 return indicates; | |
140 } | |
141 return (std::abs(xDelta) >= xHysteresis) || (std::abs(yDelta) >= yHysteresis); | |
52 } | 142 } |
53 | 143 |
54 - (BOOL)deltaIndicatesConclusionReachedWithXDelta:(float)xDelta | 144 - (BOOL)deltaIndicatesConclusionReachedWithXDelta:(float)xDelta |
55 yDelta:(float)yDelta | 145 yDelta:(float)yDelta |
56 xHysteresis:(float)xHysteresis | 146 xHysteresis:(float)xHysteresis |
57 yHysteresis:(float)yHysteresis { | 147 yHysteresis:(float)yHysteresis { |
58 return (ABS(xDelta) >= xHysteresis) || (ABS(yDelta) >= yHysteresis); | 148 if ([button_ respondsToSelector: |
149 @selector(deltaIndicatesConclusionReachedWithXDelta: | |
150 yDelta: | |
151 xHysteresis: | |
152 yHysteresis: | |
153 indicates:)]) { | |
154 BOOL indicates = NO; | |
155 DraggableButtonResult result = [button_ | |
156 deltaIndicatesConclusionReachedWithXDelta:xDelta | |
157 yDelta:yDelta | |
158 xHysteresis:xHysteresis | |
159 yHysteresis:yHysteresis | |
160 indicates:&indicates]; | |
161 if (result != kDraggableButtonImplUseBase) | |
162 return indicates; | |
163 } | |
164 return (std::abs(xDelta) >= xHysteresis) || (std::abs(yDelta) >= yHysteresis); | |
59 } | 165 } |
60 | 166 |
167 - (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent | |
168 withExpiration:(NSDate*)expiration { | |
169 return [self dragShouldBeginFromMouseDown:mouseDownEvent | |
170 withExpiration:expiration | |
171 xHysteresis:kWebDragStartHysteresisX | |
172 yHysteresis:kWebDragStartHysteresisY]; | |
173 } | |
174 | |
175 // Implementation Details ////////////////////////////////////////////////////// | |
61 | 176 |
62 // Determine whether a mouse down should turn into a drag; started as copy of | 177 // Determine whether a mouse down should turn into a drag; started as copy of |
63 // NSTableView code. | 178 // NSTableView code. |
64 - (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent | 179 - (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent |
65 withExpiration:(NSDate*)expiration | 180 withExpiration:(NSDate*)expiration |
66 xHysteresis:(float)xHysteresis | 181 xHysteresis:(float)xHysteresis |
67 yHysteresis:(float)yHysteresis { | 182 yHysteresis:(float)yHysteresis { |
68 if ([mouseDownEvent type] != NSLeftMouseDown) { | 183 if ([mouseDownEvent type] != NSLeftMouseDown) { |
69 return NO; | 184 return NO; |
70 } | 185 } |
71 | 186 |
72 NSEvent* nextEvent = nil; | 187 NSEvent* nextEvent = nil; |
73 NSEvent* firstEvent = nil; | 188 NSEvent* firstEvent = nil; |
74 NSEvent* dragEvent = nil; | 189 NSEvent* dragEvent = nil; |
75 NSEvent* mouseUp = nil; | 190 NSEvent* mouseUp = nil; |
76 BOOL dragIt = NO; | 191 BOOL dragIt = NO; |
77 | 192 |
78 while ((nextEvent = [[self window] | 193 while ((nextEvent = [[button_ window] |
79 nextEventMatchingMask:(NSLeftMouseUpMask | NSLeftMouseDraggedMask) | 194 nextEventMatchingMask:NSLeftMouseUpMask | NSLeftMouseDraggedMask |
80 untilDate:expiration | 195 untilDate:expiration |
81 inMode:NSEventTrackingRunLoopMode | 196 inMode:NSEventTrackingRunLoopMode |
82 dequeue:YES]) != nil) { | 197 dequeue:YES]) != nil) { |
83 if (firstEvent == nil) { | 198 if (firstEvent == nil) { |
84 firstEvent = nextEvent; | 199 firstEvent = nextEvent; |
85 } | 200 } |
86 if ([nextEvent type] == NSLeftMouseDragged) { | 201 if ([nextEvent type] == NSLeftMouseDragged) { |
87 float deltax = [nextEvent locationInWindow].x - | 202 float deltax = [nextEvent locationInWindow].x - |
88 [mouseDownEvent locationInWindow].x; | 203 [mouseDownEvent locationInWindow].x; |
89 float deltay = [nextEvent locationInWindow].y - | 204 float deltay = [nextEvent locationInWindow].y - |
(...skipping 25 matching lines...) Expand all Loading... | |
115 if (dragEvent != nil) { | 230 if (dragEvent != nil) { |
116 [NSApp postEvent:dragEvent atStart:YES]; | 231 [NSApp postEvent:dragEvent atStart:YES]; |
117 } | 232 } |
118 if (firstEvent != mouseUp && firstEvent != dragEvent) { | 233 if (firstEvent != mouseUp && firstEvent != dragEvent) { |
119 [NSApp postEvent:firstEvent atStart:YES]; | 234 [NSApp postEvent:firstEvent atStart:YES]; |
120 } | 235 } |
121 | 236 |
122 return dragIt; | 237 return dragIt; |
123 } | 238 } |
124 | 239 |
125 - (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent | 240 - (void)secondaryMouseUpAction:(BOOL)wasInside { |
126 withExpiration:(NSDate*)expiration { | 241 if ([button_ respondsToSelector:_cmd]) |
127 return [self dragShouldBeginFromMouseDown:mouseDownEvent | 242 [button_ secondaryMouseUpAction:wasInside]; |
128 withExpiration:expiration | 243 |
129 xHysteresis:kWebDragStartHysteresisX | 244 // No actual implementation yet. |
130 yHysteresis:kWebDragStartHysteresisY]; | |
131 } | 245 } |
132 | 246 |
133 - (void)mouseUp:(NSEvent*)theEvent { | 247 - (void)performMouseDownAction:(NSEvent*)event { |
134 durationMouseWasDown_ = [theEvent timestamp] - whenMouseDown_; | 248 if ([button_ respondsToSelector:_cmd] && |
135 | 249 [button_ performMouseDownAction:event] != kDraggableButtonImplUseBase) { |
136 if (actionHasFired_) | 250 return; |
137 return; | |
138 | |
139 if (!draggable_) { | |
140 [super mouseUp:theEvent]; | |
141 return; | |
142 } | 251 } |
143 | 252 |
144 // There are non-drag cases where a mouseUp: may happen | |
145 // (e.g. mouse-down, cmd-tab to another application, move mouse, | |
146 // mouse-up). So we check. | |
147 NSPoint viewLocal = [self convertPoint:[theEvent locationInWindow] | |
148 fromView:[[self window] contentView]]; | |
149 if (NSPointInRect(viewLocal, [self bounds])) { | |
150 [self performClick:self]; | |
151 } | |
152 } | |
153 | |
154 - (void)secondaryMouseUpAction:(BOOL)wasInside { | |
155 // Override if you want to do any extra work on mouseUp, after a mouseDown | |
156 // action has already fired. | |
157 } | |
158 | |
159 - (void)performMouseDownAction:(NSEvent*)theEvent { | |
160 int eventMask = NSLeftMouseUpMask; | 253 int eventMask = NSLeftMouseUpMask; |
161 | 254 |
162 [[self target] performSelector:[self action] withObject:self]; | 255 [[button_ target] performSelector:[button_ action] withObject:self]; |
163 actionHasFired_ = YES; | 256 actionHasFired_ = YES; |
164 | 257 |
165 while (1) { | 258 while (1) { |
166 theEvent = [[self window] nextEventMatchingMask:eventMask]; | 259 event = [[button_ window] nextEventMatchingMask:eventMask]; |
167 if (!theEvent) | 260 if (!event) |
168 continue; | 261 continue; |
169 NSPoint mouseLoc = [self convertPoint:[theEvent locationInWindow] | 262 NSPoint mouseLoc = [button_ convertPoint:[event locationInWindow] |
170 fromView:nil]; | 263 fromView:nil]; |
171 BOOL isInside = [self mouse:mouseLoc inRect:[self bounds]]; | 264 BOOL isInside = [button_ mouse:mouseLoc inRect:[button_ bounds]]; |
172 [self highlight:isInside]; | 265 [button_ highlight:isInside]; |
173 | 266 |
174 switch ([theEvent type]) { | 267 switch ([event type]) { |
175 case NSLeftMouseUp: | 268 case NSLeftMouseUp: |
176 durationMouseWasDown_ = [theEvent timestamp] - whenMouseDown_; | 269 durationMouseWasDown_ = [event timestamp] - whenMouseDown_; |
177 [self secondaryMouseUpAction:isInside]; | 270 [self secondaryMouseUpAction:isInside]; |
178 break; | 271 break; |
179 default: | 272 default: |
180 /* Ignore any other kind of event. */ | 273 // Ignore any other kind of event. |
181 break; | 274 break; |
182 } | 275 } |
183 } | 276 } |
184 | 277 |
185 [self highlight:NO]; | 278 [button_ highlight:NO]; |
186 } | |
187 | |
188 // Mimic "begin a click" operation visually. Do NOT follow through | |
189 // with normal button event handling. | |
190 - (void)mouseDown:(NSEvent*)theEvent { | |
191 [[NSCursor arrowCursor] set]; | |
192 | |
193 whenMouseDown_ = [theEvent timestamp]; | |
194 actionHasFired_ = NO; | |
195 | |
196 if (draggable_) { | |
197 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:kDragExpirationTimeout]; | |
198 if ([self dragShouldBeginFromMouseDown:theEvent | |
199 withExpiration:date]) { | |
200 [self beginDrag:theEvent]; | |
201 [self endDrag]; | |
202 } else { | |
203 if (actsOnMouseDown_) { | |
204 [self performMouseDownAction:theEvent]; | |
205 } else { | |
206 [super mouseDown:theEvent]; | |
207 } | |
208 | |
209 } | |
210 } else { | |
211 if (actsOnMouseDown_) { | |
212 [self performMouseDownAction:theEvent]; | |
213 } else { | |
214 [super mouseDown:theEvent]; | |
215 } | |
216 } | |
217 } | |
218 | |
219 - (void)beginDrag:(NSEvent*)dragEvent { | |
220 // Must be overridden by subclasses. | |
221 NOTREACHED(); | |
222 } | 279 } |
223 | 280 |
224 - (void)endDrag { | 281 - (void)endDrag { |
225 [self highlight:NO]; | 282 if ([button_ respondsToSelector:_cmd] && |
283 [button_ endDrag] != kDraggableButtonImplUseBase) { | |
284 return; | |
285 } | |
286 [button_ highlight:NO]; | |
226 } | 287 } |
227 | 288 |
228 @end // @interface DraggableButton | 289 @end |
OLD | NEW |