summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authorfinnur@chromium.org <finnur@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-01-22 22:00:09 +0000
committerfinnur@chromium.org <finnur@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-01-22 22:00:09 +0000
commitbd5a00a743c92290ec01d6ea603a5fd96098093e (patch)
treee2db07ba7b8f7e75911e5dede215bb9551216450 /chrome/browser
parenta26b7d4950c1e684ab1620674a335ff3a27eda5c (diff)
downloadchromium_src-bd5a00a743c92290ec01d6ea603a5fd96098093e.zip
chromium_src-bd5a00a743c92290ec01d6ea603a5fd96098093e.tar.gz
chromium_src-bd5a00a743c92290ec01d6ea603a5fd96098093e.tar.bz2
Resize and overflow for browser actions (part 1).
This changelist implements the following: - A resize gripper and chevron in the browser action container - Overflow when icons don't fit - Snap to multiple of icon size (no excess pixels) - Animation when resizing container / adding&removing icons (disable, enable). - Persists the last width of the browser action container. Known issues: - No menu (yes, the chevron button doesn't do anything yet). BUG=32101 TEST=Install and uninstall browser actions & make sure install bubble appears in the right location. Make sure browser action container does not expand if chevron is showing when you add browser action. Make sure container loses chevron when no overflow occurs. Make sure browser action icons never disappear due to shrinking when you release the mouse (when resizing). Make sure snapping works. Review URL: http://codereview.chromium.org/553039 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@36905 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/browser_prefs.cc4
-rw-r--r--chrome/browser/views/browser_actions_container.cc336
-rw-r--r--chrome/browser/views/browser_actions_container.h159
-rw-r--r--chrome/browser/views/detachable_toolbar_view.h12
-rw-r--r--chrome/browser/views/extensions/extension_installed_bubble.cc32
-rw-r--r--chrome/browser/views/extensions/extension_installed_bubble.h5
-rw-r--r--chrome/browser/views/location_bar_view.cc5
-rw-r--r--chrome/browser/views/location_bar_view.h4
-rw-r--r--chrome/browser/views/toolbar_view.cc19
9 files changed, 497 insertions, 79 deletions
diff --git a/chrome/browser/browser_prefs.cc b/chrome/browser/browser_prefs.cc
index b9e7fa2..710c4e0 100644
--- a/chrome/browser/browser_prefs.cc
+++ b/chrome/browser/browser_prefs.cc
@@ -34,6 +34,7 @@
#include "chrome/browser/task_manager.h"
#if defined(TOOLKIT_VIEWS) // TODO(port): whittle this down as we port
+#include "chrome/browser/views/browser_actions_container.h"
#include "chrome/browser/views/frame/browser_view.h"
#endif
@@ -92,6 +93,9 @@ void RegisterUserPrefs(PrefService* user_prefs) {
BlockedPopupContainer::RegisterUserPrefs(user_prefs);
HostZoomMap::RegisterUserPrefs(user_prefs);
DevToolsManager::RegisterUserPrefs(user_prefs);
+#if defined(TOOLKIT_VIEWS) // TODO(port): whittle this down as we port.
+ BrowserActionsContainer::RegisterUserPrefs(user_prefs);
+#endif
#if defined(TOOLKIT_GTK)
BrowserWindowGtk::RegisterUserPrefs(user_prefs);
#endif
diff --git a/chrome/browser/views/browser_actions_container.cc b/chrome/browser/views/browser_actions_container.cc
index edea4e0..6aaa387 100644
--- a/chrome/browser/views/browser_actions_container.cc
+++ b/chrome/browser/views/browser_actions_container.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -6,9 +6,11 @@
#include "app/gfx/canvas.h"
#include "app/resource_bundle.h"
+#include "app/slide_animation.h"
#include "base/stl_util-inl.h"
#include "base/string_util.h"
#include "chrome/browser/browser.h"
+#include "chrome/browser/browser_theme_provider.h"
#include "chrome/browser/browser_window.h"
#include "chrome/browser/extensions/extension_browser_event_router.h"
#include "chrome/browser/extensions/extensions_service.h"
@@ -16,20 +18,25 @@
#include "chrome/browser/renderer_host/render_widget_host_view.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/view_ids.h"
+#include "chrome/browser/views/detachable_toolbar_view.h"
#include "chrome/browser/views/extensions/extension_popup.h"
#include "chrome/browser/views/toolbar_view.h"
#include "chrome/common/notification_source.h"
#include "chrome/common/notification_type.h"
+#include "chrome/common/pref_names.h"
#include "grit/app_resources.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkTypeface.h"
#include "third_party/skia/include/effects/SkGradientShader.h"
+#include "views/controls/button/menu_button.h"
#include "views/controls/button/text_button.h"
+#include "grit/theme_resources.h"
+
// The size (both dimensions) of the buttons for page actions.
static const int kButtonSize = 29;
-// The padding between the browser actions and the omnibox/page menu.
+// The padding between the browser actions and the OmniBox/page menu.
static const int kHorizontalPadding = 4;
// The padding between browser action buttons. Visually, the actual number of
@@ -42,10 +49,20 @@ static const int kBrowserActionButtonPadding = 3;
// can draw the badge outside the visual bounds of the container.
static const int kControlVertOffset = 6;
-// The maximum of the minimum number of browser actions present when there is
-// not enough space to fit all the browser actions in the toolbar.
-static const int kMinimumNumberOfVisibleBrowserActions = 2;
+// The margin between the divider and the chrome menu buttons.
+static const int kDividerHorizontalMargin = 2;
+
+// The padding above and below the divider.
+static const int kDividerVerticalPadding = 9;
+
+// The margin above the chevron.
+static const int kChevronTopMargin = 9;
+
+// The margin to the right of the chevron.
+static const int kChevronRightMargin = 4;
+// Extra hit-area for the resize gripper.
+static const int kExtraResizeArea = 4;
////////////////////////////////////////////////////////////////////////////////
// BrowserActionButton
@@ -130,6 +147,9 @@ void BrowserActionButton::Observe(NotificationType type,
const NotificationDetails& details) {
if (type == NotificationType::EXTENSION_BROWSER_ACTION_UPDATED) {
UpdateState();
+ // The browser action may have become visible/hidden so we need to make
+ // sure the state gets updated.
+ panel_->OnBrowserActionVisibilityChanged();
} else {
NOTREACHED() << L"Received unexpected notification";
}
@@ -239,7 +259,6 @@ void BrowserActionView::PaintChildren(gfx::Canvas* canvas) {
action->PaintBadge(canvas, gfx::Rect(width(), height()), tab_id);
}
-
////////////////////////////////////////////////////////////////////////////////
// BrowserActionsContainer
@@ -249,6 +268,11 @@ BrowserActionsContainer::BrowserActionsContainer(
toolbar_(toolbar),
popup_(NULL),
popup_button_(NULL),
+ resize_gripper_(NULL),
+ chevron_(NULL),
+ suppress_chevron_(false),
+ resize_amount_(0),
+ animation_target_size_(0),
ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)) {
ExtensionsService* extension_service = profile->GetExtensionsService();
if (!extension_service) // The |extension_service| can be NULL in Incognito.
@@ -263,8 +287,25 @@ BrowserActionsContainer::BrowserActionsContainer(
registrar_.Add(this, NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE,
Source<Profile>(profile_));
- for (size_t i = 0; i < extension_service->extensions()->size(); ++i)
- AddBrowserAction(extension_service->extensions()->at(i));
+ resize_animation_.reset(new SlideAnimation(this));
+
+ resize_gripper_ = new views::ResizeGripper(this);
+ resize_gripper_->SetVisible(false);
+ AddChildView(resize_gripper_);
+
+ // TODO(glen): Come up with a new bitmap for the chevron.
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ SkBitmap* chevron_image = rb.GetBitmapNamed(IDR_BOOKMARK_BAR_CHEVRONS);
+ chevron_ = new views::MenuButton(NULL, std::wstring(), this, false);
+ chevron_->SetVisible(false);
+ chevron_->SetIcon(*chevron_image);
+ // Chevron contains >> that should point left in LTR locales.
+ chevron_->EnableCanvasFlippingForRTLUI(true);
+ AddChildView(chevron_);
+
+ int predefined_width =
+ profile_->GetPrefs()->GetInteger(prefs::kBrowserActionContainerWidth);
+ container_size_ = gfx::Size(predefined_width, kButtonSize);
SetID(VIEW_ID_BROWSER_ACTION_TOOLBAR);
}
@@ -274,7 +315,12 @@ BrowserActionsContainer::~BrowserActionsContainer() {
DeleteBrowserActionViews();
}
-int BrowserActionsContainer::GetCurrentTabId() {
+// Static.
+void BrowserActionsContainer::RegisterUserPrefs(PrefService* prefs) {
+ prefs->RegisterIntegerPref(prefs::kBrowserActionContainerWidth, 0);
+}
+
+int BrowserActionsContainer::GetCurrentTabId() const {
TabContents* tab_contents = toolbar_->browser()->GetSelectedTabContents();
if (!tab_contents)
return -1;
@@ -310,11 +356,40 @@ void BrowserActionsContainer::AddBrowserAction(Extension* extension) {
if (!extension->browser_action())
return;
+ // Before we change anything, determine the number of visible browser actions.
+ size_t visible_actions = 0;
+ for (size_t i = 0; i < browser_action_views_.size(); ++i) {
+ if (browser_action_views_[i]->IsVisible())
+ ++visible_actions;
+ }
+
+ // Add the new browser action to the vector and the view hierarchy.
BrowserActionView* view = new BrowserActionView(extension, this);
browser_action_views_.push_back(view);
AddChildView(view);
- if (GetParent())
- GetParent()->SchedulePaint();
+
+ // For details on why we do the following see the class comments in the
+ // header.
+
+ // Determine if we need to increase (we only do that if the container was
+ // showing all icons before the addition of this icon). We use -1 because
+ // we don't want to count the view that we just added.
+ if (visible_actions < browser_action_views_.size() - 1) {
+ // Some icons were hidden, don't increase the size of the container.
+ OnBrowserActionVisibilityChanged();
+ } else {
+ // Container was at max, increase the size of it by one icon.
+ animation_target_size_ = IconCountToWidth(visible_actions + 1);
+
+ // We don't want the chevron to appear while we animate. See documentation
+ // in the header for why we do this.
+ suppress_chevron_ = !chevron_->IsVisible();
+
+ // Animate!
+ resize_animation_->Reset();
+ resize_animation_->SetTweenType(SlideAnimation::NONE);
+ resize_animation_->Show();
+ }
}
void BrowserActionsContainer::RemoveBrowserAction(Extension* extension) {
@@ -325,6 +400,13 @@ void BrowserActionsContainer::RemoveBrowserAction(Extension* extension) {
HidePopup();
}
+ // Before we change anything, determine the number of visible browser actions.
+ int visible_actions = 0;
+ for (size_t i = 0; i < browser_action_views_.size(); ++i) {
+ if (browser_action_views_[i]->IsVisible())
+ ++visible_actions;
+ }
+
for (std::vector<BrowserActionView*>::iterator iter =
browser_action_views_.begin(); iter != browser_action_views_.end();
++iter) {
@@ -332,8 +414,22 @@ void BrowserActionsContainer::RemoveBrowserAction(Extension* extension) {
RemoveChildView(*iter);
delete *iter;
browser_action_views_.erase(iter);
- if (GetParent())
- GetParent()->SchedulePaint();
+
+ // For details on why we do the following see the class comments in the
+ // header.
+
+ // Calculate the target size we'll animate to (end state). This might be
+ // the same size (if the icon we are removing is in the overflow bucket
+ // and there are other icons there). We don't decrement visible_actions
+ // because we want the container to stay the same size (clamping will take
+ // care of shrinking the container if there aren't enough icons to show).
+ animation_target_size_ =
+ ClampToNearestIconCount(IconCountToWidth(visible_actions));
+
+ // Animate!
+ resize_animation_->Reset();
+ resize_animation_->SetTweenType(SlideAnimation::EASE_OUT);
+ resize_animation_->Show();
return;
}
}
@@ -350,13 +446,16 @@ void BrowserActionsContainer::DeleteBrowserActionViews() {
}
void BrowserActionsContainer::OnBrowserActionVisibilityChanged() {
+ resize_gripper_->SetVisible(browser_action_views_.size() > 0);
+
toolbar_->Layout();
+ toolbar_->SchedulePaint();
}
void BrowserActionsContainer::HidePopup() {
if (popup_) {
// This sometimes gets called via a timer (See BubbleLostFocus), so clear
- // the task factory. in case one is pending.
+ // the task factory in case one is pending.
task_factory_.RevokeAll();
// Save these variables in local temporaries since destroying the popup
@@ -427,27 +526,107 @@ void BrowserActionsContainer::OnBrowserActionExecuted(
gfx::Size BrowserActionsContainer::GetPreferredSize() {
if (browser_action_views_.empty())
return gfx::Size(0, 0);
- int width = kHorizontalPadding * 2 +
- browser_action_views_.size() * kButtonSize;
- if (browser_action_views_.size() > 1)
- width += (browser_action_views_.size() - 1) * kBrowserActionButtonPadding;
+
+ // We calculate the size of the view by taking the current width and
+ // subtracting resize_amount_ (the latter represents how far the user is
+ // resizing the view or, if animating the snapping, how far to animate it).
+ // But we also clamp it to a minimum size and the maximum size, so that the
+ // container can never shrink too far or take up more space than it needs. In
+ // other words: ContainerMinSize() < width() - resize < ClampTo(MAX).
+ int width = std::max(ContainerMinSize(),
+ container_size_.width() - resize_amount_);
+ int max_width = ClampToNearestIconCount(-1); // -1 gives max width.
+ width = std::min(width, max_width);
+
return gfx::Size(width, kButtonSize);
}
void BrowserActionsContainer::Layout() {
+ if (!resize_gripper_ || !chevron_)
+ return; // These classes are not created in Incognito mode.
+ if (browser_action_views_.size() == 0) {
+ resize_gripper_->SetVisible(false);
+ chevron_->SetVisible(false);
+ return;
+ }
+
+ int x = 0;
+ if (resize_gripper_->IsVisible()) {
+ // We'll draw the resize gripper a little wider, to add some invisible hit
+ // target area - but we don't account for it anywhere.
+ gfx::Size sz = resize_gripper_->GetPreferredSize();
+ resize_gripper_->SetBounds(x, (height() - sz.height()) / 2 + 1,
+ sz.width() + kExtraResizeArea, sz.height());
+ x += sz.width();
+ }
+
+ x += kHorizontalPadding;
+
+ // Calculate if all icons fit without showing the chevron. We need to know
+ // this beforehand, because showing the chevron will decrease the space that
+ // we have to draw the visible ones (ie. if one icon is visible and another
+ // doesn't have enough room).
+ int last_x_of_icons = x +
+ (browser_action_views_.size() * kButtonSize) +
+ ((browser_action_views_.size() - 1) *
+ kBrowserActionButtonPadding);
+
+ int max_x = width() - kDividerHorizontalMargin - kChevronRightMargin;
+
+ // If they don't all fit, show the chevron (unless suppressed).
+ gfx::Size chevron_size;
+ if (last_x_of_icons >= max_x && !suppress_chevron_) {
+ chevron_->SetVisible(true);
+ chevron_size = chevron_->GetPreferredSize();
+ max_x -= chevron_size.width();
+ chevron_->SetBounds(width() - chevron_size.width() - kChevronRightMargin,
+ kChevronTopMargin,
+ chevron_size.width(), chevron_size.height());
+ } else {
+ chevron_->SetVisible(false);
+ }
+
+ // Now draw the icons for the browser actions in the available space.
for (size_t i = 0; i < browser_action_views_.size(); ++i) {
BrowserActionView* view = browser_action_views_[i];
- int x = kHorizontalPadding +
- i * (kButtonSize + kBrowserActionButtonPadding);
- if (x + kButtonSize <= width()) {
+ // Add padding between buttons if multiple buttons.
+ int padding = (i > 0) ? kBrowserActionButtonPadding : 0;
+ if (x + kButtonSize + padding < max_x) {
+ x += padding;
view->SetBounds(x, 0, kButtonSize, height());
view->SetVisible(true);
+ x += kButtonSize;
} else {
view->SetVisible(false);
}
}
}
+void BrowserActionsContainer::Paint(gfx::Canvas* canvas) {
+ // The one pixel themed vertical divider to the right of the browser actions.
+ DetachableToolbarView::PaintVerticalDivider(
+ canvas,
+ width() - kDividerHorizontalMargin, height(), kDividerVerticalPadding,
+ DetachableToolbarView::kEdgeDividerColor,
+ DetachableToolbarView::kMiddleDividerColor,
+ GetThemeProvider()->GetColor(BrowserThemeProvider::COLOR_TOOLBAR));
+}
+
+void BrowserActionsContainer::ViewHierarchyChanged(bool is_add,
+ views::View* parent,
+ views::View* child) {
+ if (is_add && child == this) {
+ // We do this here instead of in the constructor because AddBrowserAction
+ // calls Layout on the Toolbar, which needs this object to be constructed
+ // before its Layout function is called.
+ ExtensionsService* extension_service = profile_->GetExtensionsService();
+ if (!extension_service)
+ return; // The |extension_service| can be NULL in Incognito.
+ for (size_t i = 0; i < extension_service->extensions()->size(); ++i)
+ AddBrowserAction(extension_service->extensions()->at(i));
+ }
+}
+
void BrowserActionsContainer::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
@@ -518,19 +697,110 @@ void BrowserActionsContainer::BubbleLostFocus(BrowserBubble* bubble,
task_factory_.NewRunnableMethod(&BrowserActionsContainer::HidePopup));
}
-int BrowserActionsContainer::GetClippedPreferredWidth(int available_width) {
- if (browser_action_views_.size() == 0)
- return 0;
+void BrowserActionsContainer::RunMenu(View* source, const gfx::Point& pt) {
+ // TODO(finnur): Show menu for all the hidden icons.
+}
+
+int BrowserActionsContainer::ClampToNearestIconCount(int pixelWidth) const {
+ // Calculate the width of one icon.
+ int icon_width = (kButtonSize + kBrowserActionButtonPadding);
+
+ // Calculate pixel count for the area not used by the icons.
+ int extras = WidthOfNonIconArea();
+
+ size_t icon_count = 0u;
+ if (pixelWidth >= 0) {
+ // Caller wants to know how many icons fit within a given space so we start
+ // by subtracting the padding, gripper and dividers.
+ int icon_area = pixelWidth - extras;
+ icon_area = std::max(0, icon_area);
+
+ // Make sure we never throw an icon into the chevron menu just because
+ // there isn't enough enough space for the invisible padding around buttons.
+ icon_area += kBrowserActionButtonPadding - 1;
+
+ // Count the number of icons that fit within that area.
+ icon_count = icon_area / icon_width;
+
+ // No use allowing more than what we have.
+ if (icon_count > browser_action_views_.size())
+ icon_count = browser_action_views_.size();
+ else if (icon_count == 0)
+ extras = ContainerMinSize(); // Allow very narrow width if no icons.
+ } else {
+ // A negative |pixels| count indicates caller wants to know the max width
+ // that fits all icons;
+ icon_count = browser_action_views_.size();
+ }
+
+ int returning = extras + (icon_count * icon_width);
+ return returning;
+}
+
+int BrowserActionsContainer::WidthOfNonIconArea() const {
+ int chevron_size = (chevron_->IsVisible()) ?
+ chevron_->GetPreferredSize().width() : 0;
+ return resize_gripper_->GetPreferredSize().width() + kHorizontalPadding +
+ chevron_size + kChevronRightMargin + kDividerHorizontalMargin;
+}
+
+int BrowserActionsContainer::IconCountToWidth(int icons) const {
+ DCHECK(icons >= 0);
+ if (icons == 0)
+ return ContainerMinSize();
+
+ int icon_width = kButtonSize + kBrowserActionButtonPadding;
+
+ return WidthOfNonIconArea() + (icons * icon_width);
+}
+
+int BrowserActionsContainer::ContainerMinSize() const {
+ return resize_gripper_->width() + chevron_->width() + kChevronRightMargin;
+}
+
+void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) {
+ if (!done_resizing) {
+ resize_amount_ = resize_amount;
+ OnBrowserActionVisibilityChanged();
+ } else {
+ // For details on why we do the following see the class comments in the
+ // header.
+
+ // Clamp lower limit to 0 and upper limit to the amount that allows enough
+ // room for all icons to show.
+ int new_width = std::max(0, container_size_.width() - resize_amount);
+ int max_width = ClampToNearestIconCount(-1);
+ new_width = std::min(new_width, max_width);
+
+ // Up until now we've only been modifying the resize_amount, but now it is
+ // time to set the container size to the size we have resized to, but then
+ // animate to the nearest icon count size (or down to min size if no icon).
+ container_size_.set_width(new_width);
+ animation_target_size_ = ClampToNearestIconCount(new_width);
+ resize_animation_->Reset();
+ resize_animation_->SetTweenType(SlideAnimation::EASE_OUT);
+ resize_animation_->Show();
+ }
+}
+
+void BrowserActionsContainer::AnimationProgressed(const Animation* animation) {
+ DCHECK(animation == resize_animation_.get());
+
+ double e = resize_animation_->GetCurrentValue();
+ int difference = container_size_.width() - animation_target_size_;
- // We have at least one browser action. Make some of them sticky.
- int min_width = kHorizontalPadding * 2 +
- std::min(static_cast<int>(browser_action_views_.size()),
- kMinimumNumberOfVisibleBrowserActions) * kButtonSize;
+ resize_amount_ = static_cast<int>(e * difference);
+
+ OnBrowserActionVisibilityChanged();
+}
- // Even if available_width is <= 0, we still return at least the |min_width|.
- if (available_width <= 0)
- return min_width;
+void BrowserActionsContainer::AnimationEnded(const Animation* animation) {
+ container_size_.set_width(animation_target_size_);
+ animation_target_size_ = 0;
+ resize_amount_ = 0;
+ OnBrowserActionVisibilityChanged();
+ suppress_chevron_ = false;
- return std::max(min_width, available_width - available_width % kButtonSize +
- kHorizontalPadding * 2);
+ profile_->GetPrefs()->SetInteger(prefs::kBrowserActionContainerWidth,
+ container_size_.width());
}
diff --git a/chrome/browser/views/browser_actions_container.h b/chrome/browser/views/browser_actions_container.h
index 7a6daf3..d2b87a4 100644
--- a/chrome/browser/views/browser_actions_container.h
+++ b/chrome/browser/views/browser_actions_container.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -14,13 +14,17 @@
#include "chrome/common/notification_observer.h"
#include "chrome/common/notification_registrar.h"
#include "views/controls/button/menu_button.h"
+#include "views/controls/menu/view_menu_delegate.h"
+#include "views/controls/resize_gripper.h"
#include "views/view.h"
class BrowserActionsContainer;
class Extension;
class ExtensionAction;
class ExtensionPopup;
+class PrefService;
class Profile;
+class SlideAnimation;
class ToolbarView;
////////////////////////////////////////////////////////////////////////////////
@@ -132,26 +136,103 @@ class BrowserActionView : public views::View {
BrowserActionButton* button_;
};
-
////////////////////////////////////////////////////////////////////////////////
//
// The BrowserActionsContainer is a container view, responsible for drawing the
-// icons that represent browser actions (extensions that add icons to the
-// toolbar).
+// browser action icons (extensions that add icons to the toolbar).
+//
+// The BrowserActionsContainer (when it contains one or more icons) consists of
+// the following elements, numbered as seen below the line:
+//
+// || _ Icon _ Icon _ Icon _ [chevron] _ | _
+// -----------------------------------------
+// 1 2 3 4 5 6 7 8
+//
+// 1) The ResizeGripper view.
+// 2) Padding (kHorizontalPadding).
+// 3) The browser action icon button (BrowserActionView).
+// 4) Padding to visually separate icons from one another
+// (kBrowserActionButtonPadding). Not included if only one icon visible.
+// 5) The chevron menu (MenuButton), shown when there is not enough room to show
+// all the icons.
+// 6) Padding (kDividerHorizontalMargin).
+// 7) A thin vertical divider drawn during Paint to create visual separation for
+// the container from the Page and Wrench menus.
+// 8) Padding (kChevronRightMargin).
+//
+// The BrowserActionsContainer follows a few rules, in terms of user experience:
+//
+// 1) The container can never grow beyond the space needed to show all icons
+// (hereby referred to as the max width).
+// 2) The container can never shrink below the space needed to show just the
+// resize gripper and the chevron (ignoring the case where there are no icons to
+// show, in which case the container won't be visible anyway).
+// 3) The container snaps into place (to the pixel count that fits the visible
+// icons) to make sure there is no wasted space at the edges of the container.
+// 4) If the user adds or removes icons (read: installs/uninstalls browser
+// actions) we grow and shrink the container as needed - but ONLY if the
+// container was at max width to begin with.
+// 5) If the container is NOT at max width (has an overflow menu), we respect
+// that size when adding and removing icons and DON'T grow/shrink the container.
+// This means that new icons (which always appear at the far right) will show up
+// in the overflow menu. The install bubble for extensions points to the chevron
+// menu in this case.
+//
+// Resizing the BrowserActionsContainer:
+//
+// The ResizeGripper view sends OnResize messages to the BrowserActionsContainer
+// class as the user drags the gripper. This modifies the value for
+// |resize_amount_|. That indicates to the container that a resize is in
+// progress and is used to calculate the size in GetPreferredSize(), though
+// that function never exceeds the defined minimum and maximum size of the
+// container.
+//
+// When the user releases the mouse (ends the resize), we calculate a target
+// size for the container (animation_target_size_), clamp that value to the
+// containers min and max and then animate from the *current* position (that the
+// user has dragged the view to) to the target size.
+//
+// Animating the BrowserActionsContainer:
+//
+// Animations are used when snapping the container to a value that fits all
+// visible icons. This can be triggered when the user finishes resizing the
+// container or when Browser Actions are added/removed.
+//
+// We always animate from the current width (container_size_.width()) to the
+// target size (animation_target_size_), using |resize_amount| to keep track of
+// the animation progress.
+//
+// NOTE: When adding Browser Actions to a maximum width container (no overflow)
+// we make sure to suppress the chevron menu if it wasn't visible. This is
+// because we won't have enough space to show the new Browser Action until the
+// animation ends and we don't want the chevron to flash into view while we are
+// growing the container.
//
////////////////////////////////////////////////////////////////////////////////
-class BrowserActionsContainer : public views::View,
- public NotificationObserver,
- public BrowserBubble::Delegate {
+class BrowserActionsContainer
+ : public views::View,
+ public NotificationObserver,
+ public BrowserBubble::Delegate,
+ public views::ViewMenuDelegate,
+ public views::ResizeGripper::ResizeGripperDelegate,
+ public AnimationDelegate {
public:
BrowserActionsContainer(Profile* profile, ToolbarView* toolbar);
virtual ~BrowserActionsContainer();
+ static void RegisterUserPrefs(PrefService* prefs);
+
// Get the number of browser actions being displayed.
- int num_browser_actions() { return browser_action_views_.size(); }
+ int num_browser_actions() const { return browser_action_views_.size(); }
+
+ // Whether we are performing resize animation on the container.
+ bool animating() const { return animation_target_size_ > 0; }
+
+ // Returns the chevron, if any.
+ const views::View* chevron() const { return chevron_; }
// Returns the current tab's ID, or -1 if there is no current tab.
- int GetCurrentTabId();
+ int GetCurrentTabId() const;
// Get a particular browser action view.
BrowserActionView* GetBrowserActionViewAt(int index) {
@@ -176,6 +257,10 @@ class BrowserActionsContainer : public views::View,
// Overridden from views::View:
virtual gfx::Size GetPreferredSize();
virtual void Layout();
+ virtual void Paint(gfx::Canvas* canvas);
+ virtual void ViewHierarchyChanged(bool is_add,
+ views::View* parent,
+ views::View* child);
// Overridden from NotificationObserver:
virtual void Observe(NotificationType type,
@@ -189,11 +274,15 @@ class BrowserActionsContainer : public views::View,
virtual void BubbleLostFocus(BrowserBubble* bubble,
gfx::NativeView focused_view);
- // Get clipped width required to precisely fit the browser action icons
- // given a tentative available width. The minimum size it returns is not
- // zero, but depends on the minimum number of icons that have to be there
- // by default irrespective of the available space to draw them.
- int GetClippedPreferredWidth(int available_width);
+ // Overridden from views::ViewMenuDelegate:
+ virtual void RunMenu(View* source, const gfx::Point& pt);
+
+ // Overridden from ResizeGripper::ResizeGripperDelegate:
+ virtual void OnResize(int resize_amount, bool done_resizing);
+
+ // Overridden from AnimationDelegate:
+ virtual void AnimationProgressed(const Animation* animation);
+ virtual void AnimationEnded(const Animation* animation);
// Hide the current popup.
void HidePopup();
@@ -214,6 +303,24 @@ class BrowserActionsContainer : public views::View,
// no such view.
void RemoveBrowserAction(Extension* extension);
+ // Takes a width in pixels, calculates how many icons fit within that space
+ // (up to the maximum number of icons in our vector) and shaves off the
+ // excess pixels.
+ int ClampToNearestIconCount(int pixels) const;
+
+ // Calculates the width of the container area NOT used to show the icons (the
+ // controls to the left and to the right of the icons).
+ int WidthOfNonIconArea() const;
+
+ // Given a number of |icons| return the amount of pixels needed to draw it,
+ // including the controls (chevron if visible and resize gripper).
+ int IconCountToWidth(int icons) const;
+
+ // Returns the absolute minimum size you can shrink the container down to and
+ // still show it. We account for the chevron and the resize gripper, but not
+ // all the padding that we normally show if there are icons.
+ int ContainerMinSize() const;
+
// The vector of browser actions (icons/image buttons for each action).
std::vector<BrowserActionView*> browser_action_views_;
@@ -231,6 +338,30 @@ class BrowserActionsContainer : public views::View,
// from browser_action_views_).
BrowserActionButton* popup_button_;
+ // The current size of the container.
+ gfx::Size container_size_;
+
+ // The resize gripper for the container.
+ views::ResizeGripper* resize_gripper_;
+
+ // The chevron for accessing the overflow items.
+ views::MenuButton* chevron_;
+
+ // The animation that happens when the container snaps to place.
+ scoped_ptr<SlideAnimation> resize_animation_;
+
+ // Don't show the chevron while animating.
+ bool suppress_chevron_;
+
+ // This is used while the user is resizing (and when the animations are in
+ // progress) to know how wide the delta is between the current state and what
+ // we should draw.
+ int resize_amount_;
+
+ // Keeps track of the absolute pixel width the container should have when we
+ // are done animating.
+ int animation_target_size_;
+
ScopedRunnableMethodFactory<BrowserActionsContainer> task_factory_;
DISALLOW_COPY_AND_ASSIGN(BrowserActionsContainer);
diff --git a/chrome/browser/views/detachable_toolbar_view.h b/chrome/browser/views/detachable_toolbar_view.h
index d27ee53..7d49c9b 100644
--- a/chrome/browser/views/detachable_toolbar_view.h
+++ b/chrome/browser/views/detachable_toolbar_view.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -66,9 +66,13 @@ class DetachableToolbarView : public AccessibleToolbarView {
const SkRect& rect,
double roundness);
- // Paint a themed gradient divider at location |x|. The color of the divider
- // is a gradient starting with |top_color| at the top, and changing into
- // |middle_color| and then over to |bottom_color| as you go further down.
+ // Paint a themed gradient divider at location |x|. |height| is the full
+ // height of the view you want to paint the divider into, not the height of
+ // the divider. The height of the divider will become:
+ // |height| - 2 * |vertical_padding|.
+ // The color of the divider is a gradient starting with |top_color| at the
+ // top, and changing into |middle_color| and then over to |bottom_color| as
+ // you go further down.
static void PaintVerticalDivider(gfx::Canvas* canvas,
int x,
int height,
diff --git a/chrome/browser/views/extensions/extension_installed_bubble.cc b/chrome/browser/views/extensions/extension_installed_bubble.cc
index 4d903ec..7f92bbc 100644
--- a/chrome/browser/views/extensions/extension_installed_bubble.cc
+++ b/chrome/browser/views/extensions/extension_installed_bubble.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -46,6 +46,12 @@ const int kCloseButtonPadding = 3;
// 4px to align with icon.
const int kRightcolumnVerticalShift = -4;
+// How long to wait for browser action animations to complete before retrying.
+const int kAnimationWaitTime = 50;
+
+// How often we retry when waiting for browser action animation to end.
+const int kAnimationWaitMaxRetry = 10;
+
// InstalledBubbleContent is the content view which is placed in the
// ExtensionInstalledBubble. It displays the install icon and explanatory
// text about the installed extension.
@@ -188,7 +194,8 @@ ExtensionInstalledBubble::ExtensionInstalledBubble(Extension *extension,
SkBitmap icon)
: extension_(extension),
browser_(browser),
- icon_(icon) {
+ icon_(icon),
+ animation_wait_retries_(0) {
AddRef(); // Balanced in InfoBubbleClosing.
if (extension_->browser_action()) {
@@ -215,6 +222,7 @@ void ExtensionInstalledBubble::Observe(NotificationType type,
if (type == NotificationType::EXTENSION_LOADED) {
Extension* extension = Details<Extension>(details).ptr();
if (extension == extension_) {
+ animation_wait_retries_ = 0;
// PostTask to ourself to allow all EXTENSION_LOADED Observers to run.
MessageLoopForUI::current()->PostTask(FROM_HERE, NewRunnableMethod(this,
&ExtensionInstalledBubble::ShowInternal));
@@ -228,10 +236,24 @@ void ExtensionInstalledBubble::ShowInternal() {
BrowserView* browser_view = BrowserView::GetBrowserViewForNativeWindow(
browser_->window()->GetNativeHandle());
- views::View* reference_view = NULL;
+ const views::View* reference_view = NULL;
if (type_ == BROWSER_ACTION) {
- reference_view = browser_view->GetToolbarView()->browser_actions()
- ->GetBrowserActionView(extension_);
+ BrowserActionsContainer* container =
+ browser_view->GetToolbarView()->browser_actions();
+ if (container->animating() &&
+ animation_wait_retries_++ < kAnimationWaitMaxRetry) {
+ // We don't know where the view will be until the container has stopped
+ // animating, so check back in a little while.
+ MessageLoopForUI::current()->PostDelayedTask(
+ FROM_HERE, NewRunnableMethod(this,
+ &ExtensionInstalledBubble::ShowInternal), kAnimationWaitTime);
+ return;
+ }
+ reference_view = container->GetBrowserActionView(extension_);
+ // If the view is not visible then it is in the chevron, so point the
+ // install bubble to the chevron instead.
+ if (!reference_view->IsVisible())
+ reference_view = container->chevron();
DCHECK(reference_view);
} else if (type_ == PAGE_ACTION) {
LocationBarView* location_bar_view = browser_view->GetLocationBarView();
diff --git a/chrome/browser/views/extensions/extension_installed_bubble.h b/chrome/browser/views/extensions/extension_installed_bubble.h
index 3c9f118..53c68e5 100644
--- a/chrome/browser/views/extensions/extension_installed_bubble.h
+++ b/chrome/browser/views/extensions/extension_installed_bubble.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -75,6 +75,9 @@ class ExtensionInstalledBubble
NotificationRegistrar registrar_;
BubbleType type_;
+ // How many times we've deferred due to animations being in progress.
+ int animation_wait_retries_;
+
DISALLOW_COPY_AND_ASSIGN(ExtensionInstalledBubble);
};
diff --git a/chrome/browser/views/location_bar_view.cc b/chrome/browser/views/location_bar_view.cc
index e7a85f3..03a17f9 100644
--- a/chrome/browser/views/location_bar_view.cc
+++ b/chrome/browser/views/location_bar_view.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -354,6 +354,7 @@ views::View* LocationBarView::GetPageActionView(
}
return NULL;
}
+
gfx::Size LocationBarView::GetPreferredSize() {
return gfx::Size(0,
(popup_window_mode_ ? kPopupBackground : kBackground)->height());
@@ -1372,7 +1373,7 @@ bool LocationBarView::PageActionImageView::OnMousePressed(
button = 2;
} else if (event.IsRightMouseButton()) {
// Get the top left point of this button in screen coordinates.
- gfx::Point point = gfx::Point(0,0);
+ gfx::Point point = gfx::Point(0, 0);
ConvertPointToScreen(this, &point);
// Make the menu appear below the button.
diff --git a/chrome/browser/views/location_bar_view.h b/chrome/browser/views/location_bar_view.h
index 3319c39..cc7fb215 100644
--- a/chrome/browser/views/location_bar_view.h
+++ b/chrome/browser/views/location_bar_view.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -403,7 +403,7 @@ class LocationBarView : public LocationBar,
// is the current page URL.
void UpdateVisibility(TabContents* contents, const GURL& url);
- // Either notify listners or show a popup depending on the page action.
+ // Either notify listeners or show a popup depending on the page action.
void ExecuteAction(int button);
private:
diff --git a/chrome/browser/views/toolbar_view.cc b/chrome/browser/views/toolbar_view.cc
index 71037bb..1ff6636 100644
--- a/chrome/browser/views/toolbar_view.cc
+++ b/chrome/browser/views/toolbar_view.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -62,9 +62,6 @@ static const int kStatusBubbleWidth = 480;
// Separation between the location bar and the menus.
static const int kMenuButtonOffset = 3;
-// The minimum width of the location bar when browser actions are visible.
-static const int kMinLocationBarWidthWithBrowserActions = 400;
-
// Padding to the right of the location bar
static const int kPaddingRight = 2;
@@ -402,20 +399,6 @@ void ToolbarView::Layout() {
app_menu_width - page_menu_width - browser_actions_width -
kMenuButtonOffset - go_button_width - location_x;
- // We wait until the width of location bar is a minimum allowed. After this
- // state, the width available for the browser actions is compromised until
- // it can hold a minimum number of browser actions (currently 2). After this
- // state, the location bar width starts shrinking again, with the minimum
- // number of browser actions sticking on the the right of the location bar.
- // TODO(sidchat): Use percentage width instead of fixed width to determine
- // minimum width of the location bar. BUG=24316.
- if (available_width < kMinLocationBarWidthWithBrowserActions &&
- browser_actions_width > 0) {
- available_width += browser_actions_width;
- browser_actions_width = browser_actions_->GetClippedPreferredWidth(
- available_width - kMinLocationBarWidthWithBrowserActions);
- available_width -= browser_actions_width;
- }
location_bar_->SetBounds(location_x, child_y, std::max(available_width, 0),
child_height);