diff options
author | viettrungluu@chromium.org <viettrungluu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-14 01:01:17 +0000 |
---|---|---|
committer | viettrungluu@chromium.org <viettrungluu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-14 01:01:17 +0000 |
commit | 919905ddd11db90bf0b4a03f9b3e79b9c289aa04 (patch) | |
tree | e5586bbcff8540fdb241845f7f7e075fc33ad996 /chrome/browser | |
parent | 0207043ab2d23d96a700bcb9bfb9044bd240ae7b (diff) | |
download | chromium_src-919905ddd11db90bf0b4a03f9b3e79b9c289aa04.zip chromium_src-919905ddd11db90bf0b4a03f9b3e79b9c289aa04.tar.gz chromium_src-919905ddd11db90bf0b4a03f9b3e79b9c289aa04.tar.bz2 |
Mac: Animate the bookmark bar showing/hiding.
There's a huge refactoring of the bookmark bar controller in this CL, so that animation-related code is in only a few places (instead of scattered all over the place).
Changes to BookmarkBar.xib: Since BookmarkBarToolbarView now inherits from AnimatableView, I had to hook up its delegate_ member to File's Owner (i.e., the BookmarkBarController).
Not yet implemented: morphing between the detached bar (on the NTP) and anything else.
BUG=25600
TEST=Go to a normal page, show/hide the bookmark bar (Shift-Cmd-B), watch it animate.
Review URL: http://codereview.chromium.org/384105
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@31977 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
18 files changed, 631 insertions, 293 deletions
diff --git a/chrome/browser/browser.h b/chrome/browser/browser.h index a587aff..243b464 100644 --- a/chrome/browser/browser.h +++ b/chrome/browser/browser.h @@ -712,10 +712,10 @@ class Browser : public TabStripModelDelegate, NotificationRegistrar registrar_; // This Browser's type. - Type type_; + const Type type_; // This Browser's profile. - Profile* profile_; + Profile* const profile_; // This Browser's window. BrowserWindow* window_; diff --git a/chrome/browser/cocoa/animatable_view.h b/chrome/browser/cocoa/animatable_view.h index ef35eb7..aa8f01d 100644 --- a/chrome/browser/cocoa/animatable_view.h +++ b/chrome/browser/cocoa/animatable_view.h @@ -9,6 +9,7 @@ #import "base/cocoa_protocols_mac.h" #include "base/scoped_nsobject.h" +#import "chrome/browser/cocoa/background_gradient_view.h" #import "chrome/browser/cocoa/view_resizer.h" // A view that provides an animatable height property. Provides methods to @@ -19,7 +20,7 @@ // animation ends normally and an |animationDidStop:| message when the animation // was canceled (even when canceled as a result of a new animation starting). -@interface AnimatableView : NSView<NSAnimationDelegate> { +@interface AnimatableView : BackgroundGradientView<NSAnimationDelegate> { @protected IBOutlet id delegate_; // weak, used to send animation ended messages. diff --git a/chrome/browser/cocoa/bookmark_bar_bridge_unittest.mm b/chrome/browser/cocoa/bookmark_bar_bridge_unittest.mm index 2b03429..66d853c 100644 --- a/chrome/browser/cocoa/bookmark_bar_bridge_unittest.mm +++ b/chrome/browser/cocoa/bookmark_bar_bridge_unittest.mm @@ -35,7 +35,7 @@ typedef std::pair<GURL,WindowOpenDisposition> OpenInfo; - (id)initWithBrowser:(Browser*)browser { if ((self = [super initWithBrowser:browser initialWidth:100 // arbitrary - compressDelegate:nil + delegate:nil resizeDelegate:nil])) { callbacks_.reset([[NSMutableArray alloc] init]); } diff --git a/chrome/browser/cocoa/bookmark_bar_controller.h b/chrome/browser/cocoa/bookmark_bar_controller.h index 0b51d4e..c390893 100644 --- a/chrome/browser/cocoa/bookmark_bar_controller.h +++ b/chrome/browser/cocoa/bookmark_bar_controller.h @@ -15,17 +15,16 @@ #include "chrome/browser/cocoa/tab_strip_model_observer_bridge.h" #include "webkit/glue/window_open_disposition.h" -@class BackgroundGradientView; -@class BookmarkBarStateController; +@class BookmarkBarController; class BookmarkModel; class BookmarkNode; @class BookmarkBarView; class Browser; -@protocol ToolbarCompressable; class GURL; @class MenuButton; class Profile; class PrefService; +class TabContents; @class ToolbarController; @protocol ViewResizer; @@ -40,19 +39,57 @@ const CGFloat kNoBookmarksHorizontalOffset = 5.0; const CGFloat kNoBookmarksVerticalOffset = 22.0; const CGFloat kNoBookmarksNTPVerticalOffset = 28.0; -} // namespace +// States for the bookmark bar. +enum VisualState { + kInvalidState = 0, + kHiddenState = 1, + kShowingState = 2, + kDetachedState = 3, +}; + +} // namespace bookmarks + +// The interface for the bookmark bar controller's delegate. Currently, the +// delegate is the BWC and is responsible for ensuring that the toolbar is +// displayed correctly (as specified by |-getDesiredToolbarHeightCompression| +// and |-shouldToolbarShowDivider|) at the beginning and at the end of an +// animation (or after a state change). +@protocol BookmarkBarControllerDelegate + +// Sent when the state has changed (after any animation), but before the final +// display update. +- (void)bookmarkBar:(BookmarkBarController*)controller + didChangeFromState:(bookmarks::VisualState)oldState + toState:(bookmarks::VisualState)newState; + +// Sent before the animation begins. +- (void)bookmarkBar:(BookmarkBarController*)controller +willAnimateFromState:(bookmarks::VisualState)oldState + toState:(bookmarks::VisualState)newState; + +@end // A controller for the bookmark bar in the browser window. Handles showing // and hiding based on the preference in the given profile. @interface BookmarkBarController : - NSViewController<BookmarkBarToolbarViewController> { + NSViewController<BookmarkBarToolbarViewController> { @private + // The visual state of the bookmark bar. If an animation is running, this is + // set to the "destination" and |lastVisualState_| is set to the "original" + // state. This is set to |kInvalidState| on initialization (when the + // appropriate state is not yet known). + bookmarks::VisualState visualState_; + + // The "original" state of the bookmark bar if an animation is running, + // otherwise it should be |kInvalidState|. + bookmarks::VisualState lastVisualState_; + Browser* browser_; // weak; owned by its window BookmarkModel* bookmarkModel_; // weak; part of the profile owned by the // top-level Browser object. // Our initial view width, which is applied in awakeFromNib. - float initialWidth_; + CGFloat initialWidth_; // BookmarkNodes have a 64bit id. NSMenuItems have a 32bit tag used // to represent the bookmark node they refer to. This map provides @@ -78,16 +115,12 @@ const CGFloat kNoBookmarksNTPVerticalOffset = 28.0; // BookmarkModelObserver) scoped_ptr<BookmarkBarBridge> bridge_; - // Delegate that is alerted about whether it should be compressed because - // it's right next to us. - id<ToolbarCompressable> compressDelegate_; // weak + // Delegate that is informed about state changes in the bookmark bar. + id<BookmarkBarControllerDelegate> delegate_; // weak // Delegate that can resize us. id<ViewResizer> resizeDelegate_; // weak - // Lets us get TabSelectedAt notifications. - scoped_ptr<TabStripModelObserverBridge> tabObserver_; - IBOutlet BookmarkBarView* buttonView_; IBOutlet MenuButton* offTheSideButton_; // aka the chevron IBOutlet NSMenu* buttonContextMenu_; @@ -96,22 +129,24 @@ const CGFloat kNoBookmarksNTPVerticalOffset = 28.0; scoped_nsobject<NSButton> otherBookmarksButton_; } +@property(readonly, nonatomic) bookmarks::VisualState visualState; +@property(readonly, nonatomic) bookmarks::VisualState lastVisualState; +@property(assign, nonatomic) id<BookmarkBarControllerDelegate> delegate; + // Initializes the bookmark bar controller with the given browser // profile and delegates. - (id)initWithBrowser:(Browser*)browser - initialWidth:(float)initialWidth - compressDelegate:(id<ToolbarCompressable>)compressDelegate + initialWidth:(CGFloat)initialWidth + delegate:(id<BookmarkBarControllerDelegate>)delegate resizeDelegate:(id<ViewResizer>)resizeDelegate; -// Returns the backdrop to the bookmark bar. -- (BackgroundGradientView*)backgroundGradientView; +// Updates the bookmark bar (from its current, possibly in-transition) state to +// the one appropriate for the new conditions. +- (void)updateAndShowNormalBar:(BOOL)showNormalBar + showDetachedBar:(BOOL)showDetachedBar + withAnimation:(BOOL)animate; -// Tell the bar to show itself if needed (e.g. if the kShowBookmarkBar -// is set). Called once after the controller is first created. -- (void)showIfNeeded; - -// Update the visible state of the bookmark bar based on the current value of -// -[BookmarkBarController isAlwaysVisible]. +// Update the visible state of the bookmark bar. - (void)updateVisibility; // Turn on or off the bookmark bar and prevent or reallow its @@ -119,19 +154,27 @@ const CGFloat kNoBookmarksNTPVerticalOffset = 28.0; // if needed. For fullscreen mode. - (void)setBookmarkBarEnabled:(BOOL)enabled; -// Returns YES if the bookmarks bar is currently visible, either because the -// user has asked for it to always be visible, or because the current tab is the -// New Tab page. +// Returns YES if the bookmarks bar is currently visible (as a normal toolbar or +// as a detached bar on the NTP), NO otherwise. - (BOOL)isVisible; -// Returns true if the bookmark bar needs to be shown currently because a tab -// that requires it is selected. The bookmark bar will have a different -// appearance when it is shown if isAlwaysVisible returns NO. -- (BOOL)isNewTabPage; +// Returns YES if an animation is currently running, NO otherwise. +- (BOOL)isAnimationRunning; + +// Returns YES if the bookmarks bar is (to be) shown as part of the normal +// toolbar, NO otherwise. This is exclusive of |-isShownAsDetachedBar|. +- (BOOL)isShownAsToolbar; + +// Returns YES if the bookmarks bar is (to be) shown as a detached bar, NO +// otherwise; required for the |BookmarkBarToolbarViewController| protocol. This +// is exclusive of |-isShownAsToolbar|. +- (BOOL)isShownAsDetachedBar; -// Returns true if the bookmark bar is visible for all tabs. (This corresponds -// to the user having selected "Always show the bookmark bar") -- (BOOL)isAlwaysVisible; +// Returns the amount by which the toolbar above should be compressed. +- (CGFloat)getDesiredToolbarHeightCompression; + +// Returns whether or not the toolbar above should show the divider. +- (BOOL)shouldToolbarShowDivider; // Returns true if at least one bookmark was added. - (BOOL)addURLs:(NSArray*)urls withTitles:(NSArray*)titles at:(NSPoint)point; @@ -177,7 +220,6 @@ const CGFloat kNoBookmarksNTPVerticalOffset = 28.0; node:(const BookmarkNode*)node; @end - // These APIs should only be used by unit tests (or used internally). @interface BookmarkBarController(InternalOrTestingAPI) - (void)openURL:(GURL)url disposition:(WindowOpenDisposition)disposition; diff --git a/chrome/browser/cocoa/bookmark_bar_controller.mm b/chrome/browser/cocoa/bookmark_bar_controller.mm index 7a616de..3db0e8b 100644 --- a/chrome/browser/cocoa/bookmark_bar_controller.mm +++ b/chrome/browser/cocoa/bookmark_bar_controller.mm @@ -10,6 +10,7 @@ #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/browser.h" #include "chrome/browser/browser_list.h" +#import "chrome/browser/cocoa/background_gradient_view.h" #import "chrome/browser/cocoa/bookmark_bar_bridge.h" #import "chrome/browser/cocoa/bookmark_bar_constants.h" #import "chrome/browser/cocoa/bookmark_bar_controller.h" @@ -35,6 +36,77 @@ #include "skia/ext/skia_utils_mac.h" #import "third_party/mozilla/include/NSPasteboard+Utils.h" +// Bookmark bar state changing and animations +// +// The bookmark bar has three real states: "showing" (a normal bar attached to +// the toolbar), "hidden", and "detached" (pretending to be part of the web +// content on the NTP). It can, or at least should be able to, animate between +// these states. There are several complications even without animation: +// - The placement of the bookmark bar is done by the BWC, and it needs to know +// the state in order to place the bookmark bar correctly (immediately below +// the toolbar when showing, below the infobar when detached). +// - The "divider" (a black line) needs to be drawn by either the toolbar (when +// the bookmark bar is hidden or detached) or by the bookmark bar (when it is +// showing). It should not be drawn by both. +// - The toolbar needs to vertically "compress" when the bookmark bar is +// showing. This ensures the proper display of both the bookmark bar and the +// toolbar, and gives a padded area around the bookmark bar items for right +// clicks, etc. +// +// Our model is that the BWC controls us and also the toolbar. We try not to +// talk to the browser nor the toolbar directly, instead centralizing control in +// the BWC. The key method by which the BWC controls us is +// |-updateAndShowNormalBar:showDetachedBar:withAnimation:|. This invokes state +// changes, and at appropriate times we request that the BWC do things for us +// via either the resize delegate or our general delegate. If the BWC needs any +// information about what it should do, or tell the toolbar to do, it can then +// query us back (e.g., |-isShownAs...|, |-getDesiredToolbarHeightCompression|, +// |-shouldToolbarShowDivider|, etc.). +// +// Animation-related complications: +// - Compression of the toolbar is touchy during animation. It must not be +// compressed while the bookmark bar is animating to/from showing (from/to +// hidden), otherwise it would look like the bookmark bar's contents are +// sliding out of the controls inside the toolbar. As such, we have to make +// sure that the bookmark bar is shown at the right location and at the +// right height (at various points in time). +// - Showing the divider is also complicated during animation between hidden +// and showing. We have to make sure that the toolbar does not show the +// divider despite the fact that it's not compressed. The exception to this +// is at the beginning/end of the animation when the toolbar is still +// uncompressed but the bookmark bar has height 0. If we're not careful, we +// get a flicker at this point. +// - We have to ensure that we do the right thing if we're told to change state +// while we're running an animation. The generic/easy thing to do is to jump +// to the end state of our current animation, and (if the new state change +// again involves an animation) begin the new animation. We can do better +// than that, however, and sometimes just change the current animation to go +// to the new end state (e.g., by "reversing" the animation in the showing -> +// hidden -> showing case). We also have to ensure that demands to +// immediately change state are always honoured. +// +// Pointers to animation logic: +// - |-moveToVisualState:withAnimation:| starts animations, deciding which ones +// we know how to handle. +// - |showBookmarkBarWithAnimation:| has most of the actual logic. +// - |-getDesiredToolbarHeightCompression| and |-shouldToolbarShowDivider| +// contain related logic. +// - The BWC's |-layoutSubviews| needs to know how to position things. +// - The BWC should implement |-bookmarkBar:didChangeFromState:toState:| and +// |-bookmarkBar:willAnimateFromState:toState:| in order to inform the +// toolbar of required changes. + +namespace { + +// Overlap (in pixels) between the toolbar and the bookmark bar (when showing in +// normal mode). +const CGFloat kBookmarkBarOverlap = 6.0; + +// Duration of the bookmark bar animations. +const NSTimeInterval kBookmarkBarAnimationDuration = 0.12; + +} // namespace + // Specialization of NSButton that responds to middle-clicks. By default, // NSButton ignores them. @interface BookmarkButton : NSButton @@ -47,7 +119,34 @@ @end @interface BookmarkBarController(Private) -- (void)showBookmarkBar:(BOOL)enable immediately:(BOOL)immediately; +// Determines the appropriate state for the given situation. ++ (bookmarks::VisualState)visualStateToShowNormalBar:(BOOL)showNormalBar + showDetachedBar:(BOOL)showDetachedBar; + +// Moves to the given next state (from the current state), possibly animating. +// If |animate| is NO, it will stop any running animation and jump to the given +// state. If YES, it may either (depending on implementation) jump to the end of +// the current animation and begin the next one, or stop the current animation +// mid-flight and animate to the next state. +- (void)moveToVisualState:(bookmarks::VisualState)nextVisualState + withAnimation:(BOOL)animate; + +// Return the backdrop to the bookmark bar as various types. +- (BackgroundGradientView*)backgroundGradientView; +- (AnimatableView*)animatableView; + +// Puts stuff into the final visual state without animating, stopping a running +// animation if necessary. +- (void)finalizeVisualState; + +// Stops any current animation in its tracks (midway). +- (void)stopCurrentAnimation; + +// Show/hide the bookmark bar. Handles animating the resize of the content view. +// if |animate| is YES, the changes are made using the animator; otherwise they +// are made immediately. +- (void)showBookmarkBarWithAnimation:(BOOL)animate; + - (void)addNode:(const BookmarkNode*)child toMenu:(NSMenu*)menu; - (void)addFolderNode:(const BookmarkNode*)node toMenu:(NSMenu*)menu; - (void)tagEmptyMenu:(NSMenu*)menu; @@ -62,20 +161,27 @@ @implementation BookmarkBarController +@synthesize visualState = visualState_; +@synthesize lastVisualState = lastVisualState_; +@synthesize delegate = delegate_; + - (id)initWithBrowser:(Browser*)browser initialWidth:(float)initialWidth - compressDelegate:(id<ToolbarCompressable>)compressDelegate + delegate:(id<BookmarkBarControllerDelegate>)delegate resizeDelegate:(id<ViewResizer>)resizeDelegate { if ((self = [super initWithNibName:@"BookmarkBar" bundle:mac_util::MainAppBundle()])) { + // Initialize to an invalid state. + visualState_ = bookmarks::kInvalidState; + lastVisualState_ = bookmarks::kInvalidState; + browser_ = browser; initialWidth_ = initialWidth; bookmarkModel_ = browser_->profile()->GetBookmarkModel(); buttons_.reset([[NSMutableArray alloc] init]); - compressDelegate_ = compressDelegate; + delegate_ = delegate; resizeDelegate_ = resizeDelegate; - tabObserver_.reset( - new TabStripModelObserverBridge(browser_->tabstrip_model(), self)); + [[self animatableView] setResizeDelegate:resizeDelegate]; ResourceBundle& rb = ResourceBundle::GetSharedInstance(); folderImage_.reset([rb.GetNSImageNamed(IDR_BOOKMARK_BAR_FOLDER) retain]); @@ -85,6 +191,9 @@ } - (void)dealloc { + // We better stop any in-flight animation if we're being killed. + [[self animatableView] stopAnimation]; + // Remove our view from its superview so it doesn't attempt to reference // it when the controller is gone. //TODO(dmaclach): Remove -- http://crbug.com/25845 @@ -112,8 +221,8 @@ DCHECK([offTheSideButton_ attachedMenu]); - // To make life happier when the bookmark bar is floating, the - // chevron is a child of the button view. + // To make life happier when the bookmark bar is floating, the chevron is a + // child of the button view. [offTheSideButton_ removeFromSuperview]; [buttonView_ addSubview:offTheSideButton_]; @@ -127,14 +236,16 @@ object:[self view]]; } -// Method is the same as [self view], but is provided to be explicit. +// (Private) Method is the same as [self view], but is provided to be explicit. - (BackgroundGradientView*)backgroundGradientView { + DCHECK([[self view] isKindOfClass:[BackgroundGradientView class]]); return (BackgroundGradientView*)[self view]; } -- (void)showIfNeeded { - if (browser_->profile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar)) - [self showBookmarkBar:YES immediately:YES]; +// (Private) Method is the same as [self view], but is provided to be explicit. +- (AnimatableView*)animatableView { + DCHECK([[self view] isKindOfClass:[AnimatableView class]]); + return (AnimatableView*)[self view]; } // Position the off-the-side chevron to the left of the otherBookmarks button. @@ -185,16 +296,15 @@ NSPoint newOrigin; newOrigin.x = parent.origin.x + bookmarks::kNoBookmarksHorizontalOffset; newOrigin.y = parent.origin.y + parent.size.height - - ([self isAlwaysVisible] ? bookmarks::kNoBookmarksVerticalOffset : - bookmarks::kNoBookmarksNTPVerticalOffset); + ([self isShownAsToolbar] ? bookmarks::kNoBookmarksVerticalOffset : + bookmarks::kNoBookmarksNTPVerticalOffset); [noItemsView setFrameOrigin:newOrigin]; } -// Change the layout of the bookmark bar's subviews in response to a -// visibility change (e.g. show or hide the bar) or style change -// (attached or floating). +// Change the layout of the bookmark bar's subviews in response to a visibility +// change (e.g., show or hide the bar) or style change (attached or floating). - (void)layoutSubviews { - if ([self drawAsFloatingBar]) { + if (visualState_ == bookmarks::kDetachedState) { // The internal bookmark bar should have padding to center it. NSRect frame = [[self view] frame]; [buttonView_ setFrame: @@ -212,49 +322,125 @@ } } -// Show or hide the bar based on the value of |show|. Handles animating the -// resize of the content view. if |immediately| is YES, make changes -// immediately instead of using an animator. The routine which enables the bar -// will show it if relevant using other mechanisms (the pref) to determine -// desired state. -- (void)showBookmarkBar:(BOOL)show immediately:(BOOL)immediately { - BOOL compressed = [self isAlwaysVisible]; - [compressDelegate_ setShouldBeCompressed:compressed]; +// (Private) +- (void)showBookmarkBarWithAnimation:(BOOL)animate { + if (animate) { + // Animating from hidden to normal bar. + if (lastVisualState_ == bookmarks::kHiddenState && + visualState_ == bookmarks::kShowingState) { + [[self backgroundGradientView] setShowsDivider:YES]; + [[self view] setHidden:NO]; + AnimatableView* view = [self animatableView]; + // Height takes into account the extra height we have since the toolbar + // only compresses when we're done. + [view animateToNewHeight:([self preferredHeight] - kBookmarkBarOverlap) + duration:kBookmarkBarAnimationDuration]; + return; + } + + // Animating from normal bar to hidden. + if (lastVisualState_ == bookmarks::kShowingState && + visualState_ == bookmarks::kHiddenState) { + // The toolbar uncompresses immediately at the beginning (otherwise the + // slide looks wrong, since we slide into the bottom of stuff in the + // toolbar). Do this only if we're at the beginning height since we may + // enter this mid-animation. + if (NSHeight([[self view] frame]) == bookmarks::kBookmarkBarHeight) { + [resizeDelegate_ resizeView:[self view] + newHeight:(bookmarks::kBookmarkBarHeight - + kBookmarkBarOverlap)]; + } + [[self backgroundGradientView] setShowsDivider:YES]; + [[self view] setHidden:NO]; + AnimatableView* view = [self animatableView]; + [view animateToNewHeight:0 + duration:kBookmarkBarAnimationDuration]; + return; + } + + // TODO(viettrungluu): other animation cases. Note: will need to fade in/out + // divider when animating from/to detached bar (to/from normal bar). + + // Fall through for any cases we don't know about. + } + BOOL show = [self isVisible]; CGFloat height = show ? [self preferredHeight] : 0; [resizeDelegate_ resizeView:[self view] newHeight:height]; - [[self view] setHidden:show ? NO : YES]; - DCHECK([[self view] isKindOfClass:[BookmarkBarToolbarView class]]); + // Only show the divider if showing the normal bookmark bar. + BOOL showDivider = (visualState_ == bookmarks::kShowingState || + lastVisualState_ == bookmarks::kShowingState) ? YES : NO; + [[self backgroundGradientView] setShowsDivider:showDivider]; + + // Make sure we're shown. + [[self view] setHidden:(show ? NO : YES)]; [self layoutSubviews]; [self frameDidChange]; } -// We don't change a preference; we only change visibility. -// Preference changing (global state) is handled in -// BrowserWindowCocoa::ToggleBookmarkBar(). We simply update the visibility of -// the bar based on the current value of the pref. +// We don't change a preference; we only change visibility. Preference changing +// (global state) is handled in |BrowserWindowCocoa::ToggleBookmarkBar()|. We +// simply update based on what we're told. - (void)updateVisibility { - [self showBookmarkBar:[self isVisible] immediately:YES]; + [self showBookmarkBarWithAnimation:NO]; } - (void)setBookmarkBarEnabled:(BOOL)enabled { - barIsEnabled_ = enabled ? YES : NO; + barIsEnabled_ = enabled; [self updateVisibility]; } - (BOOL)isVisible { - return ([self isAlwaysVisible] && barIsEnabled_) || [self isNewTabPage]; + return (barIsEnabled_ && (visualState_ == bookmarks::kShowingState || + visualState_ == bookmarks::kDetachedState)) ? + YES : NO; } -- (BOOL)isNewTabPage { - return browser_ && browser_->GetSelectedTabContents() && - browser_->GetSelectedTabContents()->ShouldShowBookmarkBar(); +- (BOOL)isAnimationRunning { + return (lastVisualState_ == bookmarks::kInvalidState) ? NO : YES; } -- (BOOL)isAlwaysVisible { - return browser_ && - browser_->profile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); +- (BOOL)isShownAsToolbar { + return (visualState_ == bookmarks::kShowingState) ? YES : NO; +} + +- (BOOL)isShownAsDetachedBar { + return (visualState_ == bookmarks::kDetachedState) ? YES : NO; +} + +- (CGFloat)getDesiredToolbarHeightCompression { + // Some special cases.... + if ([self isAnimationRunning]) { + // No toolbar compression when animating between showing and hidden. + if ((lastVisualState_ == bookmarks::kHiddenState && + visualState_ == bookmarks::kShowingState) || + (lastVisualState_ == bookmarks::kShowingState && + visualState_ == bookmarks::kHiddenState)) + return 0; + + // TODO(viettrungluu): other animation cases. + } + + return (visualState_ == bookmarks::kShowingState) ? kBookmarkBarOverlap : 0; +} + +- (BOOL)shouldToolbarShowDivider { + // Some special cases.... + if ([self isAnimationRunning]) { + // In general, the toolbar shouldn't show a divider while we're animating + // between showing and hidden. The exception is when our height is < 1, in + // which case we can't draw it. + if ((lastVisualState_ == bookmarks::kHiddenState && + visualState_ == bookmarks::kShowingState) || + (lastVisualState_ == bookmarks::kShowingState && + visualState_ == bookmarks::kHiddenState)) + return (NSHeight([[self view] frame]) < 1) ? YES : NO; + + // TODO(viettrungluu): other animation cases. + } + + return (visualState_ == bookmarks::kShowingState) ? NO : YES; } - (BOOL)addURLs:(NSArray*)urls withTitles:(NSArray*)titles at:(NSPoint)point { @@ -274,18 +460,15 @@ } - (int)currentTabContentsHeight { - return browser_->GetSelectedTabContents()->view()->GetContainerSize(). - height(); + return browser_->GetSelectedTabContents() ? + browser_->GetSelectedTabContents()->view()->GetContainerSize().height() : + 0; } - (ThemeProvider*)themeProvider { return browser_->profile()->GetThemeProvider(); } -- (BOOL)drawAsFloatingBar { - return ![self isAlwaysVisible] && [self isNewTabPage]; -} - // Return nil if menuItem has no delegate. - (BookmarkNode*)nodeFromMenuItem:(id)menuItem { NSCell* cell = reinterpret_cast<NSCell*>([[menuItem menu] delegate]); @@ -349,27 +532,10 @@ } - (int)preferredHeight { - return [self isAlwaysVisible] ? bookmarks::kBookmarkBarHeight : + return [self isShownAsToolbar] ? bookmarks::kBookmarkBarHeight : bookmarks::kNTPBookmarkBarHeight; } -- (void)selectTabWithContents:(TabContents*)newContents - previousContents:(TabContents*)oldContents - atIndex:(NSInteger)index - userGesture:(bool)wasUserGesture { - // We need selectTabWithContents: for when we change from a tab that is the - // new tab page to a tab that isn't. - [self updateVisibility]; -} - -- (void)tabChangedWithContents:(TabContents*)contents - atIndex:(NSInteger)index - loadingOnly:(BOOL)loading { - // We need tabChangedWithContents: for when the user clicks a bookmark from - // the bookmark bar on the new tab page. - [self updateVisibility]; -} - // Recursively add the given bookmark node and all its children to // menu, one menu item per node. - (void)addNode:(const BookmarkNode*)child toMenu:(NSMenu*)menu { @@ -934,4 +1100,106 @@ return defaultImage_; } +// Determines the appropriate state for the given situation. ++ (bookmarks::VisualState)visualStateToShowNormalBar:(BOOL)showNormalBar + showDetachedBar:(BOOL)showDetachedBar { + if (showNormalBar) + return bookmarks::kShowingState; + if (showDetachedBar) + return bookmarks::kDetachedState; + return bookmarks::kHiddenState; +} + +- (void)moveToVisualState:(bookmarks::VisualState)nextVisualState + withAnimation:(BOOL)animate { + BOOL isAnimationRunning = [self isAnimationRunning]; + + // No-op if the next state is the same as the "current" one, subject to the + // following conditions: + // - no animation is running; or + // - an animation is running and |animate| is YES ([*] if it's NO, we'd want + // to cancel the animation and jump to the final state). + if ((nextVisualState == visualState_) && (!isAnimationRunning || animate)) + return; + + // If an animation is running, we want to finalize it. Otherwise we'd have to + // be able to animate starting from the middle of one type of animation. We + // assume that animations that we know about can be "reversed". + if (isAnimationRunning) { + // Don't cancel if we're going to reverse the animation. + if (nextVisualState != lastVisualState_) { + [self stopCurrentAnimation]; + [self finalizeVisualState]; + } + + // If we're in case [*] above, we can stop here. + if (nextVisualState == visualState_) + return; + } + + // Now update with the new state change. + lastVisualState_ = visualState_; + visualState_ = nextVisualState; + + if (animate) { + // Take care of any animation cases we know how to handle. + + // We know how to handle hidden <-> normal.... + if ((lastVisualState_ == bookmarks::kHiddenState && + visualState_ == bookmarks::kShowingState) || + (lastVisualState_ == bookmarks::kShowingState && + visualState_ == bookmarks::kHiddenState)) { + [delegate_ bookmarkBar:self willAnimateFromState:lastVisualState_ + toState:visualState_]; + [self showBookmarkBarWithAnimation:YES]; + return; + } + + // TODO(viettrungluu): we don't know about any yet.... + + // Let any animation cases which we don't know how to handle fall through to + // the unanimated case. + } + + // Just jump to the state. + [self finalizeVisualState]; +} + +// N.B.: |-moveToVisualState:...| will check if this should be a no-op or not. +- (void)updateAndShowNormalBar:(BOOL)showNormalBar + showDetachedBar:(BOOL)showDetachedBar + withAnimation:(BOOL)animate { + bookmarks::VisualState newVisualState = + [BookmarkBarController visualStateToShowNormalBar:showNormalBar + showDetachedBar:showDetachedBar]; + [self moveToVisualState:newVisualState + withAnimation:animate]; +} + +// (Private) +- (void)finalizeVisualState { + // We promise that our delegate that the variables will be finalized before + // the call to |-bookmarkBar:didChangeFromState:toState:|. + bookmarks::VisualState oldVisualState = lastVisualState_; + lastVisualState_ = bookmarks::kInvalidState; + + // Notify our delegate. + [delegate_ bookmarkBar:self didChangeFromState:oldVisualState + toState:visualState_]; + + // Update ourselves visually. + [self updateVisibility]; +} + +// (Private) +- (void)stopCurrentAnimation { + [[self animatableView] stopAnimation]; +} + +// Delegate method for |AnimatableView| (a superclass of +// |BookmarkBarToolbarView|). +- (void)animationDidEnd:(NSAnimation*)animation { + [self finalizeVisualState]; +} + @end diff --git a/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm b/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm index 5bfd58c..228bd68 100644 --- a/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm +++ b/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm @@ -12,7 +12,6 @@ #import "chrome/browser/cocoa/bookmark_bar_view.h" #include "chrome/browser/cocoa/browser_test_helper.h" #import "chrome/browser/cocoa/cocoa_test_helper.h" -#import "chrome/browser/cocoa/toolbar_compressable.h" #include "chrome/browser/cocoa/test_event_utils.h" #import "chrome/browser/cocoa/view_resizer_pong.h" #include "chrome/common/pref_names.h" @@ -79,34 +78,6 @@ @end -// A BookmarkBarController that always beleives that it's on the new tab page. -@interface AlwaysNewTabPageBookmarkBarController : BookmarkBarControllerNoOpen { -} -@end - -@implementation AlwaysNewTabPageBookmarkBarController --(BOOL)isNewTabPage { - return YES; -} -@end - -@interface CompressablePong : NSObject<ToolbarCompressable> { -@private - BOOL compressed_; -} -@property (readonly) BOOL compressed; -@end - -@implementation CompressablePong - -@synthesize compressed = compressed_; - -- (void)setShouldBeCompressed:(BOOL)compressed { - compressed_ = compressed; -} - -@end - namespace { static const int kContentAreaHeight = 500; @@ -116,7 +87,6 @@ class BookmarkBarControllerTest : public PlatformTest { public: BookmarkBarControllerTest() { resizeDelegate_.reset([[ViewResizerPong alloc] init]); - compressDelegate_.reset([[CompressablePong alloc] init]); NSRect parent_frame = NSMakeRect(0, 0, 800, 50); parent_view_.reset([[NSView alloc] initWithFrame:parent_frame]); [parent_view_ setHidden:YES]; @@ -124,7 +94,7 @@ class BookmarkBarControllerTest : public PlatformTest { [[BookmarkBarControllerNoOpen alloc] initWithBrowser:helper_.browser() initialWidth:NSWidth(parent_frame) - compressDelegate:compressDelegate_.get() + delegate:nil resizeDelegate:resizeDelegate_.get()]); InstallAndToggleBar(bar_.get()); @@ -149,8 +119,17 @@ class BookmarkBarControllerTest : public PlatformTest { frame.origin.y = 100; [[[bar view] superview] setFrame:frame]; - // make sure it's open so certain things aren't no-ops - helper_.profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, true); + // Make sure it's open so certain things aren't no-ops. + [bar updateAndShowNormalBar:YES + showDetachedBar:NO + withAnimation:NO]; + } + + // Update the state of the bookmark bar. + void UpdateBookmarkBar() { + [bar_ updateAndShowNormalBar:[bar_ isShownAsToolbar] + showDetachedBar:[bar_ isShownAsDetachedBar] + withAnimation:NO]; } // Return a menu item that points to the right URL. @@ -166,12 +145,10 @@ class BookmarkBarControllerTest : public PlatformTest { return menu_item_; } - CocoaTestHelper cocoa_helper_; // Inits Cocoa, creates window, etc... scoped_nsobject<NSView> parent_view_; BrowserTestHelper helper_; scoped_nsobject<ViewResizerPong> resizeDelegate_; - scoped_nsobject<CompressablePong> compressDelegate_; scoped_nsobject<BookmarkBarControllerNoOpen> bar_; scoped_nsobject<NSMenu> menu_; scoped_nsobject<NSMenuItem> menu_item_; @@ -180,9 +157,11 @@ class BookmarkBarControllerTest : public PlatformTest { }; TEST_F(BookmarkBarControllerTest, ShowWhenShowBookmarkBarTrue) { - helper_.profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, true); - [bar_ updateVisibility]; - + [bar_ updateAndShowNormalBar:YES + showDetachedBar:NO + withAnimation:NO]; + EXPECT_TRUE([bar_ isShownAsToolbar]); + EXPECT_FALSE([bar_ isShownAsDetachedBar]); EXPECT_TRUE([bar_ isVisible]); EXPECT_FALSE([[bar_ view] isHidden]); EXPECT_GT([resizeDelegate_ height], 0); @@ -190,9 +169,11 @@ TEST_F(BookmarkBarControllerTest, ShowWhenShowBookmarkBarTrue) { } TEST_F(BookmarkBarControllerTest, HideWhenShowBookmarkBarFalse) { - helper_.profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, false); - [bar_ updateVisibility]; - + [bar_ updateAndShowNormalBar:NO + showDetachedBar:NO + withAnimation:NO]; + EXPECT_FALSE([bar_ isShownAsToolbar]); + EXPECT_FALSE([bar_ isShownAsDetachedBar]); EXPECT_FALSE([bar_ isVisible]); EXPECT_TRUE([[bar_ view] isHidden]); EXPECT_EQ(0, [resizeDelegate_ height]); @@ -200,10 +181,12 @@ TEST_F(BookmarkBarControllerTest, HideWhenShowBookmarkBarFalse) { } TEST_F(BookmarkBarControllerTest, HideWhenShowBookmarkBarTrueButDisabled) { - helper_.profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, true); [bar_ setBookmarkBarEnabled:NO]; - [bar_ updateVisibility]; - + [bar_ updateAndShowNormalBar:YES + showDetachedBar:NO + withAnimation:NO]; + EXPECT_TRUE([bar_ isShownAsToolbar]); + EXPECT_FALSE([bar_ isShownAsDetachedBar]); EXPECT_FALSE([bar_ isVisible]); EXPECT_TRUE([[bar_ view] isHidden]); EXPECT_EQ(0, [resizeDelegate_ height]); @@ -211,24 +194,15 @@ TEST_F(BookmarkBarControllerTest, HideWhenShowBookmarkBarTrueButDisabled) { } TEST_F(BookmarkBarControllerTest, ShowOnNewTabPage) { - helper_.profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, false); - - scoped_nsobject<BookmarkBarControllerTogglePong> bar; - bar.reset( - [[AlwaysNewTabPageBookmarkBarController alloc] - initWithBrowser:helper_.browser() - initialWidth:800 // arbitrary - compressDelegate:compressDelegate_.get() - resizeDelegate:resizeDelegate_.get()]); - InstallAndToggleBar(bar.get()); - - [bar setBookmarkBarEnabled:NO]; - [bar updateVisibility]; - - EXPECT_TRUE([bar isVisible]); - EXPECT_FALSE([[bar view] isHidden]); - EXPECT_GT([resizeDelegate_ height], 0); - EXPECT_GT([[bar view] frame].size.height, 0); + [bar_ updateAndShowNormalBar:NO + showDetachedBar:YES + withAnimation:NO]; + EXPECT_FALSE([bar_ isShownAsToolbar]); + EXPECT_TRUE([bar_ isShownAsDetachedBar]); + EXPECT_TRUE([bar_ isVisible]); + EXPECT_FALSE([[bar_ view] isHidden]); + ;EXPECT_GT([resizeDelegate_ height], 0); + EXPECT_GT([[bar_ view] frame].size.height, 0); // Make sure no buttons fall off the bar, either now or when resized // bigger or smaller. @@ -237,23 +211,29 @@ TEST_F(BookmarkBarControllerTest, ShowOnNewTabPage) { for (unsigned x = 0; x < arraysize(sizes); x++) { // Confirm the buttons moved from the last check (which may be // init but that's fine). - CGFloat newX = [[bar offTheSideButton] frame].origin.x; + CGFloat newX = [[bar_ offTheSideButton] frame].origin.x; EXPECT_NE(previousX, newX); previousX = newX; - // Confirm the buttons have a reasonable bounds. - EXPECT_TRUE(NSContainsRect([[bar buttonView] frame], - [[bar offTheSideButton] frame])); - EXPECT_TRUE(NSContainsRect([[bar buttonView] frame], - [[bar otherBookmarksButton] frame])); + // Confirm the buttons have a reasonable bounds. Recall that |-frame| + // returns rectangles in the superview's coordinates. + NSRect buttonViewFrame = + [[bar_ buttonView] convertRect:[[bar_ buttonView] frame] + fromView:[[bar_ buttonView] superview]]; + EXPECT_EQ([bar_ buttonView], [[bar_ offTheSideButton] superview]); + EXPECT_TRUE(NSContainsRect(buttonViewFrame, + [[bar_ offTheSideButton] frame])); + EXPECT_EQ([bar_ buttonView], [[bar_ otherBookmarksButton] superview]); + EXPECT_TRUE(NSContainsRect(buttonViewFrame, + [[bar_ otherBookmarksButton] frame])); // Now move them implicitly. // We confirm FrameChangeNotification works in the next unit test; // we simply assume it works here to resize or reposition the // buttons above. - NSRect frame = [[bar view] frame]; + NSRect frame = [[bar_ view] frame]; frame.size.width += sizes[x]; - [[bar view] setFrame:frame]; + [[bar_ view] setFrame:frame]; } } @@ -264,7 +244,7 @@ TEST_F(BookmarkBarControllerTest, FrameChangeNotification) { [[BookmarkBarControllerTogglePong alloc] initWithBrowser:helper_.browser() initialWidth:100 // arbitrary - compressDelegate:compressDelegate_.get() + delegate:nil resizeDelegate:resizeDelegate_.get()]); InstallAndToggleBar(bar.get()); @@ -415,13 +395,17 @@ TEST_F(BookmarkBarControllerTest, TestAddRemoveAndClear) { EXPECT_EQ(initial_subview_count, [[buttonView subviews] count]); GURL gurl1("http://superfriends.hall-of-justice.edu"); - std::wstring title1(L"Protectors of the Universe"); + // Short titles increase the chances of this test succeeding if the view is + // narrow. + // TODO(viettrungluu): make the test independent of window/view size, font + // metrics, button size and spacing, and everything else. + std::wstring title1(L"x"); model->SetURLStarred(gurl1, title1, true); EXPECT_EQ(1U, [[bar_ buttons] count]); EXPECT_EQ(1+initial_subview_count, [[buttonView subviews] count]); GURL gurl2("http://legion-of-doom.gov"); - std::wstring title2(L"Bad doodz"); + std::wstring title2(L"y"); model->SetURLStarred(gurl2, title2, true); EXPECT_EQ(2U, [[bar_ buttons] count]); EXPECT_EQ(2+initial_subview_count, [[buttonView subviews] count]); diff --git a/chrome/browser/cocoa/bookmark_bar_toolbar_view.h b/chrome/browser/cocoa/bookmark_bar_toolbar_view.h index 0eb79ca..2232309 100644 --- a/chrome/browser/cocoa/bookmark_bar_toolbar_view.h +++ b/chrome/browser/cocoa/bookmark_bar_toolbar_view.h @@ -12,9 +12,8 @@ #import <Cocoa/Cocoa.h> -#import "chrome/browser/cocoa/background_gradient_view.h" +#import "chrome/browser/cocoa/animatable_view.h" -@protocol BookmarkBarFloating; @class BookmarkBarView; class TabContents; class ThemeProvider; @@ -22,8 +21,8 @@ class ThemeProvider; // An interface to allow mocking of a BookmarkBarController by the // BookmarkBarToolbarView. @protocol BookmarkBarToolbarViewController -// Displaying the bookmark toolbar background in floating mode requires the -// size of the currently selected tab to properly calculate where the +// Displaying the bookmark toolbar background in bubble (floating) mode requires +// the size of the currently selected tab to properly calculate where the // background image is joined. - (int)currentTabContentsHeight; @@ -32,11 +31,11 @@ class ThemeProvider; // Returns true if the bookmark bar should be drawn as if it's a disconnected // bookmark bar on the New Tag Page. -- (BOOL)drawAsFloatingBar; +- (BOOL)isShownAsDetachedBar; @end -@interface BookmarkBarToolbarView : BackgroundGradientView { +@interface BookmarkBarToolbarView : AnimatableView { @private // The controller which tells us how we should be drawing (as normal or as a // floating bar). diff --git a/chrome/browser/cocoa/bookmark_bar_toolbar_view.mm b/chrome/browser/cocoa/bookmark_bar_toolbar_view.mm index 119192e..1a9b7ce 100644 --- a/chrome/browser/cocoa/bookmark_bar_toolbar_view.mm +++ b/chrome/browser/cocoa/bookmark_bar_toolbar_view.mm @@ -17,18 +17,18 @@ const CGFloat kBorderRadius = 3.0; @interface BookmarkBarToolbarView (Private) -- (void)drawRectAsFloating:(NSRect)rect; +- (void)drawRectAsBubble:(NSRect)rect; @end @implementation BookmarkBarToolbarView - (BOOL)isOpaque { - return [controller_ drawAsFloatingBar]; + return [controller_ isShownAsDetachedBar]; } - (void)drawRect:(NSRect)rect { - if ([controller_ drawAsFloatingBar]) { - [self drawRectAsFloating:rect]; + if ([controller_ isShownAsDetachedBar]) { + [self drawRectAsBubble:rect]; } else { NSPoint phase = [self gtm_themePatternPhase]; [[NSGraphicsContext currentContext] setPatternPhase:phase]; @@ -36,7 +36,7 @@ const CGFloat kBorderRadius = 3.0; } } -- (void)drawRectAsFloating:(NSRect)rect { +- (void)drawRectAsBubble:(NSRect)rect { NSRect bounds = [self bounds]; ThemeProvider* themeProvider = [controller_ themeProvider]; diff --git a/chrome/browser/cocoa/bookmark_bar_toolbar_view_unittest.mm b/chrome/browser/cocoa/bookmark_bar_toolbar_view_unittest.mm index a6414f4..5073764 100644 --- a/chrome/browser/cocoa/bookmark_bar_toolbar_view_unittest.mm +++ b/chrome/browser/cocoa/bookmark_bar_toolbar_view_unittest.mm @@ -22,8 +22,7 @@ using ::testing::DoAll; using ::testing::Return; using ::testing::SetArgumentPointee; -// When testing the floating drawing, we need to have a source of -// theme data. +// When testing the floating drawing, we need to have a source of theme data. class MockThemeProvider : public ThemeProvider { public: // Cross platform methods @@ -53,27 +52,27 @@ class MockThemeProvider : public ThemeProvider { @end // Allows us to control which way the view is rendered. -@interface DrawFloatingFakeController : +@interface DrawDetachedBarFakeController : NSObject<BookmarkBarToolbarViewController> { int current_tab_contents_height_; ThemeProvider* theme_provider_; - BOOL drawAsFloating_; + BOOL isShownAsDetachedBar_; } @property(assign) int currentTabContentsHeight; @property(assign) ThemeProvider* themeProvider; -@property(assign) BOOL drawAsFloatingBar; +@property(assign) BOOL isShownAsDetachedBar; @end -@implementation DrawFloatingFakeController +@implementation DrawDetachedBarFakeController @synthesize currentTabContentsHeight = current_tab_contents_height_; @synthesize themeProvider = theme_provider_; -@synthesize drawAsFloatingBar = drawAsFloating_; +@synthesize isShownAsDetachedBar = isShownAsDetachedBar_; @end class BookmarkBarToolbarViewTest : public PlatformTest { public: BookmarkBarToolbarViewTest() { - controller_.reset([[DrawFloatingFakeController alloc] init]); + controller_.reset([[DrawDetachedBarFakeController alloc] init]); NSRect frame = NSMakeRect(0, 0, 400, 40); view_.reset([[BookmarkBarToolbarView alloc] initWithFrame:frame]); [cocoa_helper_.contentView() addSubview:view_.get()]; @@ -81,7 +80,7 @@ class BookmarkBarToolbarViewTest : public PlatformTest { } CocoaTestHelper cocoa_helper_; // Inits Cocoa, creates window, etc... - scoped_nsobject<DrawFloatingFakeController> controller_; + scoped_nsobject<DrawDetachedBarFakeController> controller_; scoped_nsobject<BookmarkBarToolbarView> view_; }; @@ -95,13 +94,13 @@ TEST_F(BookmarkBarToolbarViewTest, AddRemove) { // Test drawing (part 1), mostly to ensure nothing leaks or crashes. TEST_F(BookmarkBarToolbarViewTest, DisplayAsNormalBar) { - [controller_.get() setDrawAsFloatingBar:NO]; + [controller_.get() setIsShownAsDetachedBar:NO]; [view_ display]; } // Test drawing (part 2), mostly to ensure nothing leaks or crashes. -TEST_F(BookmarkBarToolbarViewTest, DisplayAsFloatingBarWithNoImage) { - [controller_.get() setDrawAsFloatingBar:YES]; +TEST_F(BookmarkBarToolbarViewTest, DisplayAsDetachedBarWithNoImage) { + [controller_.get() setIsShownAsDetachedBar:YES]; // Tests where we don't have a background image, only a color. MockThemeProvider provider; @@ -114,7 +113,7 @@ TEST_F(BookmarkBarToolbarViewTest, DisplayAsFloatingBarWithNoImage) { [view_ display]; } -// Actions used in DisplayAsFloatingBarWithBgImage. +// Actions used in DisplayAsDetachedBarWithBgImage. ACTION(SetBackgroundTiling) { *arg1 = BrowserThemeProvider::NO_REPEAT; return true; @@ -126,8 +125,8 @@ ACTION(SetAlignLeft) { } // Test drawing (part 3), mostly to ensure nothing leaks or crashes. -TEST_F(BookmarkBarToolbarViewTest, DisplayAsFloatingBarWithBgImage) { - [controller_.get() setDrawAsFloatingBar:YES]; +TEST_F(BookmarkBarToolbarViewTest, DisplayAsDetachedBarWithBgImage) { + [controller_.get() setIsShownAsDetachedBar:YES]; // Tests where we have a background image, with positioning information. MockThemeProvider provider; diff --git a/chrome/browser/cocoa/browser_window_cocoa.mm b/chrome/browser/cocoa/browser_window_cocoa.mm index 8e5b50b..f38dd2d 100644 --- a/chrome/browser/cocoa/browser_window_cocoa.mm +++ b/chrome/browser/cocoa/browser_window_cocoa.mm @@ -423,7 +423,7 @@ void BrowserWindowCocoa::Observe(NotificationType type, // Only the key window gets a direct toggle from the menu. // Other windows hear about it from the notification. case NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED: - [controller_ updateBookmarkBarVisibility]; + [controller_ updateBookmarkBarVisibilityWithAnimation:YES]; break; default: NOTREACHED(); // we don't ask for anything else! diff --git a/chrome/browser/cocoa/browser_window_controller.h b/chrome/browser/cocoa/browser_window_controller.h index 7156778..d7fedde 100644 --- a/chrome/browser/cocoa/browser_window_controller.h +++ b/chrome/browser/cocoa/browser_window_controller.h @@ -44,6 +44,7 @@ class TabStripModelObserverBridge; @interface BrowserWindowController : TabWindowController<NSUserInterfaceValidations, + BookmarkBarControllerDelegate, BookmarkBubbleControllerDelegate, BrowserCommandExecutor, ViewResizer, @@ -122,8 +123,9 @@ class TabStripModelObserverBridge; - (BOOL)isBookmarkBarVisible; -// Called after the visibility perf changed. -- (void)updateBookmarkBarVisibility; +// Called after bookmark bar visibility changes (due to pref change or change in +// tab/tab contents). +- (void)updateBookmarkBarVisibilityWithAnimation:(BOOL)animate; - (BOOL)isDownloadShelfVisible; diff --git a/chrome/browser/cocoa/browser_window_controller.mm b/chrome/browser/cocoa/browser_window_controller.mm index 65d0475..1eb90c8 100644 --- a/chrome/browser/cocoa/browser_window_controller.mm +++ b/chrome/browser/cocoa/browser_window_controller.mm @@ -42,6 +42,7 @@ #import "chrome/browser/cocoa/toolbar_controller.h" #import "chrome/browser/browser_theme_provider.h" #include "chrome/browser/sync/sync_status_ui_helper.h" +#include "chrome/common/pref_names.h" #include "chrome/common/pref_service.h" #import "chrome/browser/cocoa/background_gradient_view.h" #include "grit/generated_resources.h" @@ -84,6 +85,13 @@ willPositionSheet:(NSWindow*)sheet // Repositions the windows subviews. - (void)layoutSubviews; +// Should we show the normal bookmark bar? +- (BOOL)shouldShowBookmarkBar; + +// Is the current page one for which the bookmark should be shown detached *if* +// the normal bookmark bar is not shown? +- (BOOL)shouldShowDetachedBookmarkBar; + @end @@ -176,7 +184,7 @@ willPositionSheet:(NSWindow*)sheet [[BookmarkBarController alloc] initWithBrowser:browser_.get() initialWidth:NSWidth([[[self window] contentView] frame]) - compressDelegate:toolbarController_.get() + delegate:self resizeDelegate:self]); // Add bookmark bar to the view hierarchy. This also triggers the @@ -192,10 +200,10 @@ willPositionSheet:(NSWindow*)sheet [bookmarkBarController_ setBookmarkBarEnabled:NO]; } - // We don't want to try and show the bar before it gets placed in - // it's parent view, so this step shoudn't be inside the bookmark - // bar controller's awakeFromNib. - [bookmarkBarController_ showIfNeeded]; + // We don't want to try and show the bar before it gets placed in its parent + // view, so this step shoudn't be inside the bookmark bar controller's + // |-awakeFromNib|. + [self updateBookmarkBarVisibilityWithAnimation:NO]; if (browser_->SupportsWindowFeature(Browser::FEATURE_EXTENSIONSHELF)) { // Create the extension shelf. @@ -474,10 +482,10 @@ willPositionSheet:(NSWindow*)sheet view == [downloadShelfController_ view] || view == [bookmarkBarController_ view]); - // Change the height of the view and call layoutViews. We set the height here - // without regard to where the view is on the screen or whether it needs to - // "grow up" or "grow down." The below call to layoutSubviews will position - // each view correctly. + // Change the height of the view and call |-layoutSubViews|. We set the height + // here without regard to where the view is on the screen or whether it needs + // to "grow up" or "grow down." The below call to |-layoutSubviews| will + // position each view correctly. NSRect frame = [view frame]; if (frame.size.height == height) return; @@ -934,8 +942,11 @@ willPositionSheet:(NSWindow*)sheet return [bookmarkBarController_ isVisible]; } -- (void)updateBookmarkBarVisibility { - [bookmarkBarController_ updateVisibility]; +- (void)updateBookmarkBarVisibilityWithAnimation:(BOOL)animate { + [bookmarkBarController_ + updateAndShowNormalBar:[self shouldShowBookmarkBar] + showDetachedBar:[self shouldShowDetachedBookmarkBar] + withAnimation:animate]; } - (BOOL)isDownloadShelfVisible { @@ -1082,6 +1093,9 @@ willPositionSheet:(NSWindow*)sheet UpdateToolbar(newContents, true); UpdateUIForContents(newContents); #endif + + // Update the bookmark bar. + [self updateBookmarkBarVisibilityWithAnimation:NO]; } - (void)tabChangedWithContents:(TabContents*)contents @@ -1091,6 +1105,9 @@ willPositionSheet:(NSWindow*)sheet // Update titles if this is the currently selected tab. windowShim_->UpdateTitleBar(); } + + // Update the bookmark bar. + [self updateBookmarkBarVisibilityWithAnimation:NO]; } - (void)userChangedTheme { @@ -1199,6 +1216,28 @@ willPositionSheet:(NSWindow*)sheet } } +// (Needed for |BookmarkBarControllerDelegate| protocol.) +- (void)bookmarkBar:(BookmarkBarController*)controller + didChangeFromState:(bookmarks::VisualState)oldState + toState:(bookmarks::VisualState)newState { + [toolbarController_ + setHeightCompression:[controller getDesiredToolbarHeightCompression]]; + [toolbarController_ setShowsDivider:[controller shouldToolbarShowDivider]]; + + // TODO(viettrungluu): anything else? +} + +// (Needed for |BookmarkBarControllerDelegate| protocol.) +- (void)bookmarkBar:(BookmarkBarController*)controller +willAnimateFromState:(bookmarks::VisualState)oldState + toState:(bookmarks::VisualState)newState { + [toolbarController_ + setHeightCompression:[controller getDesiredToolbarHeightCompression]]; + [toolbarController_ setShowsDivider:[controller shouldToolbarShowDivider]]; + + // TODO(viettrungluu): anything else? +} + @end @implementation BrowserWindowController (Private) @@ -1285,25 +1324,30 @@ willPositionSheet:(NSWindow*)sheet - (NSRect)window:(NSWindow*)window willPositionSheet:(NSWindow*)sheet usingRect:(NSRect)defaultSheetRect { - // Any sheet should come from right above the apparent content area, or - // equivalently right below the apparent toolbar. - NSRect toolbarFrame = [[toolbarController_ view] frame]; - defaultSheetRect.origin.y = toolbarFrame.origin.y; - - // Position as follows: - // - On a normal (non-NTP) page, position the sheet under the bookmark bar if - // it's visible. Else put it immediately below the normal toolbar. - // - On the NTP, if the bookmark bar is enabled ("always visible"), then it - // looks like a normal bookmark bar, so position the sheet under it; if it - // isn't enabled, the bookmark bar will look as if it's a part of the - // content area, so just put it immediately below the toolbar. - if ([bookmarkBarController_ isVisible] && - (![bookmarkBarController_ isNewTabPage] || - [bookmarkBarController_ isAlwaysVisible])) { - NSRect bookmarkBarFrame = [[bookmarkBarController_ view] frame]; - defaultSheetRect.origin.y -= bookmarkBarFrame.size.height; + // Position the sheet as follows: + // - If the bookmark bar is hidden or shown as a bubble (on the NTP when the + // bookmark bar is disabled), position the sheet immediately below the + // normal toolbar. + // - If the bookmark bar is shown (attached to the normal toolbar), position + // the sheet below the bookmark bar. + // - If the bookmark bar is currently animating, position the sheet according + // to where the bar will be when the animation ends. + switch ([bookmarkBarController_ visualState]) { + case bookmarks::kShowingState: { + NSRect bookmarkBarFrame = [[bookmarkBarController_ view] frame]; + defaultSheetRect.origin.y = bookmarkBarFrame.origin.y; + break; + } + case bookmarks::kHiddenState: + case bookmarks::kDetachedState: { + NSRect toolbarFrame = [[toolbarController_ view] frame]; + defaultSheetRect.origin.y = toolbarFrame.origin.y; + break; + } + default: + case bookmarks::kInvalidState: + NOTREACHED(); } - return defaultSheetRect; } @@ -1384,7 +1428,18 @@ willPositionSheet:(NSWindow*)sheet } [toolbarView setFrame:toolbarFrame]; - if ([bookmarkBarController_ isAlwaysVisible]) { + bookmarks::VisualState bookmarkBarState = + [bookmarkBarController_ visualState]; + bookmarks::VisualState lastBookmarkBarState = + [bookmarkBarController_ lastVisualState]; + + // If the bookmark bar is showing, or animating between showing and hidden, + // place the bookmark bar immediately below the toolbar. + // TODO(viettrungluu): Improve/abstract this when I implement other + // animations. + if ((bookmarkBarState == bookmarks::kShowingState) || + (bookmarkBarState == bookmarks::kHiddenState && + lastBookmarkBarState == bookmarks::kShowingState)) { NSView* bookmarkBarView = [bookmarkBarController_ view]; [bookmarkBarView setHidden:NO]; NSRect bookmarkBarFrame = [bookmarkBarView frame]; @@ -1402,8 +1457,8 @@ willPositionSheet:(NSWindow*)sheet [infoBarView setFrame:infoBarFrame]; maxY -= NSHeight(infoBarFrame); - if (![bookmarkBarController_ isAlwaysVisible] && - [bookmarkBarController_ isVisible]) { + // If the bookmark bar is detached, place it at the bottom of the stack. + if (bookmarkBarState == bookmarks::kDetachedState) { NSView* bookmarkBarView = [bookmarkBarController_ view]; [bookmarkBarView setHidden:NO]; NSRect bookmarkBarFrame = [bookmarkBarView frame]; @@ -1413,12 +1468,6 @@ willPositionSheet:(NSWindow*)sheet maxY -= NSHeight(bookmarkBarFrame); } - if (![bookmarkBarController_ isVisible]) { - // If the bookmark bar is not visible in either mode, we need to hide it - // otherwise it'll render over other elements. - [[bookmarkBarController_ view] setHidden:YES]; - } - // Place the extension shelf at the bottom of the view, if it exists. if (extensionShelfController_.get()) { NSView* extensionView = [extensionShelfController_ view]; @@ -1453,15 +1502,22 @@ willPositionSheet:(NSWindow*)sheet verticalOffsetForStatusBubble_ = minY; - // The bottom of the visible toolbar stack is the one that shows the - // divider stroke. If the bookmark bar is visible and not in new tab page - // mode, it is the bottom visible toolbar and so it must, otherwise the - // main toolbar is. - BOOL bookmarkToolbarShowsDivider = [bookmarkBarController_ isAlwaysVisible]; - [[toolbarController_ backgroundGradientView] - setShowsDivider:!bookmarkToolbarShowsDivider]; - [[bookmarkBarController_ backgroundGradientView] - setShowsDivider:bookmarkToolbarShowsDivider]; + // Normally, we don't need to tell the toolbar whether or not to show the + // divider, but things break down during animation. + [toolbarController_ + setShowsDivider:[bookmarkBarController_ shouldToolbarShowDivider]]; +} + +- (BOOL)shouldShowBookmarkBar { + DCHECK(browser_.get()); + return browser_->profile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar) ? + YES : NO; +} + +- (BOOL)shouldShowDetachedBookmarkBar { + DCHECK(browser_.get()); + TabContents* contents = browser_->GetSelectedTabContents(); + return (contents && contents->ShouldShowBookmarkBar()) ? YES : NO; } @end diff --git a/chrome/browser/cocoa/browser_window_controller_unittest.mm b/chrome/browser/cocoa/browser_window_controller_unittest.mm index e2b7451..b03d095 100644 --- a/chrome/browser/cocoa/browser_window_controller_unittest.mm +++ b/chrome/browser/cocoa/browser_window_controller_unittest.mm @@ -105,6 +105,7 @@ TEST_F(BrowserWindowControllerTest, TestNormal) { // Force the bookmark bar to be shown. browser_helper_.profile()->GetPrefs()-> SetBoolean(prefs::kShowBookmarkBar, true); + [controller_ updateBookmarkBarVisibilityWithAnimation:NO]; // Make sure a normal BrowserWindowController is, uh, normal. EXPECT_TRUE([controller_ isNormalWindow]); @@ -139,7 +140,7 @@ TEST_F(BrowserWindowControllerTest, BookmarkBarControllerIndirection) { browser_helper_.profile()->GetPrefs()-> SetBoolean(prefs::kShowBookmarkBar, true); - [controller_ updateBookmarkBarVisibility]; + [controller_ updateBookmarkBarVisibilityWithAnimation:NO]; EXPECT_TRUE([controller_ isBookmarkBarVisible]); } diff --git a/chrome/browser/cocoa/toolbar_compressable.h b/chrome/browser/cocoa/toolbar_compressable.h deleted file mode 100644 index ddd21c4..0000000 --- a/chrome/browser/cocoa/toolbar_compressable.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2009 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_TOOLBAR_COMPRESSABLE_H_ -#define CHROME_BROWSER_COCOA_TOOLBAR_COMPRESSABLE_H_ - -#include "chrome/browser/tabs/tab_strip_model.h" - -#import <Cocoa/Cocoa.h> - -// Defines a protocol that allows one view to tell another view that it should -// remove pixels from the bottom of the view. Only ToolbarController implements -// this, but it's a protocol for unit testing reasons. -@protocol ToolbarCompressable -- (void)setShouldBeCompressed:(BOOL)compressed; -@end - -#endif // CHROME_BROWSER_COCOA_TOOLBAR_COMPRESSABLE_H_ diff --git a/chrome/browser/cocoa/toolbar_controller.h b/chrome/browser/cocoa/toolbar_controller.h index 991ff39..f46e264 100644 --- a/chrome/browser/cocoa/toolbar_controller.h +++ b/chrome/browser/cocoa/toolbar_controller.h @@ -10,7 +10,6 @@ #include "base/scoped_ptr.h" #include "base/scoped_nsobject.h" #import "chrome/browser/cocoa/command_observer_bridge.h" -#import "chrome/browser/cocoa/toolbar_compressable.h" #import "chrome/browser/cocoa/delayedmenu_button.h" #import "chrome/browser/cocoa/view_resizer.h" #include "chrome/common/pref_member.h" @@ -18,7 +17,6 @@ @class AutocompleteTextField; @class AutocompleteTextFieldEditor; @class BackForwardMenuController; -@class BackgroundGradientView; class Browser; @class BrowserActionsController; class BubblePositioner; @@ -41,7 +39,7 @@ class ToolbarView; // the web content view. @interface ToolbarController : - NSViewController<CommandObserverProtocol, ToolbarCompressable> { + NSViewController<CommandObserverProtocol> { @private ToolbarModel* toolbarModel_; // weak, one per window CommandUpdater* commands_; // weak, one per window @@ -111,9 +109,6 @@ class ToolbarView; // returns nil if we don't want to override the custom field editor for |obj|. - (id)customFieldEditorForObject:(id)obj; -// Returns the backdrop to the toolbar. -- (BackgroundGradientView*)backgroundGradientView; - // Make the location bar the first responder, if possible. - (void)focusLocationBar; @@ -140,6 +135,14 @@ class ToolbarView; // Somewhere near the star button seems like a good start. - (NSRect)starButtonInWindowCoordinates; +// Chop off the bottom of the toolbar by |compressByHeight|; needed when the +// bookmark bar is attached. +- (void)setHeightCompression:(CGFloat)compressByHeight; + +// Display (or not) the divider (line at bottom); needed when the bookmark bar +// is attached. +- (void)setShowsDivider:(BOOL)showDivider; + @end // A set of private methods used by tests, in the absence of "friends" in ObjC. diff --git a/chrome/browser/cocoa/toolbar_controller.mm b/chrome/browser/cocoa/toolbar_controller.mm index 9092b51..8653f49 100644 --- a/chrome/browser/cocoa/toolbar_controller.mm +++ b/chrome/browser/cocoa/toolbar_controller.mm @@ -15,6 +15,7 @@ #import "chrome/browser/cocoa/autocomplete_text_field.h" #import "chrome/browser/cocoa/autocomplete_text_field_editor.h" #import "chrome/browser/cocoa/back_forward_menu_controller.h" +#import "chrome/browser/cocoa/background_gradient_view.h" #import "chrome/browser/cocoa/encoding_menu_controller_delegate_mac.h" #import "chrome/browser/cocoa/extensions/browser_actions_controller.h" #import "chrome/browser/cocoa/gradient_button_cell.h" @@ -45,12 +46,10 @@ static NSString* const kWrenchButtonImageName = @"menu_chrome_Template.pdf"; // Height of the toolbar in pixels when the bookmark bar is closed. static const float kBaseToolbarHeight = 36.0; -// Overlap (in pixels) between the toolbar and the bookmark bar. -static const float kBookmarkBarOverlap = 6.0; - @interface ToolbarController(Private) - (void)initCommandStatus:(CommandUpdater*)commands; - (void)prefChanged:(std::wstring*)prefName; +- (BackgroundGradientView*)backgroundGradientView; - (void)browserActionsChanged; - (void)adjustLocationAndGoPositionsBy:(CGFloat)dX; @end @@ -352,7 +351,10 @@ class PrefObserverBridge : public NotificationObserver { return locationBar_; } +// (Private) Returns the backdrop to the toolbar. - (BackgroundGradientView*)backgroundGradientView { + // We really do mean |[super view]|; see our override of |-view|. + DCHECK([[super view] isKindOfClass:[BackgroundGradientView class]]); return (BackgroundGradientView*)[super view]; } @@ -514,14 +516,16 @@ class PrefObserverBridge : public NotificationObserver { fromView:starButton_]; } -- (void)setShouldBeCompressed:(BOOL)compressed { - CGFloat newToolbarHeight = kBaseToolbarHeight; - if (compressed) - newToolbarHeight -= kBookmarkBarOverlap; - +- (void)setHeightCompression:(CGFloat)compressByHeight { + // Resize. + CGFloat newToolbarHeight = kBaseToolbarHeight - compressByHeight; [resizeDelegate_ resizeView:[self view] newHeight:newToolbarHeight]; } +- (void)setShowsDivider:(BOOL)showDivider { + [[self backgroundGradientView] setShowsDivider:showDivider]; +} + - (NSString*)view:(NSView*)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point diff --git a/chrome/browser/cocoa/toolbar_controller_unittest.mm b/chrome/browser/cocoa/toolbar_controller_unittest.mm index 5c731fd..37b20cb 100644 --- a/chrome/browser/cocoa/toolbar_controller_unittest.mm +++ b/chrome/browser/cocoa/toolbar_controller_unittest.mm @@ -296,14 +296,11 @@ TEST_F(ToolbarControllerTest, PopulateEncodingMenu) { EXPECT_NE(0, [encodings numberOfItems]); } -TEST_F(ToolbarControllerTest, CorrectHeightWhenCompressed) { - [bar_ setShouldBeCompressed:YES]; - EXPECT_EQ(30.0, [resizeDelegate_ height]); -} - -TEST_F(ToolbarControllerTest, CorrectHeightWhenUnompressed) { - [bar_ setShouldBeCompressed:NO]; - EXPECT_EQ(36.0, [resizeDelegate_ height]); +TEST_F(ToolbarControllerTest, HeightCompression) { + for (int i = 0; i <= 10; i++) { + [bar_ setHeightCompression:static_cast<CGFloat>(i)]; + EXPECT_EQ(static_cast<CGFloat>(36 - i), [resizeDelegate_ height]); + } } } // namespace diff --git a/chrome/browser/cocoa/view_resizer_pong.mm b/chrome/browser/cocoa/view_resizer_pong.mm index 9246af7..78879e9 100644 --- a/chrome/browser/cocoa/view_resizer_pong.mm +++ b/chrome/browser/cocoa/view_resizer_pong.mm @@ -14,6 +14,7 @@ [self setHeight:height]; // Set the view's height and width, in case it uses that as important state. - [view setFrame:NSMakeRect(100, 100, 250, height)]; + [view setFrame:NSMakeRect(100, 50, + NSWidth([[view superview] frame]) - 50, height)]; } @end |