| 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..a704498046064e48cb597e8f90ef40d896f2acc9 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 <cmath>
|
| +
|
| #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_)
|
| + 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
|
|
|