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.
5 #import "chrome/browser/ui/cocoa/extensions/browser_actions_container_view.h"
9 #include "base/basictypes.h"
10 #import "chrome/browser/ui/cocoa/view_id_util.h"
12 NSString* const kBrowserActionGrippyDragStartedNotification =
13 @"BrowserActionGrippyDragStartedNotification";
14 NSString* const kBrowserActionGrippyDraggingNotification =
15 @"BrowserActionGrippyDraggingNotification";
16 NSString* const kBrowserActionGrippyDragFinishedNotification =
17 @"BrowserActionGrippyDragFinishedNotification";
18 NSString* const kBrowserActionsContainerWillAnimate =
19 @"BrowserActionsContainerWillAnimate";
20 NSString* const kBrowserActionsContainerMouseEntered =
21 @"BrowserActionsContainerMouseEntered";
22 NSString* const kTranslationWithDelta =
23 @"TranslationWithDelta";
26 const CGFloat kAnimationDuration = 0.2;
27 const CGFloat kGrippyWidth = 3.0;
28 const CGFloat kMinimumContainerWidth = 3.0;
31 @interface BrowserActionsContainerView(Private)
32 // Returns the cursor that should be shown when hovering over the grippy based
33 // on |canDragLeft_| and |canDragRight_|.
34 - (NSCursor*)appropriateCursorForGrippy;
36 // Returns the maximum allowed size for the container.
37 - (CGFloat)maxAllowedWidth;
40 @implementation BrowserActionsContainerView
42 @synthesize canDragLeft = canDragLeft_;
43 @synthesize canDragRight = canDragRight_;
44 @synthesize grippyPinned = grippyPinned_;
45 @synthesize maxDesiredWidth = maxDesiredWidth_;
46 @synthesize userIsResizing = userIsResizing_;
47 @synthesize delegate = delegate_;
50 #pragma mark Overridden Class Functions
52 - (id)initWithFrame:(NSRect)frameRect {
53 if ((self = [super initWithFrame:frameRect])) {
54 grippyRect_ = NSMakeRect(0.0, 0.0, kGrippyWidth, NSHeight([self bounds]));
59 resizeAnimation_.reset([[NSViewAnimation alloc] init]);
60 [resizeAnimation_ setDuration:kAnimationDuration];
61 [resizeAnimation_ setAnimationBlockingMode:NSAnimationNonblocking];
69 if (trackingArea_.get())
70 [self removeTrackingArea:trackingArea_.get()];
74 - (void)setTrackingEnabled:(BOOL)enabled {
77 [[CrTrackingArea alloc] initWithRect:NSZeroRect
78 options:NSTrackingMouseEnteredAndExited |
79 NSTrackingActiveInActiveApp |
80 NSTrackingInVisibleRect
83 [self addTrackingArea:trackingArea_.get()];
84 } else if (trackingArea_.get()) {
85 [self removeTrackingArea:trackingArea_.get()];
86 [trackingArea_.get() clearOwner];
87 trackingArea_.reset(nil);
91 - (void)setResizable:(BOOL)resizable {
92 if (resizable == resizable_)
94 resizable_ = resizable;
95 [self setNeedsDisplay:YES];
102 - (void)resetCursorRects {
103 [self addCursorRect:grippyRect_ cursor:[self appropriateCursorForGrippy]];
106 - (BOOL)acceptsFirstResponder {
110 - (void)mouseEntered:(NSEvent*)theEvent {
111 [[NSNotificationCenter defaultCenter]
112 postNotificationName:kBrowserActionsContainerMouseEntered
116 - (void)mouseDown:(NSEvent*)theEvent {
117 initialDragPoint_ = [self convertPoint:[theEvent locationInWindow]
120 !NSMouseInRect(initialDragPoint_, grippyRect_, [self isFlipped]))
123 userIsResizing_ = YES;
125 [[self appropriateCursorForGrippy] push];
126 // Disable cursor rects so that the Omnibox and other UI elements don't push
127 // cursors while the user is dragging. The cursor should be grippy until
128 // the |-mouseUp:| message is received.
129 [[self window] disableCursorRects];
131 [[NSNotificationCenter defaultCenter]
132 postNotificationName:kBrowserActionGrippyDragStartedNotification
136 - (void)mouseUp:(NSEvent*)theEvent {
137 if (!userIsResizing_)
141 [[self window] enableCursorRects];
143 userIsResizing_ = NO;
144 [[NSNotificationCenter defaultCenter]
145 postNotificationName:kBrowserActionGrippyDragFinishedNotification
149 - (void)mouseDragged:(NSEvent*)theEvent {
150 if (!userIsResizing_)
153 NSPoint location = [self convertPoint:[theEvent locationInWindow]
155 NSRect containerFrame = [self frame];
156 CGFloat dX = [theEvent deltaX];
157 CGFloat withDelta = location.x - dX;
158 canDragRight_ = (withDelta >= initialDragPoint_.x) &&
159 (NSWidth(containerFrame) > kMinimumContainerWidth);
160 CGFloat maxAllowedWidth = [self maxAllowedWidth];
161 containerFrame.size.width =
162 std::max(NSWidth(containerFrame) - dX, kMinimumContainerWidth);
163 canDragLeft_ = withDelta <= initialDragPoint_.x &&
164 NSWidth(containerFrame) < maxDesiredWidth_ &&
165 NSWidth(containerFrame) < maxAllowedWidth;
167 if ((dX < 0.0 && !canDragLeft_) || (dX > 0.0 && !canDragRight_))
170 if (NSWidth(containerFrame) <= kMinimumContainerWidth)
173 grippyPinned_ = NSWidth(containerFrame) >= maxAllowedWidth;
174 containerFrame.origin.x += dX;
176 [self setFrame:containerFrame];
177 [self setNeedsDisplay:YES];
179 [[NSNotificationCenter defaultCenter]
180 postNotificationName:kBrowserActionGrippyDraggingNotification
185 return VIEW_ID_BROWSER_ACTION_TOOLBAR;
189 #pragma mark Public Methods
191 - (void)resizeToWidth:(CGFloat)width animate:(BOOL)animate {
192 width = std::max(width, kMinimumContainerWidth);
193 NSRect frame = [self frame];
195 CGFloat maxAllowedWidth = [self maxAllowedWidth];
196 width = std::min(maxAllowedWidth, width);
198 CGFloat dX = frame.size.width - width;
199 frame.size.width = width;
200 NSRect newFrame = NSOffsetRect(frame, dX, 0);
202 grippyPinned_ = width == maxAllowedWidth;
204 [self stopAnimation];
207 NSDictionary* animationDictionary = @{
208 NSViewAnimationTargetKey : self,
209 NSViewAnimationStartFrameKey : [NSValue valueWithRect:[self frame]],
210 NSViewAnimationEndFrameKey : [NSValue valueWithRect:newFrame]
212 [resizeAnimation_ setViewAnimations:@[ animationDictionary ]];
213 [resizeAnimation_ startAnimation];
215 [[NSNotificationCenter defaultCenter]
216 postNotificationName:kBrowserActionsContainerWillAnimate
219 [self setFrame:newFrame];
220 [self setNeedsDisplay:YES];
224 - (NSRect)animationEndFrame {
225 if ([resizeAnimation_ isAnimating]) {
226 NSRect endFrame = [[[[resizeAnimation_ viewAnimations] objectAtIndex:0]
227 valueForKey:NSViewAnimationEndFrameKey] rectValue];
234 - (BOOL)isAnimating {
235 return [resizeAnimation_ isAnimating];
238 - (void)stopAnimation {
239 if ([resizeAnimation_ isAnimating])
240 [resizeAnimation_ stopAnimation];
244 #pragma mark Private Methods
246 // Returns the cursor to display over the grippy hover region depending on the
247 // current drag state.
248 - (NSCursor*)appropriateCursorForGrippy {
250 if (!resizable_ || (!canDragLeft_ && !canDragRight_)) {
251 retVal = [NSCursor arrowCursor];
252 } else if (!canDragLeft_) {
253 retVal = [NSCursor resizeRightCursor];
254 } else if (!canDragRight_) {
255 retVal = [NSCursor resizeLeftCursor];
257 retVal = [NSCursor resizeLeftRightCursor];
262 - (CGFloat)maxAllowedWidth {
263 return delegate_ ? delegate_->GetMaxAllowedWidth() : CGFLOAT_MAX;