diff options
7 files changed, 198 insertions, 17 deletions
diff --git a/chrome/browser/cocoa/extensions/browser_actions_container_view.h b/chrome/browser/cocoa/extensions/browser_actions_container_view.h index 6a50b8b..73cee1b 100644 --- a/chrome/browser/cocoa/extensions/browser_actions_container_view.h +++ b/chrome/browser/cocoa/extensions/browser_actions_container_view.h @@ -7,6 +7,9 @@ #import <Cocoa/Cocoa.h> +#import "base/scoped_nsobject.h" + +@class MenuButton; @class BrowserActionButton; // Sent when a user-initiated drag to resize the container is initiated. @@ -18,10 +21,20 @@ extern const NSString* kBrowserActionGrippyDraggingNotification; // Sent when a user-initiated drag to resize the container has finished. extern const NSString* kBrowserActionGrippyDragFinishedNotification; +// The width of the chevron button in pixels. +extern const CGFloat kChevronWidth; + + // The view that encompasses the Browser Action buttons in the toolbar and // provides mechanisms for resizing. @interface BrowserActionsContainerView : NSView { @private + // The currently running animation. + scoped_nsobject<NSAnimation> animation_; + + // The chevron button used when Browser Actions are hidden. + scoped_nsobject<MenuButton> chevronMenuButton_; + // The frame encompasing the grippy used for resizing the container. NSRect grippyRect_; @@ -58,14 +71,19 @@ extern const NSString* kBrowserActionGrippyDragFinishedNotification; // that |resizeDeltaX| is accurate. - (void)resizeToWidth:(CGFloat)width animate:(BOOL)animate; -// Returns the (visible) button at the given index in the view's hierarchy. -- (BrowserActionButton*)buttonAtIndex:(NSUInteger)index; - // Returns the change in the x-pos of the frame rect during resizing. Meant to // be queried when a NSViewFrameDidChangeNotification is fired to determine // placement of surrounding elements. - (CGFloat)resizeDeltaX; +// Returns whether the chevron button is currently hidden or in the process of +// being hidden (fading out). Will return NO if it is not hidden or is in the +// process of fading in. +- (BOOL)chevronIsHidden; + +// Sets whether to show the chevron button. +- (void)setChevronHidden:(BOOL)hidden animate:(BOOL)animate; + @property(nonatomic) BOOL canDragLeft; @property(nonatomic) BOOL canDragRight; @property(nonatomic) BOOL grippyPinned; diff --git a/chrome/browser/cocoa/extensions/browser_actions_container_view.mm b/chrome/browser/cocoa/extensions/browser_actions_container_view.mm index ce78490..82490a5 100644 --- a/chrome/browser/cocoa/extensions/browser_actions_container_view.mm +++ b/chrome/browser/cocoa/extensions/browser_actions_container_view.mm @@ -4,9 +4,14 @@ #include <algorithm> +#include "app/resource_bundle.h" +#include "base/logging.h" #import "base/scoped_nsobject.h" #import "chrome/browser/cocoa/extensions/browser_action_button.h" #import "chrome/browser/cocoa/extensions/browser_actions_container_view.h" +#import "chrome/browser/cocoa/menu_button.h" +#include "grit/theme_resources.h" +#import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h" extern const NSString* kBrowserActionGrippyDragStartedNotification = @"BrowserActionGrippyDragStartedNotification"; @@ -14,9 +19,12 @@ extern const NSString* kBrowserActionGrippyDraggingNotification = @"BrowserActionGrippyDraggingNotification"; extern const NSString* kBrowserActionGrippyDragFinishedNotification = @"BrowserActionGrippyDragFinishedNotification"; +extern const CGFloat kChevronWidth = 14.0; namespace { -const CGFloat kAnimationDuration = 0.1; +const CGFloat kAnimationDuration = 0.2; +const CGFloat kChevronHeight = 28.0; +const CGFloat kChevronRightPadding = 5.0; const CGFloat kGrippyLowerPadding = 4.0; const CGFloat kGrippyUpperPadding = 8.0; const CGFloat kGrippyWidth = 10.0; @@ -31,6 +39,7 @@ const CGFloat kUpperPadding = 9.0; @interface BrowserActionsContainerView(Private) - (NSCursor*)appropriateCursorForGrippy; - (void)drawGrippy; +- (void)updateChevronPosition; @end @implementation BrowserActionsContainerView @@ -44,6 +53,9 @@ const CGFloat kUpperPadding = 9.0; - (id)initWithFrame:(NSRect)frameRect { if ((self = [super initWithFrame:frameRect])) { grippyRect_ = NSMakeRect(0.0, 0.0, kGrippyWidth, NSHeight([self bounds])); + animation_.reset([[NSViewAnimation alloc] init]); + [animation_ setDuration:kAnimationDuration]; + [animation_ setAnimationBlockingMode:NSAnimationNonblocking]; } return self; } @@ -70,6 +82,11 @@ const CGFloat kUpperPadding = 9.0; [self drawGrippy]; } +- (void)setFrame:(NSRect)frameRect { + [super setFrame:frameRect]; + [self updateChevronPosition]; +} + // Draws the area that the user can use to resize the container. Currently, two // vertical "grip" bars. - (void)drawGrippy { @@ -112,10 +129,6 @@ const CGFloat kUpperPadding = 9.0; [self setNeedsDisplay:YES]; } -- (BrowserActionButton*)buttonAtIndex:(NSUInteger)index { - return [[self subviews] objectAtIndex:index]; -} - // Returns the cursor to display over the grippy hover region depending on the // current drag state. - (NSCursor*)appropriateCursorForGrippy { @@ -147,6 +160,8 @@ const CGFloat kUpperPadding = 9.0; if (!NSMouseInRect(initialDragPoint_, grippyRect_, [self isFlipped])) return; + [self setChevronHidden:YES animate:YES]; + lastXPos_ = [self frame].origin.x; userIsResizing_ = YES; [[NSNotificationCenter defaultCenter] @@ -175,6 +190,9 @@ const CGFloat kUpperPadding = 9.0; } - (void)mouseUp:(NSEvent*)theEvent { + if (!userIsResizing_) + return; + userIsResizing_ = NO; [[NSNotificationCenter defaultCenter] postNotificationName:kBrowserActionGrippyDragFinishedNotification @@ -211,4 +229,75 @@ const CGFloat kUpperPadding = 9.0; return [self frame].origin.x - lastXPos_; } +- (BOOL)chevronIsHidden { + if (!chevronMenuButton_.get()) + return YES; + + if (![animation_ isAnimating]) + return [chevronMenuButton_ isHidden]; + + DCHECK([[animation_ viewAnimations] count] > 0); + + // The chevron is animating in or out. Determine which one and have the return + // value reflect where the animation is headed. + NSString* effect = [[[animation_ viewAnimations] objectAtIndex:0] + valueForKey:NSViewAnimationEffectKey]; + if (effect == NSViewAnimationFadeInEffect) { + return NO; + } else if (effect == NSViewAnimationFadeOutEffect) { + return YES; + } + + NOTREACHED(); + return YES; +} + +- (void)setChevronHidden:(BOOL)hidden animate:(BOOL)animate { + if (hidden == [self chevronIsHidden]) + return; + + if (!chevronMenuButton_.get()) { + chevronMenuButton_.reset([[MenuButton alloc] init]); + [chevronMenuButton_ setBordered:NO]; + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + [chevronMenuButton_ setImage:rb.GetNSImageNamed(IDR_BOOKMARK_BAR_CHEVRONS)]; + [self addSubview:chevronMenuButton_]; + } + + [self updateChevronPosition]; + // Stop any running animation. + [animation_ stopAnimation]; + + if (!animate) { + [chevronMenuButton_ setHidden:hidden]; + return; + } + + NSDictionary* animationDictionary; + if (hidden) { + animationDictionary = [NSDictionary dictionaryWithObjectsAndKeys: + chevronMenuButton_.get(), NSViewAnimationTargetKey, + NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey, + nil]; + } else { + [chevronMenuButton_ setHidden:NO]; + animationDictionary = [NSDictionary dictionaryWithObjectsAndKeys: + chevronMenuButton_.get(), NSViewAnimationTargetKey, + NSViewAnimationFadeInEffect, NSViewAnimationEffectKey, + nil]; + } + [animation_ setViewAnimations: + [NSArray arrayWithObjects:animationDictionary, nil]]; + [animation_ startAnimation]; +} + +- (void)updateChevronPosition { + CGFloat xPos = NSWidth([self frame]) - kChevronWidth - kChevronRightPadding; + NSRect buttonFrame = NSMakeRect(xPos, + kLowerPadding, + kChevronWidth, + kChevronHeight); + [chevronMenuButton_ setFrame:buttonFrame]; +} + @end diff --git a/chrome/browser/cocoa/extensions/browser_actions_controller.h b/chrome/browser/cocoa/extensions/browser_actions_controller.h index 4ff528a..cd39283 100644 --- a/chrome/browser/cocoa/extensions/browser_actions_controller.h +++ b/chrome/browser/cocoa/extensions/browser_actions_controller.h @@ -49,6 +49,9 @@ extern const NSString* kBrowserActionVisibilityChangedNotification; // buttons present in the container view. The ID is a string unique to each // extension. scoped_nsobject<NSMutableDictionary> buttons_; + + // Array of hidden buttons in the correct order in which the user specified. + scoped_nsobject<NSMutableArray> hiddenButtons_; } @property(readonly, nonatomic) BrowserActionsContainerView* containerView; diff --git a/chrome/browser/cocoa/extensions/browser_actions_controller.mm b/chrome/browser/cocoa/extensions/browser_actions_controller.mm index 2d494e4..491c516 100644 --- a/chrome/browser/cocoa/extensions/browser_actions_controller.mm +++ b/chrome/browser/cocoa/extensions/browser_actions_controller.mm @@ -39,13 +39,16 @@ const CGFloat kButtonOpacityLeadPadding = 5.0; withIndex:(NSUInteger)index; - (void)removeActionButtonForExtension:(Extension*)extension; - (CGFloat)containerWidthWithButtonCount:(NSUInteger)buttonCount; +- (NSUInteger)containerButtonCapacity; - (void)repositionActionButtons; - (void)updateButtonOpacityAndDragAbilities; - (void)containerFrameChanged; +- (void)containerDragStart; - (void)containerDragging; - (void)containerDragFinished; - (int)currentTabId; - (bool)shouldDisplayBrowserAction:(Extension*)extension; +- (void)showChevronIfNecessaryWithAnimation:(BOOL)animation; @end // A helper class to proxy extension notifications to the view controller's @@ -133,6 +136,11 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, object:containerView_]; [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(containerDragStart) + name:kBrowserActionGrippyDragStartedNotification + object:containerView_]; + [[NSNotificationCenter defaultCenter] + addObserver:self selector:@selector(containerDragging) name:kBrowserActionGrippyDraggingNotification object:containerView_]; @@ -142,8 +150,10 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, name:kBrowserActionGrippyDragFinishedNotification object:containerView_]; + hiddenButtons_.reset([[NSMutableArray alloc] init]); buttons_.reset([[NSMutableDictionary alloc] init]); [self createButtons]; + [self showChevronIfNecessaryWithAnimation:NO]; } return self; @@ -213,10 +223,14 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, NSString* buttonKey = base::SysUTF8ToNSString(extension->id()); if (!buttonKey) return; + [buttons_ setObject:newButton forKey:buttonKey]; - [containerView_ addSubview:newButton]; - if (index >= [self visibleButtonCount]) + if (index < [self containerButtonCapacity]) { + [containerView_ addSubview:newButton]; + } else { + [hiddenButtons_ addObject:newButton]; [newButton setAlphaValue:0.0]; + } [self repositionActionButtons]; [containerView_ setNeedsDisplay:YES]; @@ -237,6 +251,10 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, return; [button removeFromSuperview]; + // It may or may not be hidden, but it won't matter to NSMutableArray either + // way. + [hiddenButtons_ removeObject:button]; + [buttons_ removeObjectForKey:buttonKey]; if ([buttons_ count] == 0) { // No more buttons? Hide the container. @@ -273,10 +291,22 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, if (buttonCount > 0) { width = kGrippyXOffset + kContainerPadding + (buttonCount * (kBrowserActionWidth + kBrowserActionButtonPadding)); + // Make room for the chevron if necessary. + if ([self buttonCount] != [self visibleButtonCount]) + width += kChevronWidth + kBrowserActionButtonPadding; } return width; } +- (NSUInteger)containerButtonCapacity { + CGFloat containerWidth = [self savedWidth]; + if (containerWidth == 0) + containerWidth = [self containerWidthWithButtonCount:[self buttonCount]]; + + return (containerWidth - kGrippyXOffset + kContainerPadding) / + (kBrowserActionWidth + kBrowserActionButtonPadding); +} + // Resizes the container given the number of visible buttons in the container, // taking into account the size of the grippy. Also updates the persistent // width preference. @@ -285,6 +315,8 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, [self containerWidthWithButtonCount:[self visibleButtonCount]]; [containerView_ resizeToWidth:width animate:animate]; + [self showChevronIfNecessaryWithAnimation:YES]; + if (!profile_->IsOffTheRecord()) profile_->GetPrefs()->SetReal(prefs::kBrowserActionContainerWidth, NSWidth([containerView_ frame])); @@ -322,6 +354,13 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, [self updateButtonOpacityAndDragAbilities]; } +- (void)containerDragStart { + while([hiddenButtons_ count] > 0) { + [containerView_ addSubview:[hiddenButtons_ objectAtIndex:0]]; + [hiddenButtons_ removeObjectAtIndex:0]; + } +} + - (void)containerDragging { [[NSNotificationCenter defaultCenter] postNotificationName:kBrowserActionGrippyDraggingNotification @@ -337,9 +376,14 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, CGFloat intersectionWidth = NSWidth(NSIntersectionRect([containerView_ bounds], buttonFrame)); + // Pad the threshold by 5 pixels in order to have the buttons hide more + // easily. if (([containerView_ grippyPinned] && intersectionWidth > 0) || - (intersectionWidth <= (NSWidth(buttonFrame) / 2))) + (intersectionWidth <= (NSWidth(buttonFrame) / 2) + 5.0)) { [button setAlphaValue:0.0]; + [button removeFromSuperview]; + [hiddenButtons_ addObject:button]; + } } [self resizeContainerWithAnimation:NO]; @@ -350,12 +394,7 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, } - (NSUInteger)visibleButtonCount { - int count = 0; - for (BrowserActionButton* button in [buttons_ allValues]) { - if ([button alphaValue] > 0.0) - ++count; - } - return count; + return [buttons_ count] - [hiddenButtons_ count]; } - (void)browserActionClicked:(BrowserActionButton*)sender { @@ -434,6 +473,11 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, return profile_->GetPrefs()->GetReal(prefs::kBrowserActionContainerWidth); } +- (void)showChevronIfNecessaryWithAnimation:(BOOL)animation { + BOOL hideChevron = [self buttonCount] == [self visibleButtonCount]; + [containerView_ setChevronHidden:hideChevron animate:animation]; +} + + (void)registerUserPrefs:(PrefService*)prefs { prefs->RegisterRealPref(prefs::kBrowserActionContainerWidth, 0); } diff --git a/chrome/browser/cocoa/extensions/browser_actions_overflow_menu.h b/chrome/browser/cocoa/extensions/browser_actions_overflow_menu.h new file mode 100644 index 0000000..aee3b42 --- /dev/null +++ b/chrome/browser/cocoa/extensions/browser_actions_overflow_menu.h @@ -0,0 +1,16 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_COCOA_EXTENSIONS_BROWSER_ACTIONS_OVERFLOW_MENU_ +#define CHROME_BROWSER_COCOA_EXTENSIONS_BROWSER_ACTIONS_OVERFLOW_MENU_ + +#import <Cocoa/Cocoa.h> + +@interface BrowserActionsOverflowMenu : NSMenu { + +} + +@end + +#endif // CHROME_BROWSER_COCOA_EXTENSIONS_BROWSER_ACTIONS_OVERFLOW_MENU_ diff --git a/chrome/browser/cocoa/extensions/browser_actions_overflow_menu.mm b/chrome/browser/cocoa/extensions/browser_actions_overflow_menu.mm new file mode 100644 index 0000000..25b967a --- /dev/null +++ b/chrome/browser/cocoa/extensions/browser_actions_overflow_menu.mm @@ -0,0 +1,9 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "browser_actions_overflow_menu.h" + +@implementation BrowserActionsOverflowMenu + +@end diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 2cf75b5..9ce744d 100755 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -597,6 +597,8 @@ 'browser/cocoa/extensions/browser_actions_container_view.mm', 'browser/cocoa/extensions/browser_actions_controller.h', 'browser/cocoa/extensions/browser_actions_controller.mm', + 'browser/cocoa/extensions/browser_actions_overflow_menu.h', + 'browser/cocoa/extensions/browser_actions_overflow_menu.mm', 'browser/cocoa/extensions/extension_action_context_menu.h', 'browser/cocoa/extensions/extension_action_context_menu.mm', 'browser/cocoa/extensions/extension_popup_controller.h', |