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/extensions/browser_actions_container_view.h" 6 7 #include <algorithm> 8 9 #include "base/basictypes.h" 10 #import "base/memory/scoped_nsobject.h" 11 #import "chrome/browser/ui/cocoa/view_id_util.h" 12 13 NSString* const kBrowserActionGrippyDragStartedNotification = 14 @"BrowserActionGrippyDragStartedNotification"; 15 NSString* const kBrowserActionGrippyDraggingNotification = 16 @"BrowserActionGrippyDraggingNotification"; 17 NSString* const kBrowserActionGrippyDragFinishedNotification = 18 @"BrowserActionGrippyDragFinishedNotification"; 19 20 namespace { 21 const CGFloat kAnimationDuration = 0.2; 22 const CGFloat kGrippyWidth = 4.0; 23 const CGFloat kMinimumContainerWidth = 10.0; 24 } // namespace 25 26 @interface BrowserActionsContainerView(Private) 27 // Returns the cursor that should be shown when hovering over the grippy based 28 // on |canDragLeft_| and |canDragRight_|. 29 - (NSCursor*)appropriateCursorForGrippy; 30 @end 31 32 @implementation BrowserActionsContainerView 33 34 @synthesize animationEndFrame = animationEndFrame_; 35 @synthesize canDragLeft = canDragLeft_; 36 @synthesize canDragRight = canDragRight_; 37 @synthesize grippyPinned = grippyPinned_; 38 @synthesize maxWidth = maxWidth_; 39 @synthesize userIsResizing = userIsResizing_; 40 41 #pragma mark - 42 #pragma mark Overridden Class Functions 43 44 - (id)initWithFrame:(NSRect)frameRect { 45 if ((self = [super initWithFrame:frameRect])) { 46 grippyRect_ = NSMakeRect(0.0, 0.0, kGrippyWidth, NSHeight([self bounds])); 47 canDragLeft_ = YES; 48 canDragRight_ = YES; 49 resizable_ = YES; 50 [self setHidden:YES]; 51 } 52 return self; 53 } 54 55 - (void)setResizable:(BOOL)resizable { 56 if (resizable == resizable_) 57 return; 58 resizable_ = resizable; 59 [self setNeedsDisplay:YES]; 60 } 61 62 - (BOOL)isResizable { 63 return resizable_; 64 } 65 66 - (void)resetCursorRects { 67 [self discardCursorRects]; 68 [self addCursorRect:grippyRect_ cursor:[self appropriateCursorForGrippy]]; 69 } 70 71 - (BOOL)acceptsFirstResponder { 72 return YES; 73 } 74 75 - (void)mouseDown:(NSEvent*)theEvent { 76 initialDragPoint_ = [self convertPoint:[theEvent locationInWindow] 77 fromView:nil]; 78 if (!resizable_ || 79 !NSMouseInRect(initialDragPoint_, grippyRect_, [self isFlipped])) 80 return; 81 82 lastXPos_ = [self frame].origin.x; 83 userIsResizing_ = YES; 84 85 [[self appropriateCursorForGrippy] push]; 86 // Disable cursor rects so that the Omnibox and other UI elements don't push 87 // cursors while the user is dragging. The cursor should be grippy until 88 // the |-mouseUp:| message is received. 89 [[self window] disableCursorRects]; 90 91 [[NSNotificationCenter defaultCenter] 92 postNotificationName:kBrowserActionGrippyDragStartedNotification 93 object:self]; 94 } 95 96 - (void)mouseUp:(NSEvent*)theEvent { 97 if (!userIsResizing_) 98 return; 99 100 [NSCursor pop]; 101 [[self window] enableCursorRects]; 102 103 userIsResizing_ = NO; 104 [[NSNotificationCenter defaultCenter] 105 postNotificationName:kBrowserActionGrippyDragFinishedNotification 106 object:self]; 107 } 108 109 - (void)mouseDragged:(NSEvent*)theEvent { 110 if (!userIsResizing_) 111 return; 112 113 NSPoint location = [self convertPoint:[theEvent locationInWindow] 114 fromView:nil]; 115 NSRect containerFrame = [self frame]; 116 CGFloat dX = [theEvent deltaX]; 117 CGFloat withDelta = location.x - dX; 118 canDragRight_ = (withDelta >= initialDragPoint_.x) && 119 (NSWidth(containerFrame) > kMinimumContainerWidth); 120 canDragLeft_ = (withDelta <= initialDragPoint_.x) && 121 (NSWidth(containerFrame) < maxWidth_); 122 if ((dX < 0.0 && !canDragLeft_) || (dX > 0.0 && !canDragRight_)) 123 return; 124 125 containerFrame.size.width = 126 std::max(NSWidth(containerFrame) - dX, kMinimumContainerWidth); 127 128 if (NSWidth(containerFrame) == kMinimumContainerWidth) 129 return; 130 131 containerFrame.origin.x += dX; 132 133 [self setFrame:containerFrame]; 134 [self setNeedsDisplay:YES]; 135 136 [[NSNotificationCenter defaultCenter] 137 postNotificationName:kBrowserActionGrippyDraggingNotification 138 object:self]; 139 140 lastXPos_ += dX; 141 } 142 143 - (ViewID)viewID { 144 return VIEW_ID_BROWSER_ACTION_TOOLBAR; 145 } 146 147 #pragma mark - 148 #pragma mark Public Methods 149 150 - (void)resizeToWidth:(CGFloat)width animate:(BOOL)animate { 151 width = std::max(width, kMinimumContainerWidth); 152 NSRect frame = [self frame]; 153 lastXPos_ = frame.origin.x; 154 CGFloat dX = frame.size.width - width; 155 frame.size.width = width; 156 NSRect newFrame = NSOffsetRect(frame, dX, 0); 157 if (animate) { 158 [NSAnimationContext beginGrouping]; 159 [[NSAnimationContext currentContext] setDuration:kAnimationDuration]; 160 [[self animator] setFrame:newFrame]; 161 [NSAnimationContext endGrouping]; 162 animationEndFrame_ = newFrame; 163 } else { 164 [self setFrame:newFrame]; 165 [self setNeedsDisplay:YES]; 166 } 167 } 168 169 - (CGFloat)resizeDeltaX { 170 return [self frame].origin.x - lastXPos_; 171 } 172 173 #pragma mark - 174 #pragma mark Private Methods 175 176 // Returns the cursor to display over the grippy hover region depending on the 177 // current drag state. 178 - (NSCursor*)appropriateCursorForGrippy { 179 NSCursor* retVal; 180 if (!resizable_ || (!canDragLeft_ && !canDragRight_)) { 181 retVal = [NSCursor arrowCursor]; 182 } else if (!canDragLeft_) { 183 retVal = [NSCursor resizeRightCursor]; 184 } else if (!canDragRight_) { 185 retVal = [NSCursor resizeLeftCursor]; 186 } else { 187 retVal = [NSCursor resizeLeftRightCursor]; 188 } 189 return retVal; 190 } 191 192 @end 193