Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(298)

Unified Diff: chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm

Issue 2751573002: [Mac] Refactor bookmark bar controller (Closed)
Patch Set: Factor layout tests to helper, address CL comments Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm
index f0fb4ca20654aeb48501eaf2461812b3fb69804f..2e375d052275f7d5e4c9b885a6daf1238300410a 100644
--- a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm
+++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm
@@ -43,6 +43,7 @@
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_model_observer_for_cocoa.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_name_folder_controller.h"
#import "chrome/browser/ui/cocoa/browser_window_controller.h"
+#import "chrome/browser/ui/cocoa/l10n_util.h"
#import "chrome/browser/ui/cocoa/menu_button.h"
#import "chrome/browser/ui/cocoa/themed_window.h"
#import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
@@ -78,6 +79,7 @@
#include "ui/resources/grit/ui_resources.h"
using base::UserMetricsAction;
+using bookmarks::BookmarkBarLayout;
using bookmarks::BookmarkModel;
using bookmarks::BookmarkNode;
using bookmarks::BookmarkNodeData;
@@ -144,6 +146,25 @@ using content::WebContents;
// - The BWC should implement |-bookmarkBar:didChangeFromState:toState:| and
// |-bookmarkBar:willAnimateFromState:toState:| in order to inform the
// toolbar of required changes.
+//
+// Layout:
+//
+// Several events (initial load, changes to the bookmark model etc.) can
+// require the bar layout to change. In most cases, this is accomplished
+// by building a BookmarkBarLayout from the current state of the view,
+// the bookmark model, and the managed bookmark service. If the calculated
+// layout differs from the previous one, it's applied to the view
+// via |applyLayout:animated:|. This is a cheap way to "coalesce" multiple
+// potentially layout-changing events, since in practice, these events come
+// in bursts and don't require a change.
+//
+// Temporary changes in layout during dragging are an exception to this,
+// since the layout temporarily adjusts to the drag (for example, adding
+// a placeholder space for a mid-drag button or removing the space previously
+// taken up by a button which is being dragged off the bar). In this case,
+// the original stored layout is maintained as the source of truth, and
+// elements are laid out from a combination of the stored layout and the
+// drag state. See |setDropInsertionPos:| for details.
namespace {
@@ -151,6 +172,11 @@ namespace {
const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
const NSTimeInterval kDragAndDropAnimationDuration = 0.25;
+const int kMaxReusePoolSize = 10;
+
+// Min width for no item text field and import bookmarks button.
+const CGFloat kNoItemElementMinWidth = 30;
+
void RecordAppLaunch(Profile* profile, GURL url) {
const extensions::Extension* extension =
extensions::ExtensionRegistry::Get(profile)->
@@ -162,94 +188,71 @@ void RecordAppLaunch(Profile* profile, GURL url) {
extension->GetType());
}
-} // namespace
-
-@interface BookmarkBarController ()
-
-// Updates the sizes and positions of the subviews.
-- (void)layoutSubviews;
-
-// 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)moveToState:(BookmarkBar::State)nextState
- withAnimation:(BOOL)animate;
-
-// Create buttons for all items in the given bookmark node tree.
-// Modifies self->buttons_. Do not add more buttons than will fit on the view.
-- (void)addNodesToButtonList:(const BookmarkNode*)node;
-
-// Create an autoreleased button appropriate for insertion into the bookmark
-// bar. Update |xOffset| with the offset appropriate for the subsequent button.
-- (BookmarkButton*)buttonForNode:(const BookmarkNode*)node
- xOffset:(int*)xOffset;
-
-// Find a parent whose button is visible on the bookmark bar.
-- (BookmarkButton*)bookmarkButtonToPulseForNode:(const BookmarkNode*)node;
-
-// Puts stuff into the final state without animating, stopping a running
-// animation if necessary.
-- (void)finalizeState;
-
-// Stops any current animation in its tracks (midway).
-- (void)stopCurrentAnimation;
-
-// Show/hide the bookmark bar.
-// if |animate| is YES, the changes are made using the animator; otherwise they
-// are made immediately.
-- (void)showBookmarkBarWithAnimation:(BOOL)animate;
-
-// Handles animating the resize of the content view. Returns YES if it handled
-// the animation, NO if not (and hence it should be done instantly).
-- (BOOL)doBookmarkBarAnimation;
-
-// |point| is in the base coordinate system of the destination window;
-// it comes from an id<NSDraggingInfo>. |copy| is YES if a copy is to be
-// made and inserted into the new location while leaving the bookmark in
-// the old location, otherwise move the bookmark by removing from its old
-// location and inserting into the new location.
-- (BOOL)dragBookmark:(const BookmarkNode*)sourceNode
- to:(NSPoint)point
- copy:(BOOL)copy;
+const CGFloat kBookmarkButtonHeightMinusPadding =
+ bookmarks::kBookmarkButtonHeight - bookmarks::kBookmarkVerticalPadding * 2;
-// Returns the index in the model for a drag to the location given by
-// |point|. This is determined by finding the first button before the center
-// of which |point| falls, scanning left to right. Note that, currently, only
-// the x-coordinate of |point| is considered. Though not currently implemented,
-// we may check for errors, in which case this would return negative value;
-// callers should check for this.
-- (int)indexForDragToPoint:(NSPoint)point;
-
-// Add or remove buttons to/from the bar until it is filled but not overflowed.
-- (void)redistributeButtonsOnBarAsNeeded;
-
-// Determine the nature of the bookmark bar contents based on the number of
-// buttons showing. If too many then show the off-the-side list, if none
-// then show the no items label.
-- (void)reconfigureBookmarkBar;
-
-- (int)preferredHeight;
-- (void)addButtonsToView;
-- (BOOL)setManagedBookmarksButtonVisibility;
-- (BOOL)setSupervisedBookmarksButtonVisibility;
-- (BOOL)setOtherBookmarksButtonVisibility;
-- (BOOL)setAppsPageShortcutButtonVisibility;
-- (BookmarkButton*)createCustomBookmarkButtonForCell:(NSCell*)cell;
-- (void)createManagedBookmarksButton;
-- (void)createSupervisedBookmarksButton;
-- (void)createOtherBookmarksButton;
-- (void)createAppsPageShortcutButton;
-- (void)openAppsPage:(id)sender;
-- (void)centerNoItemsLabel;
-- (void)positionRightSideButtons;
-- (void)watchForExitEvent:(BOOL)watch;
-- (void)resetAllButtonPositionsWithAnimation:(BOOL)animate;
-
-@end
+} // namespace
-@implementation BookmarkBarController
+namespace bookmarks {
+
+BookmarkBarLayout::BookmarkBarLayout()
+ : visible_elements(0),
+ apps_button_offset(0),
+ managed_bookmarks_button_offset(0),
+ supervised_bookmarks_button_offset(0),
+ off_the_side_button_offset(0),
+ other_bookmarks_button_offset(0),
+ no_item_textfield_offset(0),
+ no_item_textfield_width(0),
+ import_bookmarks_button_offset(0),
+ import_bookmarks_button_width(0),
+ max_x(0){};
+BookmarkBarLayout::~BookmarkBarLayout(){};
+BookmarkBarLayout::BookmarkBarLayout(BookmarkBarLayout&& other) = default;
+BookmarkBarLayout& BookmarkBarLayout::operator=(BookmarkBarLayout&& other) =
+ default;
+
+bool operator==(const BookmarkBarLayout& lhs, const BookmarkBarLayout& rhs) {
+ return std::tie(lhs.visible_elements, lhs.apps_button_offset,
+ lhs.managed_bookmarks_button_offset,
+ lhs.supervised_bookmarks_button_offset,
+ lhs.off_the_side_button_offset,
+ lhs.other_bookmarks_button_offset,
+ lhs.no_item_textfield_offset, lhs.no_item_textfield_width,
+ lhs.import_bookmarks_button_offset,
+ lhs.import_bookmarks_button_width, lhs.button_offsets,
+ lhs.max_x) ==
+ std::tie(
+ rhs.visible_elements, rhs.apps_button_offset,
+ rhs.managed_bookmarks_button_offset,
+ rhs.supervised_bookmarks_button_offset,
+ rhs.off_the_side_button_offset, rhs.other_bookmarks_button_offset,
+ rhs.no_item_textfield_offset, rhs.no_item_textfield_width,
+ rhs.import_bookmarks_button_offset,
+ rhs.import_bookmarks_button_width, rhs.button_offsets, rhs.max_x);
+}
+
+bool operator!=(const BookmarkBarLayout& lhs, const BookmarkBarLayout& rhs) {
+ return !(lhs == rhs);
+}
+
+} // namespace bookmarks
+
+@implementation BookmarkBarController {
+ BookmarkBarLayout layout_;
+ CGFloat originalNoItemTextFieldWidth_;
+ CGFloat originalImportBookmarksButtonWidth_;
+ CGFloat originalNoItemInterelementPadding_;
+ BOOL didCreateExtraButtons_;
+
+ // Maps bookmark node IDs to instantiated buttons for ease of lookup.
+ std::unordered_map<int64_t, base::scoped_nsobject<BookmarkButton>>
+ nodeIdToButtonMap_;
+
+ // A place to stash bookmark buttons that have been removed from the bar
+ // so that they can be reused instead of creating new ones.
+ base::scoped_nsobject<NSMutableArray> unusedButtonPool_;
+}
@synthesize currentState = currentState_;
@synthesize lastState = lastState_;
@@ -261,8 +264,7 @@ void RecordAppLaunch(Profile* profile, GURL url) {
- (id)initWithBrowser:(Browser*)browser
initialWidth:(CGFloat)initialWidth
delegate:(id<BookmarkBarControllerDelegate>)delegate {
- if ((self = [super initWithNibName:@"BookmarkBar"
- bundle:base::mac::FrameworkBundle()])) {
+ if ((self = [super initWithNibName:nil bundle:nil])) {
currentState_ = BookmarkBar::HIDDEN;
lastState_ = BookmarkBar::HIDDEN;
@@ -273,10 +275,12 @@ void RecordAppLaunch(Profile* profile, GURL url) {
managedBookmarkService_ =
ManagedBookmarkServiceFactory::GetForProfile(browser_->profile());
buttons_.reset([[NSMutableArray alloc] init]);
+ unusedButtonPool_.reset([[NSMutableArray alloc] init]);
delegate_ = delegate;
folderTarget_.reset(
[[BookmarkFolderTarget alloc] initWithController:self
profile:browser_->profile()]);
+ didCreateExtraButtons_ = NO;
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
folderImage_.reset(
@@ -333,21 +337,18 @@ void RecordAppLaunch(Profile* profile, GURL url) {
buttonView_.reset([[BookmarkBarView alloc]
initWithController:self
frame:NSMakeRect(0, -2, 584, 144)]);
- [buttonView_ setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin |
- NSViewMaxXMargin];
+ [buttonView_ setAutoresizingMask:NSViewWidthSizable | NSViewMaxXMargin];
[[buttonView_ importBookmarksButton] setTarget:self];
[[buttonView_ importBookmarksButton] setAction:@selector(importBookmarks:)];
- [self createOffTheSideButton];
- [buttonView_ addSubview:offTheSideButton_];
-
[self.view addSubview:buttonView_];
- // viewDidLoad became part of the API in 10.10
+
+ // viewDidLoad became part of the API in 10.10.
if (!base::mac::IsAtLeastOS10_10())
[self viewDidLoad];
}
-- (BookmarkButton*)bookmarkButtonToPulseForNode:(const BookmarkNode*)node {
+- (BookmarkButton*)findAncestorButtonOnBarForNode:(const BookmarkNode*)node {
// Find the closest parent that is visible on the bar.
while (node) {
// Check if we've reached one of the special buttons. Otherwise, if the next
@@ -382,7 +383,7 @@ void RecordAppLaunch(Profile* profile, GURL url) {
- (void)startPulsingBookmarkNode:(const BookmarkNode*)node {
[self stopPulsingBookmarkNode];
- pulsingButton_.reset([self bookmarkButtonToPulseForNode:node],
+ pulsingButton_.reset([self findAncestorButtonOnBarForNode:node],
base::scoped_policy::RETAIN);
if (!pulsingButton_)
return;
@@ -438,12 +439,20 @@ void RecordAppLaunch(Profile* profile, GURL url) {
[view performSelector:@selector(setController:) withObject:nil];
// For safety, make sure the buttons can no longer call us.
- for (BookmarkButton* button in buttons_.get()) {
+ base::scoped_nsobject<NSMutableArray> buttons([buttons_ mutableCopy]);
+ [buttons addObjectsFromArray:unusedButtonPool_];
+ if (didCreateExtraButtons_) {
+ [buttons addObjectsFromArray:@[
+ appsPageShortcutButton_, managedBookmarksButton_,
+ supervisedBookmarksButton_, otherBookmarksButton_, offTheSideButton_
+ ]];
+ }
+
+ for (BookmarkButton* button in buttons.get()) {
[button setDelegate:nil];
[button setTarget:nil];
[button setAction:nil];
}
-
bridge_.reset(NULL);
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self watchForExitEvent:NO];
@@ -456,20 +465,13 @@ void RecordAppLaunch(Profile* profile, GURL url) {
// Remember the original sizes of the 'no items' and 'import bookmarks'
// fields to aid in resizing when the window frame changes.
- originalNoItemsRect_ = [[buttonView_ noItemTextfield] frame];
- originalImportBookmarksRect_ = [[buttonView_ importBookmarksButton] frame];
-
- // Bookmark buttons start farther from the bookmark bar's left edge so
- // adjust the positions of the noItems and importBookmarks textfields.
- const CGFloat kBookmarksTextfieldOffsetX = 14;
- originalNoItemsRect_.origin.x += kBookmarksTextfieldOffsetX;
- [[buttonView_ noItemTextfield] setFrame:originalNoItemsRect_];
+ NSRect noItemTextFieldFrame = [[buttonView_ noItemTextField] frame];
+ NSRect noItemButtonFrame = [[buttonView_ importBookmarksButton] frame];
+ originalNoItemTextFieldWidth_ = NSWidth(noItemTextFieldFrame);
+ originalImportBookmarksButtonWidth_ = NSWidth(noItemButtonFrame);
+ originalNoItemInterelementPadding_ =
+ NSMinX(noItemButtonFrame) - NSMaxX(noItemTextFieldFrame);
- originalImportBookmarksRect_.origin.x += kBookmarksTextfieldOffsetX;
- [[buttonView_ importBookmarksButton] setFrame:originalImportBookmarksRect_];
-
- // When resized we may need to add new buttons, or remove them (if
- // no longer visible), or add/remove the "off the side" menu.
[[self view] setPostsFrameChangedNotifications:YES];
[[NSNotificationCenter defaultCenter]
addObserver:self
@@ -488,6 +490,7 @@ void RecordAppLaunch(Profile* profile, GURL url) {
selector:@selector(willEnterOrLeaveFullscreen:)
name:NSWindowWillExitFullScreenNotification
object:nil];
+ [self layoutSubviews];
// Don't pass ourself along (as 'self') until our init is completely
// done. Thus, this call is (almost) last.
@@ -553,6 +556,7 @@ void RecordAppLaunch(Profile* profile, GURL url) {
[[self view] autoresizingMask]);
[[self view] setFrame:frame];
[self layoutSubviews];
+ [self frameDidChange];
}
// Change the layout of the bookmark bar's subviews in response to a visibility
@@ -568,7 +572,6 @@ void RecordAppLaunch(Profile* profile, GURL url) {
padding = bookmarks::kNTPBookmarkBarPadding;
buttonViewFrame =
NSInsetRect(buttonViewFrame, morph * padding, morph * padding);
-
[buttonView_ setFrame:buttonViewFrame];
// Update bookmark button backgrounds.
@@ -591,19 +594,6 @@ void RecordAppLaunch(Profile* profile, GURL url) {
[self showBookmarkBarWithAnimation:NO];
}
-- (void)updateExtraButtonsVisibility {
- if (!appsPageShortcutButton_.get() ||
- !managedBookmarksButton_.get() ||
- !supervisedBookmarksButton_.get()) {
- return;
- }
- [self setAppsPageShortcutButtonVisibility];
- [self setManagedBookmarksButtonVisibility];
- [self setSupervisedBookmarksButtonVisibility];
- [self resetAllButtonPositionsWithAnimation:NO];
- [self reconfigureBookmarkBar];
-}
-
- (void)updateHiddenState {
BOOL oldHidden = [[self view] isHidden];
BOOL newHidden = ![self isVisible];
@@ -828,7 +818,7 @@ void RecordAppLaunch(Profile* profile, GURL url) {
// Middle click on chevron should not open bookmarks under it, instead just
// open its folder menu.
if (sender == offTheSideButton_.get()) {
- [[sender cell] setStartingChildIndex:displayedButtonCount_];
+ [[sender cell] setStartingChildIndex:layout_.VisibleButtonCount()];
NSEvent* event = [NSApp currentEvent];
if ([event type] == NSOtherMouseUp) {
[self openOrCloseBookmarkFolderForOffTheSideButton];
@@ -891,55 +881,6 @@ void RecordAppLaunch(Profile* profile, GURL url) {
BOOKMARK_LAUNCH_LOCATION_ATTACHED_BAR;
}
-// Position the right-side buttons including the off-the-side chevron.
-- (void)positionRightSideButtons {
- int maxX = NSMaxX([[self buttonView] bounds]) -
- bookmarks::kBookmarkHorizontalPadding;
- int right = maxX;
-
- int ignored = 0;
- NSRect frame = [self frameForBookmarkButtonFromCell:
- [otherBookmarksButton_ cell] xOffset:&ignored];
- if (![otherBookmarksButton_ isHidden]) {
- right -= NSWidth(frame);
- frame.origin.x = right;
- } else {
- frame.origin.x = maxX - NSWidth(frame);
- }
- [otherBookmarksButton_ setFrame:frame];
-
- frame = [offTheSideButton_ frame];
- frame.size.height = bookmarks::kBookmarkFolderButtonHeight;
- right -= frame.size.width;
- frame.origin.x = right;
- [offTheSideButton_ setFrame:frame];
-}
-
-// Configure the off-the-side button (e.g. specify the node range,
-// check if we should enable or disable it, etc).
-- (void)configureOffTheSideButtonContentsAndVisibility {
- [[offTheSideButton_ cell] setStartingChildIndex:displayedButtonCount_];
- [[offTheSideButton_ cell]
- setBookmarkNode:bookmarkModel_->bookmark_bar_node()];
- int bookmarkChildren = bookmarkModel_->bookmark_bar_node()->child_count();
- if (bookmarkChildren > displayedButtonCount_) {
- [offTheSideButton_ setHidden:NO];
- // Set the off the side button as needing re-display. This is needed to
- // avoid the button being shown with a black background the first time
- // it's displayed. See https://codereview.chromium.org/1630453002/ for
- // more context.
- [offTheSideButton_ setNeedsDisplay:YES];
- } else {
- // If we just deleted the last item in an off-the-side menu so the
- // button will be going away, make sure the menu goes away.
- if (folderController_ &&
- ([folderController_ parentButton] == offTheSideButton_))
- [self closeAllBookmarkFolders];
- // (And hide the button, too.)
- [offTheSideButton_ setHidden:YES];
- }
-}
-
// Main menubar observation code, so we can know to close our fake menus if the
// user clicks on the actual menubar, as multiple unconnected menus sharing
// the screen looks weird.
@@ -996,19 +937,9 @@ void RecordAppLaunch(Profile* profile, GURL url) {
}
}
-// Keep the "no items" label centered in response to a frame size change.
-- (void)centerNoItemsLabel {
- // Note that this computation is done in the parent's coordinate system,
- // which is unflipped. Also, we want the label to be a fixed distance from
- // the bottom, so that it slides up properly (on animating to hidden).
- // The textfield sits in the itemcontainer, so to center it we maintain
- // equal vertical padding on the top and bottom.
- int yoffset = (NSHeight([[buttonView_ noItemTextfield] frame]) -
- NSHeight([[buttonView_ noItemContainer] frame])) / 2;
- [[buttonView_ noItemContainer] setFrameOrigin:NSMakePoint(0, yoffset)];
-}
-
-// (Private)
+// Show/hide the bookmark bar.
+// If |animate| is YES, the changes are made using the animator; otherwise they
+// are made immediately.
- (void)showBookmarkBarWithAnimation:(BOOL)animate {
if (animate && stateAnimationsEnabled_) {
// If |-doBookmarkBarAnimation| does the animation, we're done.
@@ -1035,7 +966,8 @@ void RecordAppLaunch(Profile* profile, GURL url) {
[self frameDidChange];
}
-// (Private)
+// Handles animating the resize of the content view. Returns YES if it handled
+// the animation, NO if not (and hence it should be done instantly).
- (BOOL)doBookmarkBarAnimation {
BookmarkBarToolbarView* view = [self controlledView];
if ([self isAnimatingFromState:BookmarkBar::HIDDEN
@@ -1106,189 +1038,72 @@ void RecordAppLaunch(Profile* profile, GURL url) {
return std::min([cell cellSize].width, bookmarks::kDefaultBookmarkWidth);
}
-// For the given root node of the bookmark bar, show or hide (as
-// appropriate) the "no items" container (text which says "bookmarks
-// go here").
-- (void)showOrHideNoItemContainerForNode:(const BookmarkNode*)node {
- BOOL hideNoItemWarning = !node->empty();
- [[buttonView_ noItemContainer] setHidden:hideNoItemWarning];
-}
-
-// TODO(jrg): write a "build bar" so there is a nice spot for things
-// like the contextual menu which is invoked when not over a
-// bookmark. On Safari that menu has a "new folder" option.
-- (void)addNodesToButtonList:(const BookmarkNode*)node {
- [self showOrHideNoItemContainerForNode:node];
-
- CGFloat maxViewX = NSMaxX([[self view] bounds]);
- int xOffset =
- bookmarks::kBookmarkLeftMargin - bookmarks::kBookmarkHorizontalPadding;
-
- // Draw the apps bookmark if needed.
- if (![appsPageShortcutButton_ isHidden]) {
- NSRect frame =
- [self frameForBookmarkButtonFromCell:[appsPageShortcutButton_ cell]
- xOffset:&xOffset];
- [appsPageShortcutButton_ setFrame:frame];
- }
-
- // Draw the managed bookmark folder if needed.
- if (![managedBookmarksButton_ isHidden]) {
- xOffset += bookmarks::kBookmarkHorizontalPadding;
- NSRect frame =
- [self frameForBookmarkButtonFromCell:[managedBookmarksButton_ cell]
- xOffset:&xOffset];
- [managedBookmarksButton_ setFrame:frame];
- }
-
- // Draw the supervised bookmark folder if needed.
- if (![supervisedBookmarksButton_ isHidden]) {
- xOffset += bookmarks::kBookmarkHorizontalPadding;
- NSRect frame =
- [self frameForBookmarkButtonFromCell:[supervisedBookmarksButton_ cell]
- xOffset:&xOffset];
- [supervisedBookmarksButton_ setFrame:frame];
- }
-
- for (int i = 0; i < node->child_count(); i++) {
- const BookmarkNode* child = node->GetChild(i);
- BookmarkButton* button = [self buttonForNode:child xOffset:&xOffset];
- if (NSMinX([button frame]) >= maxViewX) {
- [button setDelegate:nil];
- break;
- }
- [buttons_ addObject:button];
- }
-}
-
-- (BookmarkButton*)buttonForNode:(const BookmarkNode*)node
- xOffset:(int*)xOffset {
- BookmarkButtonCell* cell = [self cellForBookmarkNode:node];
- NSRect frame = [self frameForBookmarkButtonFromCell:cell xOffset:xOffset];
-
- base::scoped_nsobject<BookmarkButton> button(
- [[BookmarkButton alloc] initWithFrame:frame]);
- DCHECK(button.get());
-
- // [NSButton setCell:] warns to NOT use setCell: other than in the
- // initializer of a control. However, we are using a basic
- // NSButton whose initializer does not take an NSCell as an
- // object. To honor the assumed semantics, we do nothing with
- // NSButton between alloc/init and setCell:.
- [button setCell:cell];
- [button setDelegate:self];
-
- // We cannot set the button cell's text color until it is placed in
- // the button (e.g. the [button setCell:cell] call right above). We
- // also cannot set the cell's text color until the view is added to
- // the hierarchy. If that second part is now true, set the color.
- // (If not we'll set the color on the 1st themeChanged:
- // notification.)
- const ui::ThemeProvider* themeProvider = [[[self view] window] themeProvider];
- if (themeProvider) {
- NSColor* color =
- themeProvider->GetNSColor(ThemeProperties::COLOR_BOOKMARK_TEXT);
- [cell setTextColor:color];
+- (BookmarkButton*)buttonForNode:(const BookmarkNode*)node {
+ BookmarkButton* button = nil;
+ int64_t nodeId = node->id();
+ auto buttonIt = nodeIdToButtonMap_.find(nodeId);
+ if (buttonIt != nodeIdToButtonMap_.end()) {
+ button = (*buttonIt).second.get();
+ [self updateTitleAndTooltipForButton:button];
+ } else if ([unusedButtonPool_ count] > 0) {
+ button = [[[unusedButtonPool_ firstObject] retain] autorelease];
+ [unusedButtonPool_ removeObjectAtIndex:0];
+ BOOL darkTheme = [[[self view] window] hasDarkTheme];
+ [[button cell]
+ setBookmarkNode:node
+ image:[self faviconForNode:node forADarkTheme:darkTheme]];
+ } else {
+ BookmarkButtonCell* cell = [self cellForBookmarkNode:node];
+ NSRect frame = NSMakeRect(0, bookmarks::kBookmarkVerticalPadding, 0,
+ kBookmarkButtonHeightMinusPadding);
+ button = [[[BookmarkButton alloc] initWithFrame:frame] autorelease];
+ [button setCell:cell];
+ [buttonView_ addSubview:button];
+ [button setDelegate:self];
}
+ DCHECK(button);
+ // Do setup.
+ nodeIdToButtonMap_.insert(
+ {nodeId, base::scoped_nsobject<BookmarkButton>([button retain])});
if (node->is_folder()) {
[button setTarget:self];
[button setAction:@selector(openBookmarkFolderFromButton:)];
[[button draggableButton] setActsOnMouseDown:YES];
- // If it has a title, and it will be truncated, show full title in
- // tooltip.
- NSString* title = base::SysUTF16ToNSString(node->GetTitle());
- if ([title length] &&
- [[button cell] cellSize].width > bookmarks::kDefaultBookmarkWidth) {
- [button setToolTip:title];
- }
} else {
- // Make the button do something
+ // Make the button do something.
[button setTarget:self];
[button setAction:@selector(openBookmark:)];
- if (node->is_url())
- [button setToolTip:[BookmarkMenuCocoaController tooltipForNode:node]];
- }
- return [[button.get() retain] autorelease];
-}
-
-// Add bookmark buttons to the view only if they are completely
-// visible and don't overlap the "other bookmarks". Remove buttons
-// which are clipped. Called when building the bookmark bar the first time.
-- (void)addButtonsToView {
- displayedButtonCount_ = 0;
- NSMutableArray* buttons = [self buttons];
- for (NSButton* button in buttons) {
- if (NSMaxX([button frame]) > (NSMinX([offTheSideButton_ frame]) -
- bookmarks::kBookmarkHorizontalPadding))
- break;
- [buttonView_ addSubview:button];
- ++displayedButtonCount_;
+ [[button draggableButton] setActsOnMouseDown:NO];
}
- NSUInteger removalCount =
- [buttons count] - (NSUInteger)displayedButtonCount_;
- if (removalCount > 0) {
- NSRange removalRange = NSMakeRange(displayedButtonCount_, removalCount);
- [buttons removeObjectsInRange:removalRange];
- }
-}
-
-// Shows or hides the Managed, Supervised, or Other Bookmarks button as
-// appropriate, and returns whether it ended up visible.
-- (BOOL)setBookmarkButtonVisibility:(BookmarkButton*)button
- canShow:(BOOL)show
- resetAllButtonPositions:(BOOL)resetButtons {
- if (!button)
- return NO;
-
- BOOL visible = ![button bookmarkNode]->empty() && show;
- BOOL currentVisibility = ![button isHidden];
- if (currentVisibility != visible) {
- [button setHidden:!visible];
- if (resetButtons)
- [self resetAllButtonPositionsWithAnimation:NO];
- }
- return visible;
+ [self updateTitleAndTooltipForButton:button];
+ return button;
}
-// Shows or hides the Managed Bookmarks button as appropriate, and returns
-// whether it ended up visible.
-- (BOOL)setManagedBookmarksButtonVisibility {
- PrefService* prefs = browser_->profile()->GetPrefs();
- BOOL prefIsSet =
- prefs->GetBoolean(bookmarks::prefs::kShowManagedBookmarksInBookmarkBar);
- return [self setBookmarkButtonVisibility:managedBookmarksButton_.get()
- canShow:prefIsSet
- resetAllButtonPositions:YES];
-}
+- (void)updateTitleAndTooltipForButton:(BookmarkButton*)button {
+ const BookmarkNode* node = [button bookmarkNode];
+ CGFloat buttonWidth = [self widthOfButtonForNode:node];
+ NSString* buttonTitle = base::SysUTF16ToNSString(node->GetTitle());
-// Shows or hides the Supervised Bookmarks button as appropriate, and returns
-// whether it ended up visible.
-- (BOOL)setSupervisedBookmarksButtonVisibility {
- return [self setBookmarkButtonVisibility:supervisedBookmarksButton_.get()
- canShow:YES
- resetAllButtonPositions:YES];
-}
+ if (NSWidth([button frame]) == buttonWidth &&
+ [[button title] isEqualToString:buttonTitle])
+ return;
-// Shows or hides the Other Bookmarks button as appropriate, and returns
-// whether it ended up visible.
-- (BOOL)setOtherBookmarksButtonVisibility {
- return [self setBookmarkButtonVisibility:otherBookmarksButton_.get()
- canShow:YES
- resetAllButtonPositions:NO];
-}
+ CGRect frame = [button frame];
+ frame.size.width = buttonWidth;
+ [button setFrame:frame];
+ [[button cell] setTitle:buttonTitle];
+ NSString* tooltip = nil;
-// Shows or hides the Apps button as appropriate, and returns whether it ended
-// up visible.
-- (BOOL)setAppsPageShortcutButtonVisibility {
- if (!appsPageShortcutButton_.get())
- return NO;
+ // Folders show a tooltip iff the title is truncated.
+ if (node->is_folder() && [buttonTitle length] > 0 &&
+ [[button cell] cellSize].width < buttonWidth) {
+ tooltip = buttonTitle;
+ } else if (node->is_url()) {
+ tooltip = [BookmarkMenuCocoaController tooltipForNode:node];
+ }
- BOOL visible =
- bookmarkModel_->loaded() &&
- chrome::ShouldShowAppsShortcutInBookmarkBar(browser_->profile());
- [appsPageShortcutButton_ setHidden:!visible];
- return visible;
+ [button setToolTip:tooltip];
}
// Creates a bookmark bar button that does not correspond to a regular bookmark
@@ -1316,9 +1131,6 @@ void RecordAppLaunch(Profile* profile, GURL url) {
NSCell* cell = [managedBookmarksButton_ cell];
[cell setTitle:title];
- // Its visibility may have changed too.
- [self setManagedBookmarksButtonVisibility];
-
return;
}
@@ -1327,16 +1139,17 @@ void RecordAppLaunch(Profile* profile, GURL url) {
managedBookmarksButton_.reset([self createCustomBookmarkButtonForCell:cell]);
[managedBookmarksButton_ setAction:@selector(openBookmarkFolderFromButton:)];
view_id_util::SetID(managedBookmarksButton_.get(), VIEW_ID_MANAGED_BOOKMARKS);
+ NSRect frame = NSMakeRect(0, bookmarks::kBookmarkVerticalPadding,
+ [self widthForBookmarkButtonCell:cell],
+ kBookmarkButtonHeightMinusPadding);
+ [managedBookmarksButton_ setFrame:frame];
[buttonView_ addSubview:managedBookmarksButton_.get()];
- [self setManagedBookmarksButtonVisibility];
}
// Creates the button for "Supervised Bookmarks", but does not position it.
- (void)createSupervisedBookmarksButton {
if (supervisedBookmarksButton_.get()) {
- // The button's already there, but its visibility may have changed.
- [self setSupervisedBookmarksButtonVisibility];
return;
}
@@ -1348,9 +1161,11 @@ void RecordAppLaunch(Profile* profile, GURL url) {
setAction:@selector(openBookmarkFolderFromButton:)];
view_id_util::SetID(supervisedBookmarksButton_.get(),
VIEW_ID_SUPERVISED_BOOKMARKS);
+ NSRect frame = NSMakeRect(0, bookmarks::kBookmarkVerticalPadding,
+ [self widthForBookmarkButtonCell:cell],
+ kBookmarkButtonHeightMinusPadding);
+ [supervisedBookmarksButton_ setFrame:frame];
[buttonView_ addSubview:supervisedBookmarksButton_.get()];
-
- [self setSupervisedBookmarksButtonVisibility];
}
// Creates the button for "Other Bookmarks", but does not position it.
@@ -1358,27 +1173,23 @@ void RecordAppLaunch(Profile* profile, GURL url) {
// Can't create this until the model is loaded, but only need to
// create it once.
if (otherBookmarksButton_.get()) {
- [self setOtherBookmarksButtonVisibility];
return;
}
NSCell* cell = [self cellForBookmarkNode:bookmarkModel_->other_node()];
otherBookmarksButton_.reset([self createCustomBookmarkButtonForCell:cell]);
- // Peg at right; keep same height as bar.
- [otherBookmarksButton_ setAutoresizingMask:(NSViewMinXMargin)];
[otherBookmarksButton_ setAction:@selector(openBookmarkFolderFromButton:)];
+ NSRect frame = NSMakeRect(0, bookmarks::kBookmarkVerticalPadding,
+ [self widthForBookmarkButtonCell:cell],
+ kBookmarkButtonHeightMinusPadding);
+ [otherBookmarksButton_ setFrame:frame];
view_id_util::SetID(otherBookmarksButton_.get(), VIEW_ID_OTHER_BOOKMARKS);
[buttonView_ addSubview:otherBookmarksButton_.get()];
-
- [self setOtherBookmarksButtonVisibility];
}
// Creates the button for "Apps", but does not position it.
- (void)createAppsPageShortcutButton {
- // Can't create this until the model is loaded, but only need to
- // create it once.
if (appsPageShortcutButton_.get()) {
- [self setAppsPageShortcutButtonVisibility];
return;
}
@@ -1388,20 +1199,29 @@ void RecordAppLaunch(Profile* profile, GURL url) {
IDR_BOOKMARK_BAR_APPS_SHORTCUT).ToNSImage();
NSCell* cell = [self cellForCustomButtonWithText:text
image:image];
+ NSRect frame;
+ frame.origin.y = bookmarks::kBookmarkVerticalPadding;
+ frame.size = NSMakeSize([self widthForBookmarkButtonCell:cell],
+ kBookmarkButtonHeightMinusPadding);
appsPageShortcutButton_.reset([self createCustomBookmarkButtonForCell:cell]);
+ [appsPageShortcutButton_ setFrame:frame];
[[appsPageShortcutButton_ draggableButton] setActsOnMouseDown:NO];
[appsPageShortcutButton_ setAction:@selector(openAppsPage:)];
NSString* tooltip =
l10n_util::GetNSString(IDS_BOOKMARK_BAR_APPS_SHORTCUT_TOOLTIP);
[appsPageShortcutButton_ setToolTip:tooltip];
[buttonView_ addSubview:appsPageShortcutButton_.get()];
-
- [self setAppsPageShortcutButtonVisibility];
}
+// Creates the "off-the-side" (chevron/overflow) button but
+// does not position it.
- (void)createOffTheSideButton {
+ if (offTheSideButton_.get()) {
+ return;
+ }
+ DCHECK(bookmarkModel_->loaded());
offTheSideButton_.reset(
- [[BookmarkButton alloc] initWithFrame:NSMakeRect(586, 0, 20, 24)]);
+ [[BookmarkButton alloc] initWithFrame:NSMakeRect(0, 0, 20, 24)]);
id offTheSideCell = [BookmarkButtonCell offTheSideButtonCell];
[offTheSideCell setTag:kMaterialStandardButtonTypeWithLimitedClickFeedback];
[offTheSideCell setImagePosition:NSImageOnly];
@@ -1409,6 +1229,8 @@ void RecordAppLaunch(Profile* profile, GURL url) {
[offTheSideCell setHighlightsBy:NSNoCellMask];
[offTheSideCell setShowsBorderOnlyWhileMouseInside:YES];
[offTheSideCell setBezelStyle:NSShadowlessSquareBezelStyle];
+ [offTheSideCell setBookmarkNode:bookmarkModel_->bookmark_bar_node()];
+
[offTheSideButton_ setCell:offTheSideCell];
[offTheSideButton_ setImage:[self offTheSideButtonImage:NO]];
[offTheSideButton_ setButtonType:NSMomentaryLightButton];
@@ -1418,6 +1240,22 @@ void RecordAppLaunch(Profile* profile, GURL url) {
[offTheSideButton_ setDelegate:self];
[[offTheSideButton_ draggableButton] setDraggable:NO];
[[offTheSideButton_ draggableButton] setActsOnMouseDown:YES];
+ [offTheSideButton_ setHidden:YES];
+ [buttonView_ addSubview:offTheSideButton_];
+}
+
+- (void)updateExtraButtonsVisibility {
+ [self rebuildLayoutWithAnimated:NO];
+}
+
+- (void)createExtraButtons {
+ DCHECK(!didCreateExtraButtons_);
+ [self createSupervisedBookmarksButton];
+ [self createManagedBookmarksButton];
+ [self createOtherBookmarksButton];
+ [self createAppsPageShortcutButton];
+ [self createOffTheSideButton];
+ didCreateExtraButtons_ = YES;
}
- (void)openAppsPage:(id)sender {
@@ -1434,6 +1272,11 @@ void RecordAppLaunch(Profile* profile, GURL url) {
[contextMenuController_ cancelTracking];
}
+// 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)moveToState:(BookmarkBar::State)nextState
withAnimation:(BOOL)animate {
BOOL isAnimationRunning = [self isAnimationRunning];
@@ -1500,7 +1343,8 @@ void RecordAppLaunch(Profile* profile, GURL url) {
[self moveToState:newState withAnimation:animate];
}
-// (Private)
+// Jump to final state (detached, attached, hidden, etc.) without animating,
+// stopping a running animation if necessary.
- (void)finalizeState {
// We promise that our delegate that the variables will be finalized before
// the call to |-bookmarkBar:didChangeFromState:toState:|.
@@ -1517,7 +1361,7 @@ void RecordAppLaunch(Profile* profile, GURL url) {
[self updateVisibility];
}
-// (Private)
+// Stops any current animation in its tracks (midway).
- (void)stopCurrentAnimation {
[[self controlledView] stopAnimation];
}
@@ -1528,36 +1372,6 @@ void RecordAppLaunch(Profile* profile, GURL url) {
[self finalizeState];
}
-- (void)reconfigureBookmarkBar {
- [self setManagedBookmarksButtonVisibility];
- [self setSupervisedBookmarksButtonVisibility];
- [self redistributeButtonsOnBarAsNeeded];
- [self positionRightSideButtons];
- [self configureOffTheSideButtonContentsAndVisibility];
- [self centerNoItemsLabel];
-}
-
-// Determine if the given |view| can completely fit within the constraint of
-// maximum x, given by |maxViewX|, and, if not, narrow the view up to a minimum
-// width. If the minimum width is not achievable then hide the view. Return YES
-// if the view was hidden.
-- (BOOL)shrinkOrHideView:(NSView*)view forMaxX:(CGFloat)maxViewX {
- BOOL wasHidden = NO;
- // See if the view needs to be narrowed.
- NSRect frame = [view frame];
- if (NSMaxX(frame) > maxViewX) {
- // Resize if more than 30 pixels are showing, otherwise hide.
- if (NSMinX(frame) + 30.0 < maxViewX) {
- frame.size.width = maxViewX - NSMinX(frame);
- [view setFrame:frame];
- } else {
- [view setHidden:YES];
- wasHidden = YES;
- }
- }
- return wasHidden;
-}
-
// Bookmark button menu items that open a new window (e.g., open in new window,
// open in incognito, edit, etc.) cause us to lose a mouse-exited event
// on the button, which leaves it in a hover state.
@@ -1583,173 +1397,9 @@ void RecordAppLaunch(Profile* profile, GURL url) {
}
}
-
-// Adjust the horizontal width, x position and the visibility of the "For quick
-// access" text field and "Import bookmarks..." button based on the current
-// width of the containing |buttonView_| (which is affected by window width).
-- (void)adjustNoItemContainerForMaxX:(CGFloat)maxViewX {
- if (![[buttonView_ noItemContainer] isHidden]) {
- // Reset initial frames for the two items, then adjust as necessary.
- NSTextField* noItemTextfield = [buttonView_ noItemTextfield];
- NSRect noItemsRect = originalNoItemsRect_;
- NSRect importBookmarksRect = originalImportBookmarksRect_;
- if (![appsPageShortcutButton_ isHidden]) {
- float width = NSWidth([appsPageShortcutButton_ frame]);
- noItemsRect.origin.x += width;
- importBookmarksRect.origin.x += width;
- }
- if (![managedBookmarksButton_ isHidden]) {
- float width = NSWidth([managedBookmarksButton_ frame]);
- noItemsRect.origin.x += width;
- importBookmarksRect.origin.x += width;
- }
- if (![supervisedBookmarksButton_ isHidden]) {
- float width = NSWidth([supervisedBookmarksButton_ frame]);
- noItemsRect.origin.x += width;
- importBookmarksRect.origin.x += width;
- }
- [noItemTextfield setFrame:noItemsRect];
- [noItemTextfield setHidden:NO];
- NSButton* importBookmarksButton = [buttonView_ importBookmarksButton];
- [importBookmarksButton setFrame:importBookmarksRect];
- [importBookmarksButton setHidden:NO];
- // Check each to see if they need to be shrunk or hidden.
- if ([self shrinkOrHideView:importBookmarksButton forMaxX:maxViewX])
- [self shrinkOrHideView:noItemTextfield forMaxX:maxViewX];
- }
-}
-
-// Scans through all buttons from left to right, calculating from scratch where
-// they should be based on the preceding widths, until it finds the one
-// requested.
-// Returns NSZeroRect if there is no such button in the bookmark bar.
-// Enables you to work out where a button will end up when it is done animating.
-- (NSRect)finalRectOfButton:(BookmarkButton*)wantedButton {
- CGFloat left = bookmarks::kBookmarkLeftMargin;
- NSRect buttonFrame = NSZeroRect;
-
- // Draw the apps bookmark if needed.
- if (![appsPageShortcutButton_ isHidden]) {
- left = NSMaxX([appsPageShortcutButton_ frame]) +
- bookmarks::kBookmarkHorizontalPadding;
- }
-
- // Draw the managed bookmarks folder if needed.
- if (![managedBookmarksButton_ isHidden]) {
- left = NSMaxX([managedBookmarksButton_ frame]) +
- bookmarks::kBookmarkHorizontalPadding;
- }
-
- // Draw the supervised bookmarks folder if needed.
- if (![supervisedBookmarksButton_ isHidden]) {
- left = NSMaxX([supervisedBookmarksButton_ frame]) +
- bookmarks::kBookmarkHorizontalPadding;
- }
-
- for (NSButton* button in buttons_.get()) {
- // Hidden buttons get no space.
- if ([button isHidden])
- continue;
- buttonFrame = [button frame];
- buttonFrame.origin.x = left;
- left += buttonFrame.size.width + bookmarks::kBookmarkHorizontalPadding;
- if (button == wantedButton)
- return buttonFrame;
- }
- return NSZeroRect;
-}
-
-// Calculates the final position of the last button in the bar.
-// We can't just use [[self buttons] lastObject] frame] because the button
-// may be animating currently.
-- (NSRect)finalRectOfLastButton {
- return [self finalRectOfButton:[[self buttons] lastObject]];
-}
-
-- (CGFloat)buttonViewMaxXWithOffTheSideButtonIsVisible:(BOOL)visible {
- CGFloat maxViewX = NSMaxX([buttonView_ bounds]);
- // If necessary, pull in the width to account for the Other Bookmarks button.
- if ([self setOtherBookmarksButtonVisibility]) {
- maxViewX = [otherBookmarksButton_ frame].origin.x -
- bookmarks::kBookmarkRightMargin;
- }
-
- [self positionRightSideButtons];
- // If we're already overflowing, then we need to account for the chevron.
- if (visible) {
- maxViewX =
- [offTheSideButton_ frame].origin.x - bookmarks::kBookmarkRightMargin;
- }
-
- return maxViewX;
-}
-
-- (void)redistributeButtonsOnBarAsNeeded {
- const BookmarkNode* node = bookmarkModel_->bookmark_bar_node();
- NSInteger barCount = node->child_count();
-
- // Determine the current maximum extent of the visible buttons.
- [self positionRightSideButtons];
- BOOL offTheSideButtonVisible = (barCount > displayedButtonCount_);
- CGFloat maxViewX = [self buttonViewMaxXWithOffTheSideButtonIsVisible:
- offTheSideButtonVisible];
-
- // As a result of pasting or dragging, the bar may now have more buttons
- // than will fit so remove any which overflow. They will be shown in
- // the off-the-side folder.
- while (displayedButtonCount_ > 0) {
- BookmarkButton* button = [buttons_ lastObject];
- if (NSMaxX([self finalRectOfLastButton]) < maxViewX)
- break;
- [buttons_ removeLastObject];
- [button setDelegate:nil];
- [button removeFromSuperview];
- --displayedButtonCount_;
- // Account for the fact that the chevron might now be visible.
- if (!offTheSideButtonVisible) {
- offTheSideButtonVisible = YES;
- maxViewX = [self buttonViewMaxXWithOffTheSideButtonIsVisible:YES];
- }
- }
-
- // As a result of cutting, deleting and dragging, the bar may now have room
- // for more buttons.
- int xOffset;
- if (displayedButtonCount_ > 0) {
- xOffset = NSMaxX([self finalRectOfLastButton]);
- } else if (![managedBookmarksButton_ isHidden]) {
- xOffset = NSMaxX([managedBookmarksButton_ frame]) +
- bookmarks::kBookmarkHorizontalPadding;
- } else if (![supervisedBookmarksButton_ isHidden]) {
- xOffset = NSMaxX([supervisedBookmarksButton_ frame]) +
- bookmarks::kBookmarkHorizontalPadding;
- } else if (![appsPageShortcutButton_ isHidden]) {
- xOffset = NSMaxX([appsPageShortcutButton_ frame]) +
- bookmarks::kBookmarkHorizontalPadding;
- } else {
- xOffset = bookmarks::kBookmarkLeftMargin -
- bookmarks::kBookmarkHorizontalPadding;
- }
- for (int i = displayedButtonCount_; i < barCount; ++i) {
- const BookmarkNode* child = node->GetChild(i);
- BookmarkButton* button = [self buttonForNode:child xOffset:&xOffset];
- // If we're testing against the last possible button then account
- // for the chevron no longer needing to be shown.
- if (i == barCount - 1)
- maxViewX = [self buttonViewMaxXWithOffTheSideButtonIsVisible:NO];
- if (NSMaxX([button frame]) > maxViewX) {
- [button setDelegate:nil];
- break;
- }
- ++displayedButtonCount_;
- [buttons_ addObject:button];
- [buttonView_ addSubview:button];
- }
-
- // While we're here, adjust the horizontal width and the visibility
- // of the "For quick access" and "Import bookmarks..." text fields.
- if (![buttons_ count])
- [self adjustNoItemContainerForMaxX:maxViewX];
+- (void)addButtonForNode:(const bookmarks::BookmarkNode*)node
+ atIndex:(NSInteger)buttonIndex {
+ [self rebuildLayoutWithAnimated:NO];
}
#pragma mark Private Methods Exposed for Testing
@@ -1762,16 +1412,16 @@ void RecordAppLaunch(Profile* profile, GURL url) {
return buttons_.get();
}
-- (BOOL)offTheSideButtonIsHidden {
- return [offTheSideButton_ isHidden];
+- (BookmarkButton*)otherBookmarksButton {
+ return otherBookmarksButton_.get();
}
-- (BOOL)appsPageShortcutButtonIsHidden {
- return [appsPageShortcutButton_ isHidden];
+- (BookmarkButton*)managedBookmarksButton {
+ return managedBookmarksButton_.get();
}
-- (BookmarkButton*)otherBookmarksButton {
- return otherBookmarksButton_.get();
+- (BookmarkButton*)supervisedBookmarksButton {
+ return supervisedBookmarksButton_.get();
}
- (BookmarkBarFolderController*)folderController {
@@ -1782,29 +1432,6 @@ void RecordAppLaunch(Profile* profile, GURL url) {
return folderTarget_.get();
}
-- (int)displayedButtonCount {
- return displayedButtonCount_;
-}
-
-// Delete all buttons (bookmarks, chevron, "other bookmarks") from the
-// bookmark bar; reset knowledge of bookmarks.
-- (void)clearBookmarkBar {
- [self stopPulsingBookmarkNode];
- for (BookmarkButton* button in buttons_.get()) {
- [button setDelegate:nil];
- [button removeFromSuperview];
- }
- [buttons_ removeAllObjects];
- displayedButtonCount_ = 0;
-
- // Make sure there are no stale pointers in the pasteboard. This
- // can be important if a bookmark is deleted (via bookmark sync)
- // while in the middle of a drag. The "drag completed" code
- // (e.g. [BookmarkBarView performDragOperationForBookmarkButton:]) is
- // careful enough to bail if there is no data found at "drop" time.
- [[NSPasteboard pasteboardWithName:NSDragPboard] clearContents];
-}
-
// Return an autoreleased NSCell suitable for a bookmark button.
// TODO(jrg): move much of the cell config into the BookmarkButtonCell class.
- (BookmarkButtonCell*)cellForBookmarkNode:(const BookmarkNode*)node {
@@ -1842,59 +1469,9 @@ void RecordAppLaunch(Profile* profile, GURL url) {
return cell;
}
-// Returns a frame appropriate for the given bookmark cell, suitable
-// for creating an NSButton that will contain it. |xOffset| is the X
-// offset for the frame; it is increased to be an appropriate X offset
-// for the next button.
-- (NSRect)frameForBookmarkButtonFromCell:(NSCell*)cell
- xOffset:(int*)xOffset {
- DCHECK(xOffset);
- NSRect bounds = [buttonView_ bounds];
- bounds.size.height = bookmarks::kBookmarkButtonHeight;
-
- NSRect frame = NSInsetRect(bounds,
- bookmarks::kBookmarkHorizontalPadding,
- bookmarks::kBookmarkVerticalPadding);
- frame.size.width = [self widthForBookmarkButtonCell:cell];
-
- // Add an X offset based on what we've already done
- frame.origin.x += *xOffset;
-
- // And up the X offset for next time.
- *xOffset = NSMaxX(frame);
-
- return frame;
-}
-
-// A bookmark button's contents changed. Check for growth
-// (e.g. increase the width up to the maximum). If we grew, move
-// other bookmark buttons over.
-- (void)checkForBookmarkButtonGrowth:(NSButton*)changedButton {
- NSRect frame = [changedButton frame];
- CGFloat desiredSize = [self widthForBookmarkButtonCell:[changedButton cell]];
- CGFloat delta = desiredSize - frame.size.width;
- if (delta) {
- frame.size.width = desiredSize;
- [changedButton setFrame:frame];
- for (NSButton* button in buttons_.get()) {
- NSRect buttonFrame = [button frame];
- if (buttonFrame.origin.x > frame.origin.x) {
- buttonFrame.origin.x += delta;
- [button setFrame:buttonFrame];
- }
- }
- }
- // We may have just crossed a threshold to enable the off-the-side
- // button.
- [self configureOffTheSideButtonContentsAndVisibility];
-}
-
// Called when our controlled frame has changed size.
- (void)frameDidChange {
- if (!bookmarkModel_->loaded())
- return;
- [self updateTheme:[[[self view] window] themeProvider]];
- [self reconfigureBookmarkBar];
+ [self rebuildLayoutWithAnimated:NO];
}
// Adapt appearance of buttons to the current theme. Called after
@@ -1989,11 +1566,14 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// given array, or nil if none. We use this for distinguishing
// between a hover-open candidate or drop-indicator draw.
// Helper for buttonForDroppingOnAtPoint:.
-// Get UI review on "middle half" ness.
-// http://crbug.com/36276
- (BookmarkButton*)buttonForDroppingOnAtPoint:(NSPoint)point
fromArray:(NSArray*)array {
- for (BookmarkButton* button in array) {
+ // Ensure buttons are scanned left to right.
+ id<NSFastEnumeration> buttonsToCheck =
Avi (use Gerrit) 2017/04/27 18:00:29 NSEnumerator*
+ cocoa_l10n_util::ShouldDoExperimentalRTLLayout()
+ ? [array reverseObjectEnumerator]
+ : [array objectEnumerator];
+ for (BookmarkButton* button in buttonsToCheck) {
// Hidden buttons can overlap valid visible buttons, just ignore.
if ([button isHidden])
continue;
@@ -2041,7 +1621,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// the button's folder being closed if the mouse temporarily leaves the
// middle half but is still within the button bounds.
if (hoverButton_ && NSPointInRect(point, [hoverButton_ frame]))
- return hoverButton_.get();
+ return hoverButton_.get();
BookmarkButton* button = [self buttonForDroppingOnAtPoint:point
fromArray:buttons_.get()];
@@ -2049,7 +1629,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// This is different than BookmarkBarFolderController.
if (!button) {
NSMutableArray* array = [NSMutableArray array];
- if (![self offTheSideButtonIsHidden])
+ if (layout_.IsOffTheSideButtonVisible())
[array addObject:offTheSideButton_];
[array addObject:otherBookmarksButton_];
button = [self buttonForDroppingOnAtPoint:point
@@ -2058,33 +1638,38 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
return button;
}
+// Returns the index in the model for a drag to the location given by
+// |point|. This is determined by finding the first button before the center
+// of which |point| falls, scanning front to back. Note that, currently, only
+// the x-coordinate of |point| is considered. Though not currently implemented,
+// we may check for errors, in which case this would return negative value;
+// callers should check for this.
- (int)indexForDragToPoint:(NSPoint)point {
- // TODO(jrg): revisit position info based on UI team feedback.
- // dropLocation is in bar local coordinates.
NSPoint dropLocation =
[[self view] convertPoint:point
fromView:[[[self view] window] contentView]];
- BookmarkButton* buttonToTheRightOfDraggedButton = nil;
- for (BookmarkButton* button in buttons_.get()) {
+
+ BOOL isRTL = cocoa_l10n_util::ShouldDoExperimentalRTLLayout();
+ for (size_t i = 0; i < layout_.VisibleButtonCount(); i++) {
+ BookmarkButton* button = [buttons_ objectAtIndex:i];
CGFloat midpoint = NSMidX([button frame]);
- if (dropLocation.x <= midpoint) {
- buttonToTheRightOfDraggedButton = button;
- break;
+ if (isRTL ? dropLocation.x >= midpoint : dropLocation.x <= midpoint) {
+ return i;
}
}
- if (buttonToTheRightOfDraggedButton) {
- const BookmarkNode* afterNode =
- [buttonToTheRightOfDraggedButton bookmarkNode];
- DCHECK(afterNode);
- int index = afterNode->parent()->GetIndexOf(afterNode);
- // Make sure we don't get confused by buttons which aren't visible.
- return std::min(index, displayedButtonCount_);
- }
-
- // If nothing is to my right I am at the end!
- return displayedButtonCount_;
+ // No buttons left to insert the dragged button before, so it
+ // goes at the end.
+ return layout_.VisibleButtonCount();
}
+// Informs the bar that something representing the bookmark at
+// |sourceNode| was dragged into the bar.
+// |point| is in the base coordinate system of the destination window;
+// it comes from an id<NSDraggingInfo>. |copy| is YES if a copy is to be
+// made and inserted into the new location while leaving the bookmark in
+// the old location, otherwise move the bookmark by removing from its old
+// location and inserting into the new location.
+// Returns whether the drag was allowed.
// TODO(mrossetti,jrg): Yet more duplicated code.
// http://crbug.com/35966
- (BOOL)dragBookmark:(const BookmarkNode*)sourceNode
@@ -2131,155 +1716,393 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
- (void)draggingEnded:(id<NSDraggingInfo>)info {
[self closeFolderAndStopTrackingMenus];
- [[BookmarkButton draggedButton] setHidden:NO];
- [self resetAllButtonPositionsWithAnimation:YES];
+ layout_ = {}; // Force layout.
+ [self rebuildLayoutWithAnimated:YES];
}
// Set insertionPos_ and hasInsertionPos_, and make insertion space for a
// hypothetical drop with the new button having a left edge of |where|.
-// Gets called only by our view.
- (void)setDropInsertionPos:(CGFloat)where {
- if (!hasInsertionPos_ || where != insertionPos_) {
- insertionPos_ = where;
- hasInsertionPos_ = YES;
- CGFloat left;
- if (![supervisedBookmarksButton_ isHidden]) {
- left = NSMaxX([supervisedBookmarksButton_ frame]) +
- bookmarks::kBookmarkHorizontalPadding;
- } else if (![managedBookmarksButton_ isHidden]) {
- left = NSMaxX([managedBookmarksButton_ frame]) +
- bookmarks::kBookmarkHorizontalPadding;
- } else if (![appsPageShortcutButton_ isHidden]) {
- left = NSMaxX([appsPageShortcutButton_ frame]) +
- bookmarks::kBookmarkHorizontalPadding;
+ if (hasInsertionPos_ && where == insertionPos_) {
+ return;
+ }
+ insertionPos_ = where;
+ hasInsertionPos_ = YES;
+ CGFloat paddingWidth = bookmarks::kDefaultBookmarkWidth;
+ BookmarkButton* draggedButton = [BookmarkButton draggedButton];
+ BOOL draggedButtonIsOnBar = NO;
+ int64_t draggedButtonNodeId;
+ CGFloat draggedButtonOffset;
+ if (draggedButton) {
+ paddingWidth = std::min(bookmarks::kDefaultBookmarkWidth,
+ NSWidth([draggedButton frame]));
+ draggedButtonNodeId = [draggedButton bookmarkNode]->id();
+ auto offsetIt = layout_.button_offsets.find(draggedButtonNodeId);
+ if (offsetIt != layout_.button_offsets.end()) {
+ draggedButtonIsOnBar = YES;
+ draggedButtonOffset = (*offsetIt).second;
+ }
+ }
+ paddingWidth += bookmarks::kBookmarkHorizontalPadding;
+ BOOL isRTL = cocoa_l10n_util::ShouldDoExperimentalRTLLayout();
+
+ // If the button being dragged is not currently on the bar
+ // (for example, a drag from the URL bar, a link on the desktop,
+ // a button inside a menu, etc.), buttons in front of the drag position
+ // (to the right in LTR, to the left in RTL), should make room for it.
+ // Otherwise:
+ // If a given button was to the left of the dragged button's original
+ // position, but is to the right of the drag position, or
+ // if it was to the right of the dragged button's original position and
+ // is to the left of the drag position, make room.
+ // TODO(lgrey): Extract ScopedNSAnimationContextGroup
+ // from the tab strip controller and use it on all of these.
+ [NSAnimationContext beginGrouping];
+ [[NSAnimationContext currentContext]
+ setDuration:kDragAndDropAnimationDuration];
+
+ for (BookmarkButton* button in buttons_.get()) {
+ CGRect buttonFrame = [button frame];
+ int64_t nodeId = [button bookmarkNode]->id();
+ CGFloat offset = layout_.button_offsets[nodeId];
+ CGFloat buttonEdge = isRTL ? NSWidth([buttonView_ frame]) - offset : offset;
+
+ if (draggedButtonIsOnBar) {
+ if (nodeId == draggedButtonNodeId)
+ continue;
+ BOOL wasBefore = offset < draggedButtonOffset;
+ BOOL isBefore = isRTL ? buttonEdge > where : buttonEdge < where;
+ if (isBefore && !wasBefore) {
+ offset -= paddingWidth;
+ } else if (!isBefore && wasBefore) {
+ offset += paddingWidth;
+ }
} else {
- left = bookmarks::kBookmarkLeftMargin;
+ if (isRTL ? (where > buttonEdge)
+ : (where < offset + NSWidth(buttonFrame))) {
+ offset += paddingWidth;
+ }
}
- CGFloat paddingWidth = bookmarks::kDefaultBookmarkWidth;
- BookmarkButton* draggedButton = [BookmarkButton draggedButton];
- if (draggedButton) {
- paddingWidth = std::min(bookmarks::kDefaultBookmarkWidth,
- NSWidth([draggedButton frame]));
+
+ [self applyXOffset:offset
+ toButton:button
+ animated:innerContentAnimationsEnabled_];
+ }
+
+ [NSAnimationContext endGrouping];
+}
+
+- (BookmarkBarLayout)layoutFromCurrentState {
+ BookmarkBarLayout layout = {};
+
+ const BookmarkNode* node = bookmarkModel_->bookmark_bar_node();
+ CGFloat viewWidth = NSWidth([buttonView_ frame]);
+ CGFloat xOffset = bookmarks::kBookmarkLeftMargin;
+ CGFloat maxX = viewWidth - bookmarks::kBookmarkHorizontalPadding;
+
+ layout.visible_elements = bookmarks::kVisibleElementsMaskNone;
+ layout.max_x = maxX;
+
+ // Lay out "extra" buttons (apps, managed, supervised, "Other").
+ if (chrome::ShouldShowAppsShortcutInBookmarkBar(browser_->profile())) {
+ layout.visible_elements |= bookmarks::kVisibleElementsMaskAppsButton;
+ layout.apps_button_offset = xOffset;
+ xOffset += NSWidth([appsPageShortcutButton_ frame]) +
+ bookmarks::kBookmarkHorizontalPadding;
+ }
+ if (!managedBookmarkService_->managed_node()->empty()) {
+ layout.visible_elements |=
+ bookmarks::kVisibleElementsMaskManagedBookmarksButton;
+ layout.managed_bookmarks_button_offset = xOffset;
+ xOffset += NSWidth([managedBookmarksButton_ frame]) +
+ bookmarks::kBookmarkHorizontalPadding;
+ }
+ if (!managedBookmarkService_->supervised_node()->empty()) {
+ layout.visible_elements |=
+ bookmarks::kVisibleElementsMaskSupervisedBookmarksButton;
+ layout.supervised_bookmarks_button_offset = xOffset;
+ xOffset += NSWidth([supervisedBookmarksButton_ frame]) +
+ bookmarks::kBookmarkHorizontalPadding;
+ }
+ if (!bookmarkModel_->other_node()->empty()) {
+ layout.visible_elements |=
+ bookmarks::kVisibleElementsMaskOtherBookmarksButton;
+ maxX -= NSWidth([otherBookmarksButton_ frame]);
+ layout.other_bookmarks_button_offset = maxX;
+ }
+
+ // Lay out empty state ("no items" label, import bookmarks button.)
+ if (node->empty()) {
+ CGFloat roomForTextField =
+ maxX - xOffset + bookmarks::kInitialNoItemTextFieldXOrigin;
+ if (roomForTextField >= kNoItemElementMinWidth) {
+ layout.visible_elements |= bookmarks::kVisibleElementsMaskNoItemTextField;
+ xOffset += bookmarks::kInitialNoItemTextFieldXOrigin;
+ layout.no_item_textfield_offset = xOffset;
+ layout.no_item_textfield_width =
+ std::min(maxX - xOffset, originalNoItemTextFieldWidth_);
+ xOffset +=
+ layout.no_item_textfield_width + originalNoItemInterelementPadding_;
+
+ // Does the "import bookmarks" button fit?
+ if (maxX - xOffset >= kNoItemElementMinWidth) {
+ layout.visible_elements |=
+ bookmarks::kVisibleElementsMaskImportBookmarksButton;
+ layout.import_bookmarks_button_offset = xOffset;
+ layout.import_bookmarks_button_width =
+ std::min(maxX - xOffset, originalImportBookmarksButtonWidth_);
+ }
}
- // Put all the buttons where they belong, with all buttons to the right
- // of the insertion point shuffling right to make space for it.
- [NSAnimationContext beginGrouping];
- [[NSAnimationContext currentContext]
- setDuration:kDragAndDropAnimationDuration];
- for (NSButton* button in buttons_.get()) {
- // Hidden buttons get no space.
- if ([button isHidden])
- continue;
- NSRect buttonFrame = [button frame];
- buttonFrame.origin.x = left;
- // Update "left" for next time around.
- left += buttonFrame.size.width;
- if (left > insertionPos_)
- buttonFrame.origin.x += paddingWidth;
- left += bookmarks::kBookmarkHorizontalPadding;
- if (innerContentAnimationsEnabled_)
- [[button animator] setFrame:buttonFrame];
- else
- [button setFrame:buttonFrame];
+ } else {
+ // Lay out bookmark buttons and "off-the-side" chevron.
+ CGFloat offTheSideButtonWidth = NSWidth([offTheSideButton_ frame]);
+ int buttonCount = node->child_count();
+ for (int i = 0; i < buttonCount; i++) {
+ const BookmarkNode* buttonNode = node->GetChild(i);
+ CGFloat widthOfButton = [self widthOfButtonForNode:buttonNode];
+ // If it's the last button, we just need to ensure that it can fit.
+ // If not, we need to be able to fit both it and the chevron.
+ CGFloat widthToCheck = i == buttonCount - 1
+ ? widthOfButton
+ : widthOfButton + offTheSideButtonWidth;
+ if (xOffset + widthToCheck > maxX) {
+ layout.visible_elements |=
+ bookmarks::kVisibleElementsMaskOffTheSideButton;
+ layout.off_the_side_button_offset = maxX - offTheSideButtonWidth;
+ break;
+ }
+ layout.button_offsets.insert({buttonNode->id(), xOffset});
+ xOffset += widthOfButton + bookmarks::kBookmarkHorizontalPadding;
}
- [NSAnimationContext endGrouping];
}
+ return layout;
}
-// Put all visible bookmark bar buttons in their normal locations, either with
-// or without animation according to the |animate| flag.
-// This is generally useful, so is called from various places internally.
-- (void)resetAllButtonPositionsWithAnimation:(BOOL)animate {
+- (void)rebuildLayoutWithAnimated:(BOOL)animated {
+ if (!bookmarkModel_->loaded())
+ return;
+
+ BookmarkBarLayout layout = [self layoutFromCurrentState];
+ if (layout_ != layout) {
+ layout_ = std::move(layout);
+ [self applyLayout:layout_ animated:animated];
+ }
+}
+
+- (void)applyLayout:(const BookmarkBarLayout&)layout animated:(BOOL)animated {
+ if (!bookmarkModel_->loaded())
+ return;
+
+ if (folderController_)
+ [self closeAllBookmarkFolders];
+ [self stopPulsingBookmarkNode];
+
+ // Hide or show "extra" buttons and position if necessary.
+ [self applyXOffset:layout.apps_button_offset
+ toButton:appsPageShortcutButton_
+ animated:NO];
+ [appsPageShortcutButton_ setHidden:!layout.IsAppsButtonVisible()];
+
+ [self applyXOffset:layout.managed_bookmarks_button_offset
+ toButton:managedBookmarksButton_
+ animated:NO];
+ [managedBookmarksButton_ setHidden:!layout.IsManagedBookmarksButtonVisible()];
- // Position the apps bookmark if needed.
- CGFloat left = bookmarks::kBookmarkLeftMargin;
- if (![appsPageShortcutButton_ isHidden]) {
- int xOffset = bookmarks::kBookmarkLeftMargin -
- bookmarks::kBookmarkHorizontalPadding;
- NSRect frame =
- [self frameForBookmarkButtonFromCell:[appsPageShortcutButton_ cell]
- xOffset:&xOffset];
- [appsPageShortcutButton_ setFrame:frame];
- left = xOffset + bookmarks::kBookmarkHorizontalPadding;
+ [self applyXOffset:layout.supervised_bookmarks_button_offset
+ toButton:supervisedBookmarksButton_
+ animated:NO];
+ [supervisedBookmarksButton_
+ setHidden:!layout.IsSupervisedBookmarksButtonVisible()];
+
+ [self applyXOffset:layout.other_bookmarks_button_offset
+ toButton:otherBookmarksButton_
+ animated:NO];
+ [otherBookmarksButton_ setHidden:!layout.IsOtherBookmarksButtonVisible()];
+
+ [self applyXOffset:layout.off_the_side_button_offset
+ toButton:offTheSideButton_
+ animated:NO];
+ [offTheSideButton_ setHidden:!layout.IsOffTheSideButtonVisible()];
+ if (layout.IsOffTheSideButtonVisible()) {
+ [[offTheSideButton_ cell]
+ setStartingChildIndex:layout_.VisibleButtonCount()];
+ }
+
+ // Hide or show empty state and position if necessary.
+ [[buttonView_ noItemTextField] setHidden:!layout.IsNoItemTextFieldVisible()];
+ [[buttonView_ importBookmarksButton]
+ setHidden:!layout.IsImportBookmarksButtonVisible()];
+
+ if (layout.IsNoItemTextFieldVisible()) {
+ NSTextField* noItemTextField = [buttonView_ noItemTextField];
+ [noItemTextField setHidden:NO];
+ NSRect textFieldFrame = [noItemTextField frame];
+ textFieldFrame.size.width = layout.no_item_textfield_width;
+ if (cocoa_l10n_util::ShouldDoExperimentalRTLLayout()) {
+ textFieldFrame.origin.x = NSWidth([buttonView_ frame]) -
+ layout.no_item_textfield_offset -
+ layout.no_item_textfield_width;
+ } else {
+ textFieldFrame.origin.x = layout.no_item_textfield_offset;
+ }
+ [noItemTextField setFrame:textFieldFrame];
+
+ if (layout.IsImportBookmarksButtonVisible()) {
+ NSButton* importBookmarksButton = [buttonView_ importBookmarksButton];
+ NSRect buttonFrame = [importBookmarksButton frame];
+ buttonFrame.size.width = layout.import_bookmarks_button_width;
+ if (cocoa_l10n_util::ShouldDoExperimentalRTLLayout()) {
+ buttonFrame.origin.x = NSWidth([buttonView_ frame]) -
+ layout.import_bookmarks_button_offset -
+ layout.import_bookmarks_button_width;
+ } else {
+ buttonFrame.origin.x = layout.import_bookmarks_button_offset;
+ }
+ [importBookmarksButton setFrame:buttonFrame];
+ }
+ }
+ // 1) Hide all buttons initially.
+ // 2) Show all buttons in the layout.
+ // 3) Remove any buttons that are still hidden (so: not in the layout)
+ // from the node ID -> button map and add them to the reuse pool if
+ // there's room.
+ for (BookmarkButton* button in buttons_.get()) {
+ [button setHidden:YES];
}
+ [buttons_ removeAllObjects];
- // Position the managed bookmarks folder if needed.
- if (![managedBookmarksButton_ isHidden]) {
- int xOffset = left;
- NSRect frame =
- [self frameForBookmarkButtonFromCell:[managedBookmarksButton_ cell]
- xOffset:&xOffset];
- [managedBookmarksButton_ setFrame:frame];
- left = xOffset + bookmarks::kBookmarkHorizontalPadding;
+ const BookmarkNode* parentNode = bookmarkModel_->bookmark_bar_node();
+ for (size_t i = 0; i < layout.VisibleButtonCount(); i++) {
+ const BookmarkNode* node = parentNode->GetChild(i);
+ DCHECK(node);
+ BookmarkButton* button = [self buttonForNode:node];
+ CGFloat offset = layout.button_offsets.at(node->id());
+ [self applyXOffset:offset
+ toButton:button
+ animated:animated && innerContentAnimationsEnabled_];
+ [buttons_ addObject:button];
+ [button setHidden:NO];
}
- // Position the supervised bookmarks folder if needed.
- if (![supervisedBookmarksButton_ isHidden]) {
- int xOffset = left;
- NSRect frame =
- [self frameForBookmarkButtonFromCell:[supervisedBookmarksButton_ cell]
- xOffset:&xOffset];
- [supervisedBookmarksButton_ setFrame:frame];
- left = xOffset + bookmarks::kBookmarkHorizontalPadding;
+ for (auto& item : nodeIdToButtonMap_) {
+ if ([item.second isHidden]) {
+ if ([unusedButtonPool_ count] < kMaxReusePoolSize) {
+ BookmarkButton* button = item.second.get();
+ // Dragged buttons unhide themselves, so position it off-screen
+ // while it's in the reuse pool.
+ CGRect buttonFrame = [button frame];
+ buttonFrame.origin.x = -10000;
+ [button setFrame:buttonFrame];
+ [unusedButtonPool_ addObject:button];
+ } else {
+ [item.second setDelegate:nil];
+ [item.second removeFromSuperview];
+ }
+
+ nodeIdToButtonMap_.erase(item.first);
+ }
}
+ const ui::ThemeProvider* themeProvider = [[[self view] window] themeProvider];
+ [self updateTheme:themeProvider];
+}
- animate &= innerContentAnimationsEnabled_;
+- (void)applyXOffset:(CGFloat)offset
+ toButton:(BookmarkButton*)button
+ animated:(BOOL)animated {
+ CGRect frame = [button frame];
+ BOOL wasOffscreen = frame.origin.x < -200;
+ if (cocoa_l10n_util::ShouldDoExperimentalRTLLayout()) {
+ frame.origin.x = NSWidth([buttonView_ frame]) - offset - NSWidth(frame);
+ } else {
+ frame.origin.x = offset;
+ }
- for (NSButton* button in buttons_.get()) {
- // Hidden buttons get no space.
- if ([button isHidden])
- continue;
- NSRect buttonFrame = [button frame];
- buttonFrame.origin.x = left;
- left += buttonFrame.size.width + bookmarks::kBookmarkHorizontalPadding;
- if (animate)
- [[button animator] setFrame:buttonFrame];
- else
- [button setFrame:buttonFrame];
+ // Buttons fresh from the reuse pool are kept offscreen. Animation should
+ // be disabled for them so they don't slide in.
+ if (animated && !wasOffscreen) {
+ [[button animator] setFrame:frame];
+ } else {
+ [button setFrame:frame];
}
}
+- (CGFloat)widthOfButtonForNode:(const BookmarkNode*)node {
+ // TODO(lgrey): Can we get this information without an actual image?
+ NSImage* image = [self faviconForNode:node forADarkTheme:NO];
+ CGFloat width = [BookmarkButtonCell cellWidthForNode:node image:image];
+ return std::min(width, bookmarks::kDefaultBookmarkWidth);
+}
+
// Clear insertion flag, remove insertion space and put all visible bookmark
// bar buttons in their normal locations.
// Gets called only by our view.
+// TODO(lgrey): Is there a sane way to dedupe this with |setDropInsertionPos:|?
- (void)clearDropInsertionPos {
- if (hasInsertionPos_) {
- hasInsertionPos_ = NO;
- [self resetAllButtonPositionsWithAnimation:YES];
+ if (!hasInsertionPos_) {
+ return;
+ }
+ hasInsertionPos_ = NO;
+ BookmarkButton* draggedButton = [BookmarkButton draggedButton];
+ if (!draggedButton) {
+ [self applyLayout:layout_ animated:YES];
+ return;
+ }
+ int64_t draggedButtonNodeId = [draggedButton bookmarkNode]->id();
+ if (layout_.button_offsets.find(draggedButtonNodeId) ==
+ layout_.button_offsets.end()) {
+ [self applyLayout:layout_ animated:YES];
+ return;
}
+ // The dragged button came from the bar, but is being dragged outside
+ // of it now, so the rest of the buttons should be laid out as if it
+ // were removed.
+ CGFloat draggedButtonOffset = layout_.button_offsets[draggedButtonNodeId];
+ CGFloat spaceForDraggedButton =
+ NSWidth([draggedButton frame]) + bookmarks::kBookmarkHorizontalPadding;
+ [NSAnimationContext beginGrouping];
+ [[NSAnimationContext currentContext]
+ setDuration:kDragAndDropAnimationDuration];
+
+ for (BookmarkButton* button in buttons_.get()) {
+ int64_t nodeId = [button bookmarkNode]->id();
+ CGFloat offset = layout_.button_offsets[nodeId];
+
+ if (nodeId == draggedButtonNodeId)
+ continue;
+
+ if (offset > draggedButtonOffset) {
+ offset -= spaceForDraggedButton;
+ [self applyXOffset:offset
+ toButton:button
+ animated:innerContentAnimationsEnabled_];
+ }
+ }
+
+ [NSAnimationContext endGrouping];
}
#pragma mark Bridge Notification Handlers
-// TODO(jrg): for now this is brute force.
- (void)loaded:(BookmarkModel*)model {
DCHECK(model == bookmarkModel_);
if (!model->loaded())
return;
+ // Make sure there are no stale pointers in the pasteboard. This
+ // can be important if a bookmark is deleted (via bookmark sync)
+ // while in the middle of a drag. The "drag completed" code
+ // (e.g. [BookmarkBarView performDragOperationForBookmarkButton:]) is
+ // careful enough to bail if there is no data found at "drop" time.
+ [[NSPasteboard pasteboardWithName:NSDragPboard] clearContents];
+
+ if (!didCreateExtraButtons_) {
+ [self createExtraButtons];
+ }
// If this is a rebuild request while we have a folder open, close it.
- // TODO(mrossetti): Eliminate the need for this because it causes the folder
- // menu to disappear after a cut/copy/paste/delete change.
- // See: http://crbug.com/36614
if (folderController_)
[self closeAllBookmarkFolders];
-
- // Brute force nuke and build.
- savedFrameWidth_ = NSWidth([[self view] frame]);
- const BookmarkNode* node = model->bookmark_bar_node();
- [self clearBookmarkBar];
- [self createAppsPageShortcutButton];
- [self createManagedBookmarksButton];
- [self createSupervisedBookmarksButton];
- [self addNodesToButtonList:node];
- [self createOtherBookmarksButton];
- [self updateTheme:[[[self view] window] themeProvider]];
- [self positionRightSideButtons];
- [self addButtonsToView];
- [self configureOffTheSideButtonContentsAndVisibility];
- [self reconfigureBookmarkBar];
+ [self rebuildLayoutWithAnimated:NO];
}
-
- (void)nodeAdded:(BookmarkModel*)model
parent:(const BookmarkNode*)newParent index:(int)newIndex {
// If a context menu is open, close it.
@@ -2289,11 +2112,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
id<BookmarkButtonControllerProtocol> newController =
[self controllerForNode:newParent];
[newController addButtonForNode:newNode atIndex:newIndex];
- // If we go from 0 --> 1 bookmarks we may need to hide the
- // "bookmarks go here" text container.
- [self showOrHideNoItemContainerForNode:model->bookmark_bar_node()];
- // Cope with chevron or "Other Bookmarks" buttons possibly changing state.
- [self reconfigureBookmarkBar];
+ [self rebuildLayoutWithAnimated:NO];
}
// TODO(jrg): for now this is brute force.
@@ -2316,11 +2135,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
[oldController removeButton:oldIndex animate:NO];
[newController addButtonForNode:movedNode atIndex:newIndex];
}
- // If the bar is one of the parents we may need to update the visibility
- // of the "bookmarks go here" presentation.
- [self showOrHideNoItemContainerForNode:model->bookmark_bar_node()];
- // Cope with chevron or "Other Bookmarks" buttons possibly changing state.
- [self reconfigureBookmarkBar];
+ [self rebuildLayoutWithAnimated:NO];
}
- (void)nodeRemoved:(BookmarkModel*)model
@@ -2333,17 +2148,9 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
id<BookmarkButtonControllerProtocol> parentController =
[self controllerForNode:oldParent];
[parentController removeButton:index animate:YES];
- // If we go from 1 --> 0 bookmarks we may need to show the
- // "bookmarks go here" text container.
- [self showOrHideNoItemContainerForNode:model->bookmark_bar_node()];
- // If we deleted the only item on the "off the side" menu we no
- // longer need to show it.
- [self reconfigureBookmarkBar];
+ [self rebuildLayoutWithAnimated:NO];
}
-// TODO(jrg): linear searching is bad.
-// Need a BookmarkNode-->NSCell mapping.
-//
// TODO(jrg): if the bookmark bar is open on launch, we see the
// buttons all placed, then "scooted over" as the favicons load. If
// this looks bad I may need to change widthForBookmarkButtonCell to
@@ -2351,21 +2158,14 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// favicons will eventually load.
- (void)nodeFaviconLoaded:(BookmarkModel*)model
node:(const BookmarkNode*)node {
- for (BookmarkButton* button in buttons_.get()) {
- const BookmarkNode* cellnode = [button bookmarkNode];
- if (cellnode == node) {
- BOOL darkTheme = [[[self view] window] hasDarkTheme];
- NSImage* theImage = [self faviconForNode:node forADarkTheme:darkTheme];
- [[button cell] setBookmarkCellText:[button title]
- image:theImage];
- // Adding an image means we might need more room for the
- // bookmark. Test for it by growing the button (if needed)
- // and shifting everything else over.
- [self checkForBookmarkButtonGrowth:button];
- return;
- }
- }
-
+ auto buttonIt = nodeIdToButtonMap_.find(node->id());
+ if (buttonIt != nodeIdToButtonMap_.end()) {
+ BookmarkButton* button = (*buttonIt).second;
+ BOOL darkTheme = [[[self view] window] hasDarkTheme];
+ NSImage* theImage = [self faviconForNode:node forADarkTheme:darkTheme];
+ [[button cell] setBookmarkCellText:[button title] image:theImage];
+ }
+ [self rebuildLayoutWithAnimated:NO];
if (folderController_)
[folderController_ faviconLoadedForNode:node];
}
@@ -2509,8 +2309,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
- (void)bookmarkDragDidEnd:(BookmarkButton*)button
operation:(NSDragOperation)operation {
- [button setHidden:NO];
- [self resetAllButtonPositionsWithAnimation:YES];
+ [self rebuildLayoutWithAnimated:YES];
}
@@ -2670,50 +2469,50 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
CGFloat x = 0;
CGFloat halfHorizontalPadding = 0.5 * bookmarks::kBookmarkHorizontalPadding;
int destIndex = [self indexForDragToPoint:point];
- int numButtons = displayedButtonCount_;
-
- CGFloat leftmostX;
- if (![supervisedBookmarksButton_ isHidden]) {
- leftmostX =
- NSMaxX([supervisedBookmarksButton_ frame]) + halfHorizontalPadding;
- } else if (![managedBookmarksButton_ isHidden]) {
- leftmostX = NSMaxX([managedBookmarksButton_ frame]) + halfHorizontalPadding;
- } else if (![appsPageShortcutButton_ isHidden]) {
- leftmostX = NSMaxX([appsPageShortcutButton_ frame]) + halfHorizontalPadding;
+ int numButtons = layout_.VisibleButtonCount();
+
+ CGFloat leadingOffset;
+ if (layout_.IsSupervisedBookmarksButtonVisible()) {
+ leadingOffset =
+ layout_.supervised_bookmarks_button_offset + halfHorizontalPadding;
+ } else if (layout_.IsManagedBookmarksButtonVisible()) {
+ leadingOffset =
+ layout_.managed_bookmarks_button_offset + halfHorizontalPadding;
+ } else if (layout_.IsAppsButtonVisible()) {
+ leadingOffset = layout_.apps_button_offset + halfHorizontalPadding;
} else {
- leftmostX = bookmarks::kBookmarkLeftMargin - halfHorizontalPadding;
+ leadingOffset = bookmarks::kBookmarkLeftMargin - halfHorizontalPadding;
}
// If it's a drop strictly between existing buttons ...
if (destIndex == 0) {
- x = leftmostX;
+ x = leadingOffset;
} else if (destIndex > 0 && destIndex < numButtons) {
// ... put the indicator right between the buttons.
- BookmarkButton* button =
- [buttons_ objectAtIndex:static_cast<NSUInteger>(destIndex-1)];
- DCHECK(button);
- NSRect buttonFrame = [button frame];
- x = NSMaxX(buttonFrame) + halfHorizontalPadding;
-
+ int64_t nodeId =
+ bookmarkModel_->bookmark_bar_node()->GetChild(destIndex)->id();
+ x = layout_.button_offsets[nodeId] - halfHorizontalPadding;
// If it's a drop at the end (past the last button, if there are any) ...
} else if (destIndex == numButtons) {
// and if it's past the last button ...
if (numButtons > 0) {
- // ... find the last button, and put the indicator to its right.
- BookmarkButton* button =
- [buttons_ objectAtIndex:static_cast<NSUInteger>(destIndex - 1)];
- DCHECK(button);
- x = NSMaxX([button frame]) + halfHorizontalPadding;
-
+ // ... find the last button, and put the indicator after it.
+ const BookmarkNode* node =
+ bookmarkModel_->bookmark_bar_node()->GetChild(destIndex - 1);
+ int64_t nodeId = node->id();
+ x = layout_.button_offsets[nodeId] + [self widthOfButtonForNode:node] +
+ halfHorizontalPadding;
// Otherwise, put it right at the beginning.
} else {
- x = leftmostX;
+ x = leadingOffset;
}
} else {
NOTREACHED();
}
- return x;
+ return cocoa_l10n_util::ShouldDoExperimentalRTLLayout()
+ ? NSWidth([buttonView_ frame]) - x
+ : x;
}
- (void)childFolderWillShow:(id<BookmarkButtonControllerProtocol>)child {
@@ -2737,7 +2536,6 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// Add a new folder controller as triggered by the given folder button.
- (void)addNewFolderControllerWithParentButton:(BookmarkButton*)parentButton {
-
// If doing a close/open, make sure the fullscreen chrome doesn't
// have a chance to begin animating away in the middle of things.
BrowserWindowController* browserController =
@@ -2773,34 +2571,6 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
browser_->profile());
}
-- (void)addButtonForNode:(const BookmarkNode*)node
- atIndex:(NSInteger)buttonIndex {
- int newOffset =
- bookmarks::kBookmarkLeftMargin - bookmarks::kBookmarkHorizontalPadding;
- if (buttonIndex == -1)
- buttonIndex = [buttons_ count]; // New button goes at the end.
- if (buttonIndex <= (NSInteger)[buttons_ count]) {
- if (buttonIndex) {
- BookmarkButton* targetButton = [buttons_ objectAtIndex:buttonIndex - 1];
- NSRect targetFrame = [targetButton frame];
- newOffset = targetFrame.origin.x + NSWidth(targetFrame) +
- bookmarks::kBookmarkHorizontalPadding;
- }
- BookmarkButton* newButton = [self buttonForNode:node xOffset:&newOffset];
- ++displayedButtonCount_;
- [buttons_ insertObject:newButton atIndex:buttonIndex];
- [buttonView_ addSubview:newButton];
- [self resetAllButtonPositionsWithAnimation:NO];
- // See if any buttons need to be pushed off to or brought in from the side.
- [self reconfigureBookmarkBar];
- } else {
- // A button from somewhere else (not the bar) is being moved to the
- // off-the-side so insure it gets redrawn if its showing.
- [self reconfigureBookmarkBar];
- [folderController_ reconfigureMenu];
- }
-}
-
// TODO(mrossetti): Duplicate code with BookmarkBarFolderController.
// http://crbug.com/35966
- (BOOL)addURLs:(NSArray*)urls withTitles:(NSArray*)titles at:(NSPoint)point {
@@ -2848,49 +2618,22 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
}
- (void)moveButtonFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex {
- if (fromIndex != toIndex) {
- NSInteger buttonCount = (NSInteger)[buttons_ count];
- if (toIndex == -1)
- toIndex = buttonCount;
- // See if we have a simple move within the bar, which will be the case if
- // both button indexes are in the visible space.
- if (fromIndex < buttonCount && toIndex < buttonCount) {
- BookmarkButton* movedButton = [buttons_ objectAtIndex:fromIndex];
- [buttons_ removeObjectAtIndex:fromIndex];
- [buttons_ insertObject:movedButton atIndex:toIndex];
- [movedButton setHidden:NO];
- [self resetAllButtonPositionsWithAnimation:NO];
- } else if (fromIndex < buttonCount) {
- // A button is being removed from the bar and added to off-the-side.
- // By now the node has already been inserted into the model so the
- // button to be added is represented by |toIndex|. Things get
- // complicated because the off-the-side is showing and must be redrawn
- // while possibly re-laying out the bookmark bar.
- [self removeButton:fromIndex animate:NO];
- [self reconfigureBookmarkBar];
- [folderController_ reconfigureMenu];
- } else if (toIndex < buttonCount) {
- // A button is being added to the bar and removed from off-the-side.
- // By now the node has already been inserted into the model so the
- // button to be added is represented by |toIndex|.
- const BookmarkNode* node = bookmarkModel_->bookmark_bar_node();
- const BookmarkNode* movedNode = node->GetChild(toIndex);
- DCHECK(movedNode);
- [self addButtonForNode:movedNode atIndex:toIndex];
- [self reconfigureBookmarkBar];
- } else {
- // A button is being moved within the off-the-side.
- fromIndex -= buttonCount;
- toIndex -= buttonCount;
- [folderController_ moveButtonFromIndex:fromIndex toIndex:toIndex];
- }
+ int buttonCount = layout_.VisibleButtonCount();
+ BOOL isMoveWithinOffTheSideMenu =
+ (toIndex >= buttonCount) && (fromIndex >= buttonCount);
+ if (isMoveWithinOffTheSideMenu) {
+ fromIndex -= buttonCount;
+ toIndex -= buttonCount;
+ [folderController_ moveButtonFromIndex:fromIndex toIndex:toIndex];
+ } else {
+ [self rebuildLayoutWithAnimated:NO];
}
}
- (void)removeButton:(NSInteger)buttonIndex animate:(BOOL)animate {
- if (buttonIndex < (NSInteger)[buttons_ count]) {
+ if ((size_t)buttonIndex < layout_.VisibleButtonCount()) {
// The button being removed is showing in the bar.
- BookmarkButton* oldButton = [buttons_ objectAtIndex:buttonIndex];
+ BookmarkButton* oldButton = buttons_[buttonIndex];
if (oldButton == [folderController_ parentButton]) {
// If we are deleting a button whose folder is currently open, close it!
[self closeAllBookmarkFolders];
@@ -2901,17 +2644,12 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
NSShowAnimationEffect(NSAnimationEffectDisappearingItemDefault, poofPoint,
NSZeroSize, nil, nil, nil);
}
- [oldButton setDelegate:nil];
- [oldButton removeFromSuperview];
- [buttons_ removeObjectAtIndex:buttonIndex];
- --displayedButtonCount_;
- [self resetAllButtonPositionsWithAnimation:YES];
- [self reconfigureBookmarkBar];
+ [self rebuildLayoutWithAnimated:YES];
} else if (folderController_ &&
[folderController_ parentButton] == offTheSideButton_) {
// The button being removed is in the OTS (off-the-side) and the OTS
// menu is showing so we need to remove the button.
- NSInteger index = buttonIndex - displayedButtonCount_;
+ NSInteger index = buttonIndex - layout_.VisibleButtonCount();
[folderController_ removeButton:index animate:animate];
}
}
@@ -2925,4 +2663,9 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
return [folderController_ controllerForNode:node];
}
+// For testing.
+- (const BookmarkBarLayout&)currentLayout {
+ return layout_;
+}
+
@end

Powered by Google App Engine
This is Rietveld 408576698