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 <cmath> |
| 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_) |
| 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 |