Chromium Code Reviews| Index: chrome/browser/ui/cocoa/draggable_button_mixin.mm |
| diff --git a/chrome/browser/ui/cocoa/draggable_button.mm b/chrome/browser/ui/cocoa/draggable_button_mixin.mm |
| similarity index 50% |
| copy from chrome/browser/ui/cocoa/draggable_button.mm |
| copy to chrome/browser/ui/cocoa/draggable_button_mixin.mm |
| index 4d03b2790ec78127546126e082bdf5dd4854b47c..a005a8a05df587470c43d5a2da7c0a84044675dc 100644 |
| --- a/chrome/browser/ui/cocoa/draggable_button.mm |
| +++ b/chrome/browser/ui/cocoa/draggable_button_mixin.mm |
| @@ -4,6 +4,8 @@ |
| #import "chrome/browser/ui/cocoa/draggable_button.h" |
| +#include <complex> |
|
Mark Mentovai
2011/08/11 00:51:17
<complex> is for std::abs on complex<> values. You
|
| + |
| #include "base/logging.h" |
| #import "base/memory/scoped_nsobject.h" |
| @@ -17,7 +19,32 @@ const CGFloat kDragExpirationTimeout = 0.45; |
| } |
| -@implementation DraggableButton |
| +// Private ///////////////////////////////////////////////////////////////////// |
| + |
| +@interface DraggableButtonImpl (Private) |
| + |
| +- (BOOL)deltaIndicatesDragStartWithXDelta:(float)xDelta |
| + yDelta:(float)yDelta |
| + xHysteresis:(float)xHysteresis |
| + yHysteresis:(float)yHysteresis; |
| +- (BOOL)deltaIndicatesConclusionReachedWithXDelta:(float)xDelta |
| + yDelta:(float)yDelta |
| + xHysteresis:(float)xHysteresis |
| + yHysteresis:(float)yHysteresis; |
| +- (void)performMouseDownAction:(NSEvent*)theEvent; |
| +- (void)endDrag; |
| +- (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent |
| + withExpiration:(NSDate*)expiration; |
| +- (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent |
| + withExpiration:(NSDate*)expiration |
| + xHysteresis:(float)xHysteresis |
| + yHysteresis:(float)yHysteresis; |
| + |
| +@end |
| + |
| +// Implementation ////////////////////////////////////////////////////////////// |
| + |
| +@implementation DraggableButtonImpl |
| @synthesize draggable = draggable_; |
| @synthesize actsOnMouseDown = actsOnMouseDown_; |
| @@ -25,9 +52,9 @@ const CGFloat kDragExpirationTimeout = 0.45; |
| @synthesize actionHasFired = actionHasFired_; |
| @synthesize whenMouseDown = whenMouseDown_; |
| - |
| -- (id)initWithFrame:(NSRect)frame { |
| - if ((self = [super initWithFrame:frame])) { |
| +- (id)initWithButton:(NSButton<DraggableButtonMixin>*)button { |
| + if ((self = [super init])) { |
| + button_ = button; |
| draggable_ = YES; |
| actsOnMouseDown_ = NO; |
| actionHasFired_ = NO; |
| @@ -35,29 +62,117 @@ const CGFloat kDragExpirationTimeout = 0.45; |
| return self; |
| } |
| -- (id)initWithCoder:(NSCoder*)coder { |
| - if ((self = [super initWithCoder:coder])) { |
| - draggable_ = YES; |
| - actsOnMouseDown_ = NO; |
| - actionHasFired_ = NO; |
| +// NSButton/NSResponder Implementations //////////////////////////////////////// |
| + |
| +- (DraggableButtonResult)mouseUp:(NSEvent*)theEvent { |
| + durationMouseWasDown_ = [theEvent timestamp] - whenMouseDown_; |
| + |
| + if (actionHasFired_) |
|
Mark Mentovai
2011/08/11 00:51:17
Here’s where the turd pile begins, but it’s actual
|
| + return kDraggableButtonImplDidWork; |
| + |
| + if (!draggable_) |
| + return kDraggableButtonMixinCallSuper; |
| + |
| + // There are non-drag cases where a |-mouseUp:| may happen (e.g. mouse-down, |
| + // cmd-tab to another application, move mouse, mouse-up), so check. |
| + NSPoint viewLocal = [button_ convertPoint:[theEvent locationInWindow] |
| + fromView:[[button_ window] contentView]]; |
| + if (NSPointInRect(viewLocal, [button_ bounds])) |
| + [button_ performClick:self]; |
| + |
| + return kDraggableButtonImplDidWork; |
| +} |
| + |
| +// Mimic "begin a click" operation visually. Do NOT follow through with normal |
| +// button event handling. |
| +- (DraggableButtonResult)mouseDown:(NSEvent*)theEvent { |
| + [[NSCursor arrowCursor] set]; |
| + |
| + whenMouseDown_ = [theEvent timestamp]; |
| + actionHasFired_ = NO; |
| + |
| + if (draggable_) { |
| + NSDate* date = [NSDate dateWithTimeIntervalSinceNow:kDragExpirationTimeout]; |
| + if ([self dragShouldBeginFromMouseDown:theEvent |
| + withExpiration:date]) { |
| + [button_ beginDrag:theEvent]; |
| + [self endDrag]; |
| + } else { |
| + if (actsOnMouseDown_) { |
| + [self performMouseDownAction:theEvent]; |
| + return kDraggableButtonImplDidWork; |
| + } else { |
| + return kDraggableButtonMixinCallSuper; |
| + } |
| + } |
| + } else { |
| + if (actsOnMouseDown_) { |
| + [self performMouseDownAction:theEvent]; |
| + return kDraggableButtonImplDidWork; |
| + } else { |
| + return kDraggableButtonMixinCallSuper; |
| + } |
| } |
| - return self; |
| + |
| + return kDraggableButtonImplDidWork; |
| } |
| +// Idempotent Helpers ////////////////////////////////////////////////////////// |
| + |
| - (BOOL)deltaIndicatesDragStartWithXDelta:(float)xDelta |
| yDelta:(float)yDelta |
| xHysteresis:(float)xHysteresis |
| yHysteresis:(float)yHysteresis { |
| - return (ABS(xDelta) >= xHysteresis) || (ABS(yDelta) >= yHysteresis); |
| + if ([button_ respondsToSelector:@selector(deltaIndicatesDragStartWithXDelta: |
| + yDelta: |
| + xHysteresis: |
| + yHysteresis: |
| + indicates:)]) { |
| + BOOL indicates = NO; |
| + DraggableButtonResult result = [button_ |
| + deltaIndicatesDragStartWithXDelta:xDelta |
| + yDelta:yDelta |
| + xHysteresis:xHysteresis |
| + yHysteresis:yHysteresis |
| + indicates:&indicates]; |
| + if (result != kDraggableButtonImplUseBase) |
| + return indicates; |
| + } |
| + return (std::abs(xDelta) >= xHysteresis) || (std::abs(yDelta) >= yHysteresis); |
| } |
| - (BOOL)deltaIndicatesConclusionReachedWithXDelta:(float)xDelta |
| yDelta:(float)yDelta |
| xHysteresis:(float)xHysteresis |
| yHysteresis:(float)yHysteresis { |
| - return (ABS(xDelta) >= xHysteresis) || (ABS(yDelta) >= yHysteresis); |
| + if ([button_ respondsToSelector: |
| + @selector(deltaIndicatesConclusionReachedWithXDelta: |
| + yDelta: |
| + xHysteresis: |
| + yHysteresis: |
| + indicates:)]) { |
| + BOOL indicates = NO; |
| + DraggableButtonResult result = [button_ |
| + deltaIndicatesConclusionReachedWithXDelta:xDelta |
| + yDelta:yDelta |
| + xHysteresis:xHysteresis |
| + yHysteresis:yHysteresis |
| + indicates:&indicates]; |
| + if (result != kDraggableButtonImplUseBase) |
| + return indicates; |
| + } |
| + return (std::abs(xDelta) >= xHysteresis) || (std::abs(yDelta) >= yHysteresis); |
| +} |
| + |
| +- (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent |
| + withExpiration:(NSDate*)expiration { |
| + return [self dragShouldBeginFromMouseDown:mouseDownEvent |
| + withExpiration:expiration |
| + xHysteresis:kWebDragStartHysteresisX |
| + yHysteresis:kWebDragStartHysteresisY]; |
| } |
| +// Implementation Details ////////////////////////////////////////////////////// |
| // Determine whether a mouse down should turn into a drag; started as copy of |
| // NSTableView code. |
| @@ -75,8 +190,8 @@ const CGFloat kDragExpirationTimeout = 0.45; |
| NSEvent* mouseUp = nil; |
| BOOL dragIt = NO; |
| - while ((nextEvent = [[self window] |
| - nextEventMatchingMask:(NSLeftMouseUpMask | NSLeftMouseDraggedMask) |
| + while ((nextEvent = [[button_ window] |
| + nextEventMatchingMask:NSLeftMouseUpMask | NSLeftMouseDraggedMask |
| untilDate:expiration |
| inMode:NSEventTrackingRunLoopMode |
| dequeue:YES]) != nil) { |
| @@ -122,107 +237,53 @@ const CGFloat kDragExpirationTimeout = 0.45; |
| return dragIt; |
| } |
| -- (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent |
| - withExpiration:(NSDate*)expiration { |
| - return [self dragShouldBeginFromMouseDown:mouseDownEvent |
| - withExpiration:expiration |
| - xHysteresis:kWebDragStartHysteresisX |
| - yHysteresis:kWebDragStartHysteresisY]; |
| -} |
| - |
| -- (void)mouseUp:(NSEvent*)theEvent { |
| - durationMouseWasDown_ = [theEvent timestamp] - whenMouseDown_; |
| - |
| - if (actionHasFired_) |
| - return; |
| - |
| - if (!draggable_) { |
| - [super mouseUp:theEvent]; |
| - return; |
| - } |
| +- (void)secondaryMouseUpAction:(BOOL)wasInside { |
| + if ([button_ respondsToSelector:_cmd]) |
| + [button_ secondaryMouseUpAction:wasInside]; |
| - // There are non-drag cases where a mouseUp: may happen |
| - // (e.g. mouse-down, cmd-tab to another application, move mouse, |
| - // mouse-up). So we check. |
| - NSPoint viewLocal = [self convertPoint:[theEvent locationInWindow] |
| - fromView:[[self window] contentView]]; |
| - if (NSPointInRect(viewLocal, [self bounds])) { |
| - [self performClick:self]; |
| - } |
| + // No actual implementation yet. |
| } |
| -- (void)secondaryMouseUpAction:(BOOL)wasInside { |
| - // Override if you want to do any extra work on mouseUp, after a mouseDown |
| - // action has already fired. |
| -} |
| +- (void)performMouseDownAction:(NSEvent*)event { |
| + if ([button_ respondsToSelector:_cmd] && |
| + [button_ performMouseDownAction:event] != kDraggableButtonImplUseBase) { |
| + return; |
| + } |
| -- (void)performMouseDownAction:(NSEvent*)theEvent { |
| int eventMask = NSLeftMouseUpMask; |
| - [[self target] performSelector:[self action] withObject:self]; |
| + [[button_ target] performSelector:[button_ action] withObject:self]; |
| actionHasFired_ = YES; |
| while (1) { |
| - theEvent = [[self window] nextEventMatchingMask:eventMask]; |
| - if (!theEvent) |
| + event = [[button_ window] nextEventMatchingMask:eventMask]; |
| + if (!event) |
| continue; |
| - NSPoint mouseLoc = [self convertPoint:[theEvent locationInWindow] |
| - fromView:nil]; |
| - BOOL isInside = [self mouse:mouseLoc inRect:[self bounds]]; |
| - [self highlight:isInside]; |
| + NSPoint mouseLoc = [button_ convertPoint:[event locationInWindow] |
| + fromView:nil]; |
| + BOOL isInside = [button_ mouse:mouseLoc inRect:[button_ bounds]]; |
| + [button_ highlight:isInside]; |
| - switch ([theEvent type]) { |
| + switch ([event type]) { |
| case NSLeftMouseUp: |
| - durationMouseWasDown_ = [theEvent timestamp] - whenMouseDown_; |
| + durationMouseWasDown_ = [event timestamp] - whenMouseDown_; |
| [self secondaryMouseUpAction:isInside]; |
| break; |
| default: |
| - /* Ignore any other kind of event. */ |
| + // Ignore any other kind of event. |
| break; |
| } |
| } |
| - [self highlight:NO]; |
| -} |
| - |
| -// Mimic "begin a click" operation visually. Do NOT follow through |
| -// with normal button event handling. |
| -- (void)mouseDown:(NSEvent*)theEvent { |
| - [[NSCursor arrowCursor] set]; |
| - |
| - whenMouseDown_ = [theEvent timestamp]; |
| - actionHasFired_ = NO; |
| - |
| - if (draggable_) { |
| - NSDate* date = [NSDate dateWithTimeIntervalSinceNow:kDragExpirationTimeout]; |
| - if ([self dragShouldBeginFromMouseDown:theEvent |
| - withExpiration:date]) { |
| - [self beginDrag:theEvent]; |
| - [self endDrag]; |
| - } else { |
| - if (actsOnMouseDown_) { |
| - [self performMouseDownAction:theEvent]; |
| - } else { |
| - [super mouseDown:theEvent]; |
| - } |
| - |
| - } |
| - } else { |
| - if (actsOnMouseDown_) { |
| - [self performMouseDownAction:theEvent]; |
| - } else { |
| - [super mouseDown:theEvent]; |
| - } |
| - } |
| -} |
| - |
| -- (void)beginDrag:(NSEvent*)dragEvent { |
| - // Must be overridden by subclasses. |
| - NOTREACHED(); |
| + [button_ highlight:NO]; |
| } |
| - (void)endDrag { |
| - [self highlight:NO]; |
| + if ([button_ respondsToSelector:_cmd] && |
| + [button_ endDrag] != kDraggableButtonImplUseBase) { |
| + return; |
| + } |
| + [button_ highlight:NO]; |
| } |
| -@end // @interface DraggableButton |
| +@end |