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 13NSString* const kBrowserActionGrippyDragStartedNotification = 14 @"BrowserActionGrippyDragStartedNotification"; 15NSString* const kBrowserActionGrippyDraggingNotification = 16 @"BrowserActionGrippyDraggingNotification"; 17NSString* const kBrowserActionGrippyDragFinishedNotification = 18 @"BrowserActionGrippyDragFinishedNotification"; 19 20namespace { 21const CGFloat kAnimationDuration = 0.2; 22const CGFloat kGrippyWidth = 4.0; 23const 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