blob: 7d223f021a313f80f060c87ac95e623c7a7ac734 [file] [log] [blame]
// Copyright 2013 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.
#include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
#include <algorithm>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/memory/ptr_util.h"
#include "base/numerics/ranges.h"
#include "chrome/browser/extensions/extension_message_bubble_controller.h"
#include "chrome/browser/extensions/tab_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
#include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
#include "chrome/browser/ui/views/frame/app_menu_button.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.h"
#include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/common/extensions/command.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/theme_resources.h"
#include "extensions/common/feature_switch.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/theme_provider.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/resize_area.h"
#include "ui/views/controls/separator.h"
#include "ui/views/widget/widget.h"
////////////////////////////////////////////////////////////////////////////////
// BrowserActionsContainer::DropPosition
struct BrowserActionsContainer::DropPosition {
DropPosition(size_t row, size_t icon_in_row);
// The (0-indexed) row into which the action will be dropped.
size_t row;
// The (0-indexed) icon in the row before the action will be dropped.
size_t icon_in_row;
};
BrowserActionsContainer::DropPosition::DropPosition(
size_t row, size_t icon_in_row)
: row(row), icon_in_row(icon_in_row) {
}
////////////////////////////////////////////////////////////////////////////////
// BrowserActionsContainer
BrowserActionsContainer::BrowserActionsContainer(
Browser* browser,
BrowserActionsContainer* main_container,
Delegate* delegate,
bool interactive)
: delegate_(delegate),
browser_(browser),
main_container_(main_container),
interactive_(interactive) {
set_id(VIEW_ID_BROWSER_ACTION_TOOLBAR);
toolbar_actions_bar_ = delegate_->CreateToolbarActionsBar(
this, browser,
main_container ? main_container->toolbar_actions_bar_.get() : nullptr);
if (!ShownInsideMenu()) {
if (interactive_) {
resize_area_ = new views::ResizeArea(this);
AddChildView(resize_area_);
}
resize_animation_.reset(new gfx::SlideAnimation(this));
if (GetSeparatorAreaWidth() > 0) {
separator_ = new views::Separator();
AddChildView(separator_);
}
} else {
DCHECK(!base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu));
}
}
BrowserActionsContainer::~BrowserActionsContainer() {
if (active_bubble_)
active_bubble_->GetWidget()->Close();
// We should synchronously receive the OnWidgetClosing() event, so we should
// always have cleared the active bubble by now.
DCHECK(!active_bubble_);
toolbar_actions_bar_->DeleteActions();
// All views should be removed as part of ToolbarActionsBar::DeleteActions().
DCHECK(toolbar_action_views_.empty());
}
std::string BrowserActionsContainer::GetIdAt(size_t index) const {
return toolbar_action_views_[index]->view_controller()->GetId();
}
ToolbarActionView* BrowserActionsContainer::GetViewForId(
const std::string& id) {
for (const auto& view : toolbar_action_views_) {
if (view->view_controller()->GetId() == id)
return view.get();
}
return nullptr;
}
void BrowserActionsContainer::RefreshToolbarActionViews() {
toolbar_actions_bar_->Update();
}
size_t BrowserActionsContainer::VisibleBrowserActions() const {
size_t visible_actions = 0;
for (const auto& view : toolbar_action_views_) {
if (view->visible())
++visible_actions;
}
return visible_actions;
}
size_t BrowserActionsContainer::VisibleBrowserActionsAfterAnimation() const {
if (!animating())
return VisibleBrowserActions();
return WidthToIconCount(animation_target_size_);
}
bool BrowserActionsContainer::ShownInsideMenu() const {
return main_container_ != nullptr;
}
void BrowserActionsContainer::OnToolbarActionViewDragDone() {
toolbar_actions_bar_->OnDragEnded();
}
views::LabelButton* BrowserActionsContainer::GetOverflowReferenceView() {
return delegate_->GetOverflowReferenceView();
}
gfx::Size BrowserActionsContainer::GetToolbarActionSize() {
return toolbar_actions_bar_->GetViewSize();
}
void BrowserActionsContainer::AddViewForAction(
ToolbarActionViewController* view_controller,
size_t index) {
ToolbarActionView* view = new ToolbarActionView(view_controller, this);
toolbar_action_views_.insert(toolbar_action_views_.begin() + index,
base::WrapUnique(view));
AddChildViewAt(view, index);
PreferredSizeChanged();
}
void BrowserActionsContainer::RemoveViewForAction(
ToolbarActionViewController* action) {
std::unique_ptr<ToolbarActionView> view;
for (auto iter = toolbar_action_views_.begin();
iter != toolbar_action_views_.end(); ++iter) {
if ((*iter)->view_controller() == action) {
std::swap(view, *iter);
toolbar_action_views_.erase(iter);
break;
}
}
PreferredSizeChanged();
}
void BrowserActionsContainer::RemoveAllViews() {
toolbar_action_views_.clear();
PreferredSizeChanged();
}
void BrowserActionsContainer::Redraw(bool order_changed) {
if (!added_to_view_) {
// We don't want to redraw before the view has been fully added to the
// hierarchy.
return;
}
// Need to update the resize area because resizing is not allowed when the
// actions bar is highlighting.
UpdateResizeArea();
if (order_changed) {
// Run through the views and compare them to the desired order. If something
// is out of place, find the correct spot for it.
std::vector<ToolbarActionViewController*> actions =
toolbar_actions_bar_->GetActions();
for (int i = 0; i < static_cast<int>(actions.size()) - 1; ++i) {
if (actions[i] != toolbar_action_views_[i]->view_controller()) {
// Find where the correct view is (it's guaranteed to be after our
// current index, since everything up to this point is correct).
int j = i + 1;
while (actions[i] != toolbar_action_views_[j]->view_controller())
++j;
std::swap(toolbar_action_views_[i], toolbar_action_views_[j]);
// Also move the view in the child views vector.
ReorderChildView(toolbar_action_views_[i].get(), i);
}
}
}
if (separator_)
ReorderChildView(separator_, -1);
Layout();
}
void BrowserActionsContainer::ResizeAndAnimate(gfx::Tween::Type tween_type,
int target_width) {
// TODO(pbos): Make this method show N icons and derive target_width using
// GetWidthForIconCount.
if (toolbar_actions_bar_->WidthToIconCount(target_width) > 0)
target_width += GetSeparatorAreaWidth();
target_width += GetResizeAreaWidth();
if (resize_animation_ && !toolbar_actions_bar_->suppress_animation()) {
if (!ShownInsideMenu()) {
// Make sure we don't try to animate to wider than the allowed width.
base::Optional<int> max_width = delegate_->GetMaxBrowserActionsWidth();
if (max_width && target_width > max_width.value())
target_width = GetWidthForMaxWidth(max_width.value());
}
// Animate! We have to set the animation_target_size_ after calling Reset(),
// because that could end up calling AnimationEnded which clears the value.
resize_animation_->Reset();
resize_starting_width_ = width();
resize_animation_->SetTweenType(tween_type);
animation_target_size_ = target_width;
resize_animation_->Show();
} else {
if (resize_animation_)
resize_animation_->Reset();
animation_target_size_ = target_width;
AnimationEnded(resize_animation_.get());
}
}
int BrowserActionsContainer::GetWidth(GetWidthTime get_width_time) const {
// This call originates from ToolbarActionsBar which wants to know how much
// space is / will be used for action icons (excluding the separator).
const int target_width =
get_width_time == GET_WIDTH_AFTER_ANIMATION && animating()
? animation_target_size_
: width();
const int icon_area_width =
target_width - GetSeparatorAreaWidth() - GetResizeAreaWidth();
// This needs to be clamped to non-zero as ToolbarActionsBar::ResizeDelegate
// uses this value to distinguish between an empty bar without items and a bar
// that is showing no items.
// TODO(pbos): This is landed to fix to https://crbug.com/836182. Remove the
// need for this when ToolbarActionsBar and BrowserActionsContainer merges.
return std::max(toolbar_actions_bar_->GetMinimumWidth(), icon_area_width);
}
bool BrowserActionsContainer::IsAnimating() const {
return animating();
}
void BrowserActionsContainer::StopAnimating() {
animation_target_size_ = width();
resize_animation_->Reset();
}
void BrowserActionsContainer::ShowToolbarActionBubble(
std::unique_ptr<ToolbarActionsBarBubbleDelegate> controller) {
// The container shouldn't be asked to show a bubble if it's animating.
DCHECK(!animating());
DCHECK(!active_bubble_);
views::View* anchor_view = nullptr;
bool anchored_to_action_view = false;
if (!controller->GetAnchorActionId().empty()) {
ToolbarActionView* action_view =
GetViewForId(controller->GetAnchorActionId());
if (action_view) {
anchor_view =
action_view->visible() ? action_view : GetOverflowReferenceView();
anchored_to_action_view = true;
} else {
anchor_view = BrowserView::GetBrowserViewForBrowser(browser_)
->toolbar_button_provider()
->GetAppMenuButton();
}
} else {
anchor_view = this;
}
ToolbarActionsBarBubbleViews* bubble = new ToolbarActionsBarBubbleViews(
anchor_view, gfx::Point(), anchored_to_action_view,
std::move(controller));
active_bubble_ = bubble;
views::BubbleDialogDelegateView::CreateBubble(bubble);
bubble->GetWidget()->AddObserver(this);
bubble->Show();
}
bool BrowserActionsContainer::CloseOverflowMenuIfOpen() {
AppMenuButton* app_menu_button =
BrowserView::GetBrowserViewForBrowser(browser_)
->toolbar_button_provider()
->GetAppMenuButton();
if (!app_menu_button || !app_menu_button->IsMenuShowing())
return false;
app_menu_button->CloseMenu();
return true;
}
void BrowserActionsContainer::OnWidgetClosing(views::Widget* widget) {
ClearActiveBubble(widget);
}
void BrowserActionsContainer::OnWidgetDestroying(views::Widget* widget) {
ClearActiveBubble(widget);
}
int BrowserActionsContainer::GetWidthForMaxWidth(int max_width) const {
DCHECK_GE(max_width, 0);
int preferred_width = GetPreferredSize().width();
if (preferred_width > max_width) {
// If we're trying to be nonzero width, we should make sure we at least ask
// for enough space to show the resize handle (if there are no icons, we
// will ask for a width of zero so it won't matter).
preferred_width =
std::max(GetResizeAreaWidth(), GetWidthForIconCount(WidthToIconCount(
max_width - GetResizeAreaWidth())));
}
return preferred_width;
}
// static
views::FlexRule BrowserActionsContainer::GetFlexRule() {
// We only want to flex to widths which are integer multiples of the icon
// size, plus the size of the drag handle. The one exception is if there are
// no extensions at all.
return base::BindRepeating(
[](const views::View* view, const views::SizeBounds& maximum_size) {
const BrowserActionsContainer* browser_actions =
static_cast<const BrowserActionsContainer*>(view);
gfx::Size preferred_size = browser_actions->GetPreferredSize();
if (maximum_size.width()) {
int width;
if (browser_actions->resizing() || browser_actions->animating()) {
// When there are actions present, the floor on the size of the
// browser actions bar should be the resize handle.
const int min_width = browser_actions->num_toolbar_actions() == 0
? 0
: browser_actions->GetResizeAreaWidth();
// The ceiling on the value is the lesser of the preferred and
// available size.
width = std::max(min_width, std::min(preferred_size.width(),
*maximum_size.width()));
} else {
// When not animating or resizing, the desired width should always
// be based on the number of icons that can be displayed.
width = browser_actions->GetWidthForMaxWidth(*maximum_size.width());
}
preferred_size =
gfx::Size(width, browser_actions->GetHeightForWidth(width));
}
return preferred_size;
});
}
void BrowserActionsContainer::SetSeparatorColor(SkColor color) {
if (separator_)
separator_->SetColor(color);
}
gfx::Size BrowserActionsContainer::CalculatePreferredSize() const {
if (ShownInsideMenu())
return toolbar_actions_bar_->GetFullSize();
// If there are no actions to show, then don't show the container at all.
if (toolbar_action_views_.empty())
return gfx::Size();
int preferred_width;
if (resize_starting_width_) {
// When resizing, preferred width is the starting width - resize amount.
preferred_width = *resize_starting_width_ - resize_amount_;
} else {
// Otherwise, use the normal preferred width.
preferred_width =
GetResizeAreaWidth() + toolbar_actions_bar_->GetFullSize().width();
if (toolbar_actions_bar_->GetIconCount() > 0)
preferred_width += GetSeparatorAreaWidth();
}
// The view should never be resized past the largest size or smaller than the
// empty width (including drag handle), clamp preferred size to reflect this.
preferred_width = base::ClampToRange(preferred_width, GetResizeAreaWidth(),
GetWidthWithAllActionsVisible());
return gfx::Size(preferred_width,
toolbar_actions_bar_->GetViewSize().height());
}
int BrowserActionsContainer::GetHeightForWidth(int width) const {
if (ShownInsideMenu())
toolbar_actions_bar_->SetOverflowRowWidth(width);
return GetPreferredSize().height();
}
gfx::Size BrowserActionsContainer::GetMinimumSize() const {
DCHECK(interactive_);
return gfx::Size(GetResizeAreaWidth(),
toolbar_actions_bar_->GetViewSize().height());
}
void BrowserActionsContainer::Layout() {
if (toolbar_actions_bar()->suppress_layout())
return;
if (toolbar_action_views_.empty()) {
SetVisible(false);
return;
}
SetVisible(true);
if (resize_area_)
resize_area_->SetBounds(0, 0, GetResizeAreaWidth(), height());
// The range of visible icons, from start_index (inclusive) to end_index
// (exclusive).
size_t start_index = toolbar_actions_bar()->GetStartIndexInBounds();
size_t end_index = toolbar_actions_bar()->GetEndIndexInBounds();
// Now draw the icons for the actions in the available space. Once all the
// variables are in place, the layout works equally well for the main and
// overflow container.
for (size_t i = 0u; i < toolbar_action_views_.size(); ++i) {
ToolbarActionView* view = toolbar_action_views_[i].get();
if (i < start_index || i >= end_index) {
view->SetVisible(false);
} else {
gfx::Rect bounds = toolbar_actions_bar()->GetFrameForIndex(i);
// Offset all icons by GetResizeAreaWidth() so that they start on the
// resize area's right edge. ToolbarActionsBar is not aware of the
// separate resize area.
// TODO(pbos): Remove this workaround when the files merge.
bounds.set_x(bounds.x() + GetResizeAreaWidth());
// Vertically center the icons if the available height is not enough.
// TODO(https://889745): Remove the possibility of there not being enough
// available height.
if (bounds.height() > height())
bounds.set_y((height() - bounds.height()) / 2);
view->SetBoundsRect(bounds);
view->SetVisible(true);
// TODO(corising): Move setting background to
// ToolbarActionsBar::OnToolbarHighlightModeChanged when the files merge.
if (!ShownInsideMenu() && (toolbar_actions_bar()->is_highlighting() !=
(view->background() != nullptr))) {
// Sets background to reflect whether the item is being highlighted.
const gfx::Insets bg_insets(
(height() - GetLayoutConstant(LOCATION_BAR_HEIGHT)) / 2);
const int corner_radius = height() / 2;
const SkColor bg_color = SkColorSetA(view->GetInkDropBaseColor(),
kToolbarButtonBackgroundAlpha);
view->SetBackground(
toolbar_actions_bar()->is_highlighting()
? views::CreateBackgroundFromPainter(
views::Painter::CreateSolidRoundRectPainter(
bg_color, corner_radius, bg_insets))
: nullptr);
}
}
}
if (separator_) {
separator_->SetSize(gfx::Size(views::Separator::kThickness,
GetLayoutConstant(LOCATION_BAR_ICON_SIZE)));
if (width() < GetResizeAreaWidth() + GetSeparatorAreaWidth()) {
separator_->SetVisible(false);
} else {
// Position separator_ in the center of the separator area.
separator_->SetPosition(gfx::Point(
width() - GetSeparatorAreaWidth() / 2 - separator_->width(),
(height() - separator_->height()) / 2));
separator_->SetVisible(true);
}
}
}
bool BrowserActionsContainer::GetDropFormats(
int* formats,
std::set<ui::ClipboardFormatType>* format_types) {
return BrowserActionDragData::GetDropFormats(format_types);
}
bool BrowserActionsContainer::AreDropTypesRequired() {
return BrowserActionDragData::AreDropTypesRequired();
}
bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) {
return interactive_ &&
BrowserActionDragData::CanDrop(data, browser_->profile());
}
int BrowserActionsContainer::OnDragUpdated(
const ui::DropTargetEvent& event) {
size_t row_index = 0;
size_t before_icon_in_row = 0;
// If there are no visible actions (such as when dragging an icon to an empty
// overflow/main container), then 0, 0 for row, column is correct.
if (VisibleBrowserActions() != 0) {
// Figure out where to display the indicator.
// First, since we want to switch from displaying the indicator before an
// icon to after it when the event passes the midpoint of the icon, add
// (icon width / 2) and divide by the icon width. This will convert the
// event coordinate into the index of the icon we want to display the
// indicator before. We also mirror the event.x() so that our calculations
// are consistent with left-to-right.
const auto size = toolbar_actions_bar_->GetViewSize();
const int offset_into_icon_area = GetMirroredXInView(event.x()) -
GetResizeAreaWidth() + (size.width() / 2);
const int before_icon_unclamped =
toolbar_actions_bar_->WidthToIconCount(offset_into_icon_area);
// We need to figure out how many icons are visible on the relevant row.
// In the main container, this will just be the visible actions.
int visible_icons_on_row = VisibleBrowserActionsAfterAnimation();
if (ShownInsideMenu()) {
// Next, figure out what row we're on.
const int element_padding = GetLayoutConstant(TOOLBAR_ELEMENT_PADDING);
row_index =
(event.y() + element_padding) / (size.height() + element_padding);
const int icons_per_row = platform_settings().icons_per_overflow_menu_row;
// If this is the final row of the overflow, then this is the remainder of
// visible icons. Otherwise, it's a full row (kIconsPerRow).
visible_icons_on_row =
row_index ==
static_cast<size_t>(visible_icons_on_row / icons_per_row) ?
visible_icons_on_row % icons_per_row : icons_per_row;
}
// Because the user can drag outside the container bounds, we need to clamp
// to the valid range. Note that the maximum allowable value is (num icons),
// not (num icons - 1), because we represent the indicator being past the
// last icon as being "before the (last + 1) icon".
before_icon_in_row =
base::ClampToRange(before_icon_unclamped, 0, visible_icons_on_row);
}
if (!drop_position_.get() ||
!(drop_position_->row == row_index &&
drop_position_->icon_in_row == before_icon_in_row)) {
drop_position_ =
std::make_unique<DropPosition>(row_index, before_icon_in_row);
SchedulePaint();
}
return ui::DragDropTypes::DRAG_MOVE;
}
void BrowserActionsContainer::OnDragExited() {
drop_position_.reset();
SchedulePaint();
}
int BrowserActionsContainer::OnPerformDrop(
const ui::DropTargetEvent& event) {
BrowserActionDragData data;
if (!data.Read(event.data()))
return ui::DragDropTypes::DRAG_NONE;
// Make sure we have the same view as we started with.
DCHECK_EQ(GetIdAt(data.index()), data.id());
size_t i = GetDropPositionIndex();
// |i| now points to the item to the right of the drop indicator*, which is
// correct when dragging an icon to the left. When dragging to the right,
// however, we want the icon being dragged to get the index of the item to
// the left of the drop indicator, so we subtract one.
// * Well, it can also point to the end, but not when dragging to the left. :)
if (i > data.index())
--i;
ToolbarActionsBar::DragType drag_type = ToolbarActionsBar::DRAG_TO_SAME;
if (!toolbar_action_views_[data.index()]->visible())
drag_type = ShownInsideMenu() ? ToolbarActionsBar::DRAG_TO_OVERFLOW :
ToolbarActionsBar::DRAG_TO_MAIN;
toolbar_actions_bar_->OnDragDrop(data.index(), i, drag_type);
OnDragExited(); // Perform clean up after dragging.
return ui::DragDropTypes::DRAG_MOVE;
}
void BrowserActionsContainer::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kGroup;
node_data->SetName(l10n_util::GetStringUTF8(IDS_ACCNAME_EXTENSIONS));
}
void BrowserActionsContainer::WriteDragDataForView(View* sender,
const gfx::Point& press_pt,
OSExchangeData* data) {
DCHECK(data);
auto it =
std::find_if(toolbar_action_views_.cbegin(), toolbar_action_views_.cend(),
[sender](const std::unique_ptr<ToolbarActionView>& ptr) {
return ptr.get() == sender;
});
DCHECK(it != toolbar_action_views_.cend());
size_t index = it - toolbar_action_views_.cbegin();
toolbar_actions_bar_->OnDragStarted(index);
ToolbarActionViewController* view_controller = (*it)->view_controller();
data->provider().SetDragImage(
view_controller
->GetIcon(GetCurrentWebContents(),
toolbar_actions_bar_->GetViewSize())
.AsImageSkia(),
press_pt.OffsetFromOrigin());
// Fill in the remaining info.
BrowserActionDragData drag_data(view_controller->GetId(), index);
drag_data.Write(browser_->profile(), data);
}
int BrowserActionsContainer::GetDragOperationsForView(View* sender,
const gfx::Point& p) {
return ui::DragDropTypes::DRAG_MOVE;
}
bool BrowserActionsContainer::CanStartDragForView(View* sender,
const gfx::Point& press_pt,
const gfx::Point& p) {
// We don't allow dragging while we're highlighting.
return interactive_ && !toolbar_actions_bar_->is_highlighting();
}
void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) {
// We don't allow resize while the toolbar is highlighting a subset of
// actions, since this is a temporary and entirely browser-driven sequence in
// order to warn the user about potentially dangerous items.
// We also don't allow resize when the bar is already animating, since we
// don't want two competing size changes.
if (toolbar_actions_bar_->is_highlighting() || animating())
return;
// If this is the start of the resize gesture, initialize the starting
// width.
if (!resize_starting_width_)
resize_starting_width_ = width();
resize_amount_ = resize_amount;
if (!done_resizing) {
PreferredSizeChanged();
return;
}
// 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, and then
// animate to the nearest icon count size if necessary (which may be 0).
int icon_area_width =
std::max(toolbar_actions_bar_->GetMinimumWidth(),
CalculatePreferredSize().width() - GetSeparatorAreaWidth() -
GetResizeAreaWidth());
// As we're done resizing, reset the starting width to reflect this after
// calculating the final size based on it.
resize_starting_width_.reset();
toolbar_actions_bar_->OnResizeComplete(icon_area_width);
}
void BrowserActionsContainer::OnBoundsChanged(
const gfx::Rect& previous_bounds) {
// When bounds change, it's possible that the amount of space available to the
// view changes as well. If the amount of space is not enough to fit a single
// icon, the resize handle should be disabled.
UpdateResizeArea();
}
void BrowserActionsContainer::AnimationProgressed(
const gfx::Animation* animation) {
DCHECK_EQ(resize_animation_.get(), animation);
DCHECK(resize_starting_width_);
resize_amount_ =
static_cast<int>(resize_animation_->GetCurrentValue() *
(*resize_starting_width_ - animation_target_size_));
PreferredSizeChanged();
}
void BrowserActionsContainer::AnimationCanceled(
const gfx::Animation* animation) {
AnimationEnded(animation);
}
void BrowserActionsContainer::AnimationEnded(const gfx::Animation* animation) {
animation_target_size_ = 0;
resize_amount_ = 0;
resize_starting_width_.reset();
PreferredSizeChanged();
toolbar_actions_bar_->OnAnimationEnded();
}
content::WebContents* BrowserActionsContainer::GetCurrentWebContents() {
return browser_->tab_strip_model()->GetActiveWebContents();
}
void BrowserActionsContainer::OnPaint(gfx::Canvas* canvas) {
// TODO(sky/glen): Instead of using a drop indicator, animate the icons while
// dragging (like we do for tab dragging).
if (drop_position_) {
// The two-pixel width drop indicator.
constexpr int kDropIndicatorWidth = 2;
const size_t i = GetDropPositionIndex();
const gfx::Rect frame = toolbar_actions_bar_->GetFrameForIndex(i);
gfx::Rect indicator_bounds = GetMirroredRect(
gfx::Rect(GetResizeAreaWidth() + frame.x() -
GetLayoutConstant(TOOLBAR_ELEMENT_PADDING) / 2 -
kDropIndicatorWidth / 2,
frame.y(), kDropIndicatorWidth, frame.height()));
// Clamp the indicator to the view bounds so that heading / trailing markers
// don't paint outside the controller. It's OK if they paint over the resize
// area or separator (but the in-menu container has neither).
indicator_bounds.set_x(base::ClampToRange(
indicator_bounds.x(), 0, width() - indicator_bounds.width()));
// Color of the drop indicator.
// Always get the theme provider of the browser widget, since if this view
// is shown within the menu widget, GetThemeProvider() would return the
// ui::DefaultThemeProvider which doesn't return the correct colors.
// https://crbug.com/831510.
const ui::ThemeProvider* theme_provider =
BrowserView::GetBrowserViewForBrowser(browser_)
->frame()
->GetThemeProvider();
const SkColor drop_indicator_color = color_utils::GetColorWithMaxContrast(
theme_provider->GetColor(ThemeProperties::COLOR_TOOLBAR));
canvas->FillRect(indicator_bounds, drop_indicator_color);
}
}
void BrowserActionsContainer::ViewHierarchyChanged(
const views::ViewHierarchyChangedDetails& details) {
if (!toolbar_actions_bar_->enabled())
return;
if (details.is_add && details.child == this) {
// Initial toolbar button creation and placement in the widget hierarchy.
// We do this here instead of in the constructor because adding views
// calls Layout on the Toolbar, which needs this object to be constructed
// before its Layout function is called.
toolbar_actions_bar_->CreateActions();
added_to_view_ = true;
}
}
void BrowserActionsContainer::ClearActiveBubble(views::Widget* widget) {
DCHECK(active_bubble_);
DCHECK_EQ(active_bubble_->GetWidget(), widget);
widget->RemoveObserver(this);
active_bubble_ = nullptr;
toolbar_actions_bar_->OnBubbleClosed();
}
size_t BrowserActionsContainer::WidthToIconCount(int width) const {
// TODO(pbos): Ideally we would just calculate the icon count ourselves, but
// until that point we need to subtract the separator width before asking
// |toolbar_actions_bar_| how many icons to show, so that it doesn't try to
// place an icon over the separator.
return toolbar_actions_bar_->WidthToIconCount(width -
GetSeparatorAreaWidth());
}
int BrowserActionsContainer::GetWidthForIconCount(size_t num_icons) const {
if (num_icons == 0)
return 0;
return GetResizeAreaWidth() + GetSeparatorAreaWidth() +
toolbar_actions_bar_->IconCountToWidth(num_icons);
}
int BrowserActionsContainer::GetWidthWithAllActionsVisible() const {
return GetWidthForIconCount(
toolbar_actions_bar_->toolbar_actions_unordered().size());
}
size_t BrowserActionsContainer::GetDropPositionIndex() const {
size_t i =
drop_position_->row * platform_settings().icons_per_overflow_menu_row +
drop_position_->icon_in_row;
if (ShownInsideMenu())
i += main_container_->VisibleBrowserActionsAfterAnimation();
return i;
}
int BrowserActionsContainer::GetResizeAreaWidth() const {
if (!resize_area_)
return 0;
return platform_settings().item_spacing;
}
int BrowserActionsContainer::GetSeparatorAreaWidth() const {
// The separator is not applicable to the app menu.
if (ShownInsideMenu())
return 0;
return 2 * GetLayoutConstant(TOOLBAR_STANDARD_SPACING) +
views::Separator::kThickness;
}
void BrowserActionsContainer::UpdateResizeArea() {
if (!resize_area_)
return;
const base::Optional<int> max_width = delegate_->GetMaxBrowserActionsWidth();
const bool enable_resize_area =
interactive_ && !toolbar_actions_bar()->is_highlighting() &&
(!max_width || *max_width >= GetWidthForIconCount(1));
resize_area_->SetEnabled(enable_resize_area);
}