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 3 // found in the LICENSE file. 4 5 #import "chrome/browser/ui/cocoa/wrench_menu/menu_tracked_button.h" 6 7 #include "base/mac/mac_util.h" 8 9 @interface MenuTrackedButton (Private) 10 - (void)doHighlight:(BOOL)highlight; 11 - (void)checkMouseInRect; 12 - (NSRect)insetBounds; 13 @end 14 15 @implementation MenuTrackedButton 16 17 @synthesize tracking = tracking_; 18 19 - (void)updateTrackingAreas { 20 [super updateTrackingAreas]; 21 [self removeTrackingRect:trackingTag_]; 22 trackingTag_ = [self addTrackingRect:NSInsetRect([self bounds], 1, 1) 23 owner:self 24 userData:NULL 25 assumeInside:NO]; 26 } 27 28 - (void)viewDidMoveToWindow { 29 [self updateTrackingAreas]; 30 [self doHighlight:NO]; 31 } 32 33 - (void)mouseEntered:(NSEvent*)theEvent { 34 if (!tracking_) { 35 didEnter_ = YES; 36 } 37 [self doHighlight:YES]; 38 [super mouseEntered:theEvent]; 39 } 40 41 - (void)mouseExited:(NSEvent*)theEvent { 42 didEnter_ = NO; 43 tracking_ = NO; 44 [self doHighlight:NO]; 45 [super mouseExited:theEvent]; 46 } 47 48 - (void)mouseDragged:(NSEvent*)theEvent { 49 tracking_ = !didEnter_; 50 51 NSPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; 52 BOOL highlight = NSPointInRect(point, [self insetBounds]); 53 [self doHighlight:highlight]; 54 55 // If tracking in non-sticky mode, poll the mouse cursor to see if it is still 56 // over the button and thus needs to be highlighted. The delay is the 57 // smallest that still produces the effect while minimizing jank. Smaller 58 // values make the selector fire too close to immediately/now for the mouse to 59 // have moved off the receiver, and larger values produce lag. 60 if (tracking_) { 61 [self performSelector:@selector(checkMouseInRect) 62 withObject:nil 63 afterDelay:0.05 64 inModes:[NSArray arrayWithObject:NSEventTrackingRunLoopMode]]; 65 } 66 [super mouseDragged:theEvent]; 67 } 68 69 - (void)mouseUp:(NSEvent*)theEvent { 70 [self doHighlight:NO]; 71 if (!tracking_) { 72 return [super mouseUp:theEvent]; 73 } 74 [self performClick:self]; 75 tracking_ = NO; 76 } 77 78 - (void)doHighlight:(BOOL)highlight { 79 [[self cell] setHighlighted:highlight]; 80 [self setNeedsDisplay]; 81 } 82 83 // Checks if the user's current mouse location is over this button. If it is, 84 // the user is merely hovering here. If it is not, then disable the highlight. 85 // If the menu is opened in non-sticky mode, the button does not receive enter/ 86 // exit mouse events and thus polling is necessary. 87 - (void)checkMouseInRect { 88 NSPoint point = [NSEvent mouseLocation]; 89 point = [[self window] convertScreenToBase:point]; 90 point = [self convertPoint:point fromView:nil]; 91 if (!NSPointInRect(point, [self insetBounds])) { 92 [self doHighlight:NO]; 93 } 94 } 95 96 // Returns the bounds of the receiver slightly inset to avoid highlighting both 97 // buttons in a pair that overlap. 98 - (NSRect)insetBounds { 99 return NSInsetRect([self bounds], 2, 1); 100 } 101 102 @end 103