blob: e6d908209bcf40e1ce8c87a096296613073d133f [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// 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/pinned_toolbar_actions_container.h"
#include <algorithm>
#include <iterator>
#include <memory>
#include <string>
#include <type_traits>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "base/scoped_observation.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_actions.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/toolbar/toolbar_pref_names.h"
#include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
#include "chrome/browser/ui/views/side_panel/side_panel_enums.h"
#include "chrome/browser/ui/views/side_panel/side_panel_util.h"
#include "chrome/browser/ui/views/toolbar/pinned_action_toolbar_button.h"
#include "chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container_layout.h"
#include "chrome/common/chrome_features.h"
#include "chrome/grit/generated_resources.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "ui/actions/action_id.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/dialog_model_menu_model_adapter.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/text_constants.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/menus/simple_menu_model.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/actions/action_view_controller.h"
#include "ui/views/cascading_property.h"
#include "ui/views/controls/button/button_controller.h"
#include "ui/views/layout/animating_layout_manager.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/layout/layout_manager_base.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/view_class_properties.h"
namespace {
void RecordPinnedActionsCount(int count) {
base::UmaHistogramCounts100("Browser.Actions.PinnedActionsCount", count);
}
} // namespace
///////////////////////////////////////////////////////////////////////////////
// PinnedToolbarActionsContainer::DropInfo:
struct PinnedToolbarActionsContainer::DropInfo {
explicit DropInfo(actions::ActionId action_id, size_t index);
// The id for the action being dragged.
actions::ActionId action_id;
// The (0-indexed) index the action will be dropped.
size_t index;
};
PinnedToolbarActionsContainer::DropInfo::DropInfo(actions::ActionId action_id,
size_t index)
: action_id(action_id), index(index) {}
///////////////////////////////////////////////////////////////////////////////
// PinnedToolbarActionsContainer::BrowserObserver:
// Observes the browser and when it is going to go away, clears out the pointers
// in the container so we don't try to dereference them during destruction.
class PinnedToolbarActionsContainer::BrowserObserver
: public views::ViewObserver {
public:
BrowserObserver(PinnedToolbarActionsContainer& owner,
BrowserView* browser_view)
: owner_(owner) {
observation_.Observe(browser_view);
}
void OnViewHierarchyWillBeDeleted(views::View*) override {
observation_.Reset();
owner_->browser_view_ = nullptr;
owner_->button_provider_ = nullptr;
}
private:
raw_ref<PinnedToolbarActionsContainer> owner_;
base::ScopedObservation<views::View, views::ViewObserver> observation_{this};
};
///////////////////////////////////////////////////////////////////////////////
// PinnedToolbarActionsContainer:
PinnedToolbarActionsContainer::PinnedToolbarActionsContainer(
BrowserView* browser_view,
ToolbarButtonProvider* button_provider)
: ToolbarIconContainerView(/*uses_highlight=*/false,
/*use_default_target_layout=*/false),
browser_observer_(std::make_unique<BrowserObserver>(*this, browser_view)),
browser_view_(browser_view),
button_provider_(button_provider),
model_(PinnedToolbarActionsModel::Get(browser_view->GetProfile())) {
SetPaintToLayer();
SetProperty(views::kElementIdentifierKey,
kPinnedToolbarActionsContainerElementId);
// So we only get enter/exit messages when the mouse enters/exits the whole
// container, even if it is entering/exiting a specific toolbar pinned
// button view, too.
SetNotifyEnterExitOnChild(true);
model_observation_.Observe(model_.get());
const views::FlexSpecification hide_icon_flex_specification =
views::FlexSpecification(views::LayoutOrientation::kHorizontal,
views::MinimumFlexSizeRule::kPreferredSnapToZero,
views::MaximumFlexSizeRule::kPreferred)
.WithWeight(0);
PinnedToolbarActionsContainerLayout* layout =
GetAnimatingLayoutManager()->SetTargetLayoutManager(
std::make_unique<PinnedToolbarActionsContainerLayout>());
// Set the interior margins to ensure the default margins are negated if there
// is a button at the end of the container or if the divider is at the end
// (which has a different margin than the default). This ensures the container
// is the same size regardless of where and if the divider is in the
// container.
layout->SetInteriorMargin(gfx::Insets::TLBR(
0, 0, 0, -GetLayoutConstant(TOOLBAR_ICON_DEFAULT_MARGIN)));
// Animations.
GetAnimatingLayoutManager()->SetDefaultFadeMode(
views::AnimatingLayoutManager::FadeInOutMode::
kFadeAndSlideFromTrailingEdge);
GetAnimatingLayoutManager()->SetTweenType(
gfx::Tween::Type::FAST_OUT_SLOW_IN_3);
GetAnimatingLayoutManager()->SetAnimationDuration(base::Milliseconds(300));
GetAnimatingLayoutManager()->SetOpacityTweenType(gfx::Tween::Type::LINEAR);
GetAnimatingLayoutManager()->SetOpacityAnimationDuration(
base::Milliseconds(200));
// Create the toolbar divider.
std::unique_ptr<views::View> toolbar_divider =
std::make_unique<views::View>();
toolbar_divider->SetProperty(views::kElementIdentifierKey,
kPinnedToolbarActionsContainerDividerElementId);
toolbar_divider->SetPreferredSize(
gfx::Size(GetLayoutConstant(TOOLBAR_DIVIDER_WIDTH),
GetLayoutConstant(TOOLBAR_DIVIDER_HEIGHT)));
// The divider only exists if there are pinned buttons, which have padding on
// the right. Remove that amount of padding to compensate.
toolbar_divider->SetProperty(
views::kMarginsKey,
gfx::Insets::TLBR(0,
GetLayoutConstant(TOOLBAR_DIVIDER_SPACING) -
GetLayoutConstant(TOOLBAR_ICON_DEFAULT_MARGIN),
0, GetLayoutConstant(TOOLBAR_DIVIDER_SPACING)));
toolbar_divider_ = AddChildView(std::move(toolbar_divider));
// Initialize the pinned action buttons.
action_view_controller_ = std::make_unique<views::ActionViewController>();
model_->MaybeMigrateExistingPinnedStates();
UpdateViews();
}
int PinnedToolbarActionsContainer::CalculatePoppedOutButtonsWidth() {
if (popped_out_buttons_.empty()) {
return 0;
}
int popped_out_buttons_width = 0;
for (PinnedActionToolbarButton* const popped_button : popped_out_buttons_) {
popped_out_buttons_width += popped_button->GetPreferredSize().width();
}
popped_out_buttons_width += (popped_out_buttons_.size() - 1) *
(GetLayoutConstant(TOOLBAR_ICON_DEFAULT_MARGIN));
return popped_out_buttons_width;
}
gfx::Size PinnedToolbarActionsContainer::DefaultFlexRule(
const views::SizeBounds& size_bounds) {
// Get the default flex rule
auto default_flex_rule = GetAnimatingLayoutManager()->GetDefaultFlexRule();
// Calculate the size according to the default flex rule
return default_flex_rule.Run(this, size_bounds);
}
PinnedToolbarActionsContainer::~PinnedToolbarActionsContainer() = default;
void PinnedToolbarActionsContainer::UpdateActionState(actions::ActionId id,
bool is_active) {
auto* button = GetPinnedButtonFor(id);
bool pinned = button != nullptr;
// Get or create popped out button if not pinned.
if (!pinned) {
button = GetPoppedOutButtonFor(id);
if (!button && is_active) {
button = AddPoppedOutButtonFor(id);
}
}
// If the button doesn't exist, do nothing. This could happen if |is_active|
// is false and there is no existing pinned out popped out button for the
// |id|.
if (!button) {
return;
}
// Update button highlight and force visibility if the button is active.
if (is_active) {
button->AddHighlight();
} else {
button->ResetHighlight();
}
if (!is_active) {
MaybeRemovePoppedOutButtonFor(id);
}
InvalidateLayout();
}
void PinnedToolbarActionsContainer::ShowActionEphemerallyInToolbar(
actions::ActionId id,
bool show) {
auto* button = GetButtonFor(id);
// If the button doesn't exist and shouldn't be shown, do nothing.
if (!button && !show) {
return;
}
// Create the button if it doesn't exist.
if (!button) {
button = AddPoppedOutButtonFor(id);
}
button->SetShouldShowEphemerallyInToolbar(show);
if (!show) {
MaybeRemovePoppedOutButtonFor(id);
}
}
void PinnedToolbarActionsContainer::UpdatePinnedStateAndAnnounce(
actions::ActionId id,
bool pin) {
// If already in the desired state or not pinnable, do nothing.
if (pin == IsActionPinned(id) ||
!GetActionItemFor(id)->GetProperty(actions::kActionItemPinnableKey)) {
return;
}
GetViewAccessibility().AnnounceText(l10n_util::GetStringUTF16(
pin ? IDS_TOOLBAR_BUTTON_PINNED : IDS_TOOLBAR_BUTTON_UNPINNED));
model_->UpdatePinnedState(id, pin);
}
void PinnedToolbarActionsContainer::MovePinnedActionBy(actions::ActionId id,
int delta) {
DCHECK(IsActionPinned(id));
const auto& pinned_action_ids = model_->PinnedActionIds();
auto iter = std::ranges::find(pinned_action_ids, id);
CHECK(iter != pinned_action_ids.end());
int current_index = std::distance(pinned_action_ids.begin(), iter);
int target_index = current_index + delta;
if (target_index >= 0 && target_index < int(pinned_action_ids.size())) {
model_->MovePinnedAction(id, target_index);
}
}
void PinnedToolbarActionsContainer::UpdateAllIcons() {
for (PinnedActionToolbarButton* const pinned_button : pinned_buttons_) {
pinned_button->UpdateIcon();
}
for (PinnedActionToolbarButton* const popped_out_button :
popped_out_buttons_) {
popped_out_button->UpdateIcon();
}
}
void PinnedToolbarActionsContainer::OnThemeChanged() {
const SkColor toolbar_divider_color =
GetColorProvider()->GetColor(kColorToolbarExtensionSeparatorEnabled);
toolbar_divider_->SetBackground(views::CreateRoundedRectBackground(
toolbar_divider_color, GetLayoutConstant(TOOLBAR_DIVIDER_CORNER_RADIUS)));
ToolbarIconContainerView::OnThemeChanged();
}
void PinnedToolbarActionsContainer::AddedToWidget() {
OnThemeChanged();
}
bool PinnedToolbarActionsContainer::GetDropFormats(
int* formats,
std::set<ui::ClipboardFormatType>* format_types) {
return BrowserActionDragData::GetDropFormats(format_types);
}
bool PinnedToolbarActionsContainer::AreDropTypesRequired() {
return BrowserActionDragData::AreDropTypesRequired();
}
bool PinnedToolbarActionsContainer::CanDrop(const OSExchangeData& data) {
return BrowserActionDragData::CanDrop(data,
browser_view_->browser()->profile());
}
void PinnedToolbarActionsContainer::OnDragEntered(
const ui::DropTargetEvent& event) {
drop_weak_ptr_factory_.InvalidateWeakPtrs();
}
int PinnedToolbarActionsContainer::OnDragUpdated(
const ui::DropTargetEvent& event) {
BrowserActionDragData data;
if (!data.Read(event.data())) {
return ui::DragDropTypes::DRAG_NONE;
}
// Check if the action item for the dragged icon is pinned (e.g. an action
// item could be unpinned through a sync update while dragging its icon).
std::optional<actions::ActionId> action_id =
actions::ActionIdMap::StringToActionId(data.id());
if (!action_id.has_value() || !model_->Contains(*action_id)) {
return ui::DragDropTypes::DRAG_NONE;
}
size_t before_icon = 0;
// Figure out where to display the icon during dragging transition.
// First, since we want to update the dragged action's position from before
// an icon to after it when the event passes the midpoint between two icons.
// This will convert the event coordinate into the index of the icon we want
// to display the dragged action before. We also mirror the event.x() so
// that our calculations are consistent with left-to-right.
// Note we are not including popped-out icons here, only the pinned actions.
const int offset_into_icon_area = GetMirroredXInView(event.x());
const size_t before_icon_unclamped = WidthToIconCount(offset_into_icon_area);
const size_t visible_pinned_icons = std::ranges::count_if(
pinned_buttons_,
[](PinnedActionToolbarButton* button) { return button->GetVisible(); });
const size_t button_offset = pinned_buttons_.size() - visible_pinned_icons;
// Because the user can drag outside the container bounds, we need to clamp
// to the valid range.
before_icon =
std::min(before_icon_unclamped, visible_pinned_icons - 1) + button_offset;
if (!drop_info_.get() || drop_info_->index != before_icon) {
drop_info_ = std::make_unique<DropInfo>(
*actions::ActionIdMap::StringToActionId(data.id()), before_icon);
if (auto* button = GetPinnedButtonFor(drop_info_->action_id)) {
button->SetIconVisibility(false);
}
ReorderViews();
}
return ui::DragDropTypes::DRAG_MOVE;
}
void PinnedToolbarActionsContainer::OnDragExited() {
if (!drop_info_) {
return;
}
const actions::ActionId dragged_action_id = drop_info_->action_id;
drop_info_.reset();
DragDropCleanup(dragged_action_id);
}
views::View::DropCallback PinnedToolbarActionsContainer::GetDropCallback(
const ui::DropTargetEvent& event) {
BrowserActionDragData data;
if (!data.Read(event.data())) {
return base::NullCallback();
}
auto action_id = drop_info_->action_id;
auto index = drop_info_->index;
drop_info_.reset();
base::ScopedClosureRunner cleanup(
base::BindOnce(&PinnedToolbarActionsContainer::DragDropCleanup,
weak_ptr_factory_.GetWeakPtr(), action_id));
return base::BindOnce(&PinnedToolbarActionsContainer::MovePinnedAction,
drop_weak_ptr_factory_.GetWeakPtr(), action_id, index,
std::move(cleanup));
}
void PinnedToolbarActionsContainer::OnActionAddedLocally(actions::ActionId id) {
RecordPinnedActionsCount(model_->PinnedActionIds().size());
}
void PinnedToolbarActionsContainer::OnActionRemovedLocally(
actions::ActionId id) {
RecordPinnedActionsCount(model_->PinnedActionIds().size());
}
void PinnedToolbarActionsContainer::OnActionsChanged() {
UpdateViews();
drop_weak_ptr_factory_.InvalidateWeakPtrs();
}
void PinnedToolbarActionsContainer::WriteDragDataForView(
View* sender,
const gfx::Point& press_pt,
ui::OSExchangeData* data) {
DCHECK(data);
const auto iter = std::ranges::find(pinned_buttons_, sender);
CHECK(iter != pinned_buttons_.end());
auto* button = (*iter).get();
ui::ImageModel icon =
ui::ImageModel::FromImageSkia(button->GetImage(button->GetState()));
data->provider().SetDragImage(icon.Rasterize(GetColorProvider()),
press_pt.OffsetFromOrigin());
// Fill in the remaining info.
size_t index = iter - pinned_buttons_.begin();
BrowserActionDragData drag_data(
*actions::ActionIdMap::ActionIdToString(button->GetActionId()), index);
drag_data.Write(browser_view_->GetProfile(), data);
}
int PinnedToolbarActionsContainer::GetDragOperationsForView(
View* sender,
const gfx::Point& p) {
return browser_view_->GetProfile()->IsOffTheRecord()
? ui::DragDropTypes::DRAG_NONE
: ui::DragDropTypes::DRAG_MOVE;
}
bool PinnedToolbarActionsContainer::CanStartDragForView(
View* sender,
const gfx::Point& press_pt,
const gfx::Point& p) {
// We don't allow dragging buttons that aren't pinned, or if
// the profile is incognito (to avoid changing state from an incognito
// window).
const auto iter = std::ranges::find(pinned_buttons_, sender);
return iter != pinned_buttons_.end() &&
!browser_view_->GetProfile()->IsOffTheRecord();
}
actions::ActionItem* PinnedToolbarActionsContainer::GetActionItemFor(
actions::ActionId id) {
if (!browser_view_) {
return nullptr;
}
return actions::ActionManager::Get().FindAction(
id, browser_view_->browser()->browser_actions()->root_action_item());
}
PinnedActionToolbarButton* PinnedToolbarActionsContainer::AddPoppedOutButtonFor(
actions::ActionId id) {
CHECK(GetActionItemFor(id));
auto popped_out_button = CreateOrGetButtonForAction(id);
auto* button = popped_out_button.get();
popped_out_buttons_.push_back(AddChildView(std::move(popped_out_button)));
// If the added button was previously a cached permanent button then the icon
// may not be correct for the current state (i.e. touch ui).
if (button->IsPermanent()) {
button->UpdateIcon();
}
ReorderViews();
return button;
}
void PinnedToolbarActionsContainer::MaybeRemovePoppedOutButtonFor(
actions::ActionId id) {
const auto iter = std::ranges::find(
popped_out_buttons_, id,
[](PinnedActionToolbarButton* button) { return button->GetActionId(); });
if (iter == popped_out_buttons_.end() ||
ShouldRemainPoppedOutInToolbar(*iter)) {
return;
}
GetAnimatingLayoutManager()->FadeOut(*iter);
GetAnimatingLayoutManager()->PostOrQueueAction(
base::BindOnce(&PinnedToolbarActionsContainer::RemoveButton,
weak_ptr_factory_.GetWeakPtr(), *iter));
popped_out_buttons_.erase(iter);
ReorderViews();
}
PinnedActionToolbarButton*
PinnedToolbarActionsContainer::CreatePermanentButtonFor(actions::ActionId id) {
PinnedActionToolbarButton* existing_button = GetButtonFor(id);
if (existing_button) {
existing_button->SetIsPermanent();
return existing_button;
} else {
std::unique_ptr<PinnedActionToolbarButton> new_button =
CreateOrGetButtonForAction(id);
new_button->SetIsPermanent();
PinnedActionToolbarButton* new_button_raw_ptr = new_button.get();
permanent_buttons_.push_back(std::move(new_button));
return new_button_raw_ptr;
}
}
gfx::Size PinnedToolbarActionsContainer::GetDefaultButtonSize() const {
return button_provider_ ? button_provider_->GetToolbarButtonSize()
: gfx::Size();
}
void PinnedToolbarActionsContainer::AddPinnedActionButtonFor(
actions::ActionId id) {
// Pinned buttons shouldn't appear in web apps or browsers without a tabstrip
// (like popups).
if (auto* browser = browser_view_->browser();
browser && (browser->app_controller() ||
!browser->SupportsWindowFeature(
Browser::WindowFeature::kFeatureTabStrip))) {
return;
}
actions::ActionItem* action_item = GetActionItemFor(id);
// If the action item doesn't exist (i.e. a new id synced from an
// update-to-date device to an out-of-date device) we do not want to create a
// toolbar button for it.
if (!action_item) {
return;
}
if (GetPoppedOutButtonFor(id)) {
const auto iter = std::ranges::find(popped_out_buttons_, id,
[](PinnedActionToolbarButton* button) {
return button->GetActionId();
});
(*iter)->SetPinned(true);
pinned_buttons_.push_back(*iter);
popped_out_buttons_.erase(iter);
} else {
auto pinned_button = CreateOrGetButtonForAction(id);
auto* button = pinned_button.get();
pinned_button->SetPinned(true);
pinned_buttons_.push_back(AddChildView(std::move(pinned_button)));
// If the added button was previously a cached permanent button then the
// icon may not be correct for the current state (i.e. touch ui).
if (button->IsPermanent()) {
button->UpdateIcon();
}
}
}
void PinnedToolbarActionsContainer::RemovePinnedActionButtonFor(
actions::ActionId id) {
const auto iter = std::ranges::find(
pinned_buttons_, id,
[](PinnedActionToolbarButton* button) { return button->GetActionId(); });
if (iter == pinned_buttons_.end()) {
return;
}
if (!ShouldRemainPoppedOutInToolbar(*iter)) {
GetAnimatingLayoutManager()->FadeOut(*iter);
GetAnimatingLayoutManager()->PostOrQueueAction(
base::BindOnce(&PinnedToolbarActionsContainer::RemoveButton,
weak_ptr_factory_.GetWeakPtr(), *iter));
} else {
(*iter)->SetPinned(false);
popped_out_buttons_.push_back(*iter);
}
pinned_buttons_.erase(iter);
}
PinnedActionToolbarButton* PinnedToolbarActionsContainer::GetPinnedButtonFor(
actions::ActionId id) {
const auto iter = std::ranges::find(
pinned_buttons_, id,
[](PinnedActionToolbarButton* button) { return button->GetActionId(); });
return iter == pinned_buttons_.end() ? nullptr : *iter;
}
PinnedActionToolbarButton* PinnedToolbarActionsContainer::GetPoppedOutButtonFor(
actions::ActionId id) {
const auto iter = std::ranges::find(
popped_out_buttons_, id,
[](PinnedActionToolbarButton* button) { return button->GetActionId(); });
return iter == popped_out_buttons_.end() ? nullptr : *iter;
}
PinnedActionToolbarButton* PinnedToolbarActionsContainer::GetButtonFor(
actions::ActionId id) {
if (auto* pinned_button = GetPinnedButtonFor(id)) {
return pinned_button;
}
if (auto* pop_out_button = GetPoppedOutButtonFor(id)) {
return pop_out_button;
}
return nullptr;
}
bool PinnedToolbarActionsContainer::ShouldRemainPoppedOutInToolbar(
PinnedActionToolbarButton* button) {
return button->IsActive() || button->ShouldShowEphemerallyInToolbar();
}
void PinnedToolbarActionsContainer::RemoveButton(
PinnedActionToolbarButton* button) {
if (!button->IsPermanent() && button->NeedsDelayedDestruction()) {
// Defer deletion of the view to allow the pressed event handler
// that triggers its removal to run to completion.
base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(
FROM_HERE, RemoveChildViewT(button));
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&PinnedToolbarActionsContainer::InvalidateLayout,
weak_ptr_factory_.GetWeakPtr(),
// This will always be on a fresh call stack, never
// mid-layout so the value passed here doesn't matter.
/*avoid_propagate_during_layout=*/false));
} else {
auto removed_button = RemoveChildViewT(button);
if (removed_button->IsPermanent()) {
permanent_buttons_.push_back(std::move(removed_button));
}
InvalidateLayout();
}
}
bool PinnedToolbarActionsContainer::IsOverflowed(actions::ActionId id) {
const auto* const pinned_button = GetPinnedButtonFor(id);
// TODO(pengchaocai): Support popped out buttons overflow.
// TODO(crbug.com/40949386): If this container is not visible treat the
// elements inside as overflowed.
// Need to use the target layout in case the animation has not yet shown the
// button but is in the process of revealing it.
const auto* const layout =
GetAnimatingLayoutManager()->target_layout().GetLayoutFor(pinned_button);
return GetAnimatingLayoutManager()->target_layout_manager()->CanBeVisible(
pinned_button) &&
layout && (!GetVisible() || !layout->visible);
}
views::View* PinnedToolbarActionsContainer::GetContainerView() {
return static_cast<views::View*>(this);
}
bool PinnedToolbarActionsContainer::ShouldAnyButtonsOverflow(
gfx::Size available_size) const {
views::ProposedLayout proposed_layout;
if (GetAnimatingLayoutManager()->is_animating()) {
proposed_layout = GetAnimatingLayoutManager()->target_layout();
} else {
proposed_layout =
GetAnimatingLayoutManager()->target_layout_manager()->GetProposedLayout(
available_size);
}
for (PinnedActionToolbarButton* pinned_button : pinned_buttons_) {
if (views::ChildLayout* child_layout =
proposed_layout.GetLayoutFor(pinned_button)) {
if (GetAnimatingLayoutManager()->target_layout_manager()->CanBeVisible(
pinned_button) &&
!child_layout->visible) {
return true;
}
}
}
return false;
}
bool PinnedToolbarActionsContainer::IsActionPinned(actions::ActionId id) {
PinnedActionToolbarButton* button = GetPinnedButtonFor(id);
return button != nullptr;
}
bool PinnedToolbarActionsContainer::IsActionPoppedOut(actions::ActionId id) {
PinnedActionToolbarButton* button = GetPoppedOutButtonFor(id);
return button != nullptr;
}
bool PinnedToolbarActionsContainer::IsActionPinnedOrPoppedOut(
actions::ActionId id) {
return IsActionPinned(id) || IsActionPoppedOut(id);
}
void PinnedToolbarActionsContainer::ReorderViews() {
size_t index = 0;
// Pinned buttons appear first. Use the model's ordering of pinned ActionIds
// because |pinned_buttons_| ordering is not updated on changes from the model
// or from the user dragging to reorder.
const auto ordered_pinned_ids = model_->PinnedActionIds();
for (auto id : ordered_pinned_ids) {
if (auto* button = GetPinnedButtonFor(id)) {
ReorderChildView(button, index);
index++;
}
}
// Update the order of |pinned_buttons_| to reflect the model's order.
std::sort(pinned_buttons_.begin(), pinned_buttons_.end(),
[ordered_pinned_ids](PinnedActionToolbarButton* button_1,
PinnedActionToolbarButton* button_2) {
int button_1_index =
std::find(ordered_pinned_ids.begin(),
ordered_pinned_ids.end(), button_1->GetActionId()) -
ordered_pinned_ids.begin();
int button_2_index =
std::find(ordered_pinned_ids.begin(),
ordered_pinned_ids.end(), button_2->GetActionId()) -
ordered_pinned_ids.begin();
return button_1_index < button_2_index;
});
// Add the dragged button in its location if a drag is active.
if (drop_info_.get()) {
ReorderChildView(GetPinnedButtonFor(drop_info_->action_id),
drop_info_->index);
}
// The divider exist and is after the pinned buttons if any
// exist.
ReorderChildView(toolbar_divider_, index);
index++;
// Popped out buttons appear last.
for (PinnedActionToolbarButton* popped_out_button : popped_out_buttons_) {
ReorderChildView(popped_out_button, index);
index++;
}
}
void PinnedToolbarActionsContainer::UpdateViews() {
std::vector<actions::ActionId> old_ids;
for (PinnedActionToolbarButton* const button : pinned_buttons_) {
old_ids.push_back(button->GetActionId());
}
const std::vector<actions::ActionId>& new_ids = model_->PinnedActionIds();
// 1. Remove buttons for actions in the UI that are not present in the
// model.
for (actions::ActionId id : old_ids) {
if (base::Contains(new_ids, id)) {
continue;
}
// End the drag session if the dragged button is being removed.
if (drop_info_ && drop_info_->action_id == id) {
drop_info_.reset();
}
RemovePinnedActionButtonFor(id);
}
// 2. Add buttons for actions that are in the model but not in the UI.
for (actions::ActionId id : new_ids) {
if (base::Contains(old_ids, id)) {
continue;
}
AddPinnedActionButtonFor(id);
}
// 3. Clamp the drag index within the new bounds of the container in cases
// where a button was removed by sync while a user was dragging a different
// button.
if (drop_info_.get() && drop_info_->index >= pinned_buttons_.size()) {
drop_info_->index = std::max(size_t(0), pinned_buttons_.size() - 1);
}
// 4. Ensure the views match the ordering in the model.
ReorderViews();
}
void PinnedToolbarActionsContainer::SetActionButtonIconVisibility(
actions::ActionId id,
bool visible) {
if (auto* button = GetPinnedButtonFor(id)) {
button->SetIconVisibility(visible);
}
}
void PinnedToolbarActionsContainer::MovePinnedAction(
actions::ActionId action_id,
size_t index,
base::ScopedClosureRunner cleanup,
const ui::DropTargetEvent& event,
ui::mojom::DragOperation& output_drag_op,
std::unique_ptr<ui::LayerTreeOwner> drag_image_layer_owner) {
// Adjust the index based on the the the button we are moving it next to in
// the toolbar. This is necessary because there might be ids in the model that
// are not currently available that need to be factored into the index
// calculation.
if (index < pinned_buttons_.size()) {
auto target_index_button = pinned_buttons_[index];
const auto& pinned_action_ids = model_->PinnedActionIds();
auto it = find(pinned_action_ids.begin(), pinned_action_ids.end(),
target_index_button->GetActionId());
CHECK(it != pinned_action_ids.end());
index = it - pinned_action_ids.begin();
}
model_->MovePinnedAction(action_id, index);
output_drag_op = ui::mojom::DragOperation::kMove;
// `cleanup` will run automatically when it goes out of scope to finish
// up the drag.
}
void PinnedToolbarActionsContainer::DragDropCleanup(
actions::ActionId dragged_action_id) {
ReorderViews();
GetAnimatingLayoutManager()->PostOrQueueAction(base::BindOnce(
&PinnedToolbarActionsContainer::SetActionButtonIconVisibility,
weak_ptr_factory_.GetWeakPtr(), dragged_action_id, true));
}
size_t PinnedToolbarActionsContainer::WidthToIconCount(int x_offset) {
if (!button_provider_) {
return 0;
}
const int element_padding = GetLayoutConstant(TOOLBAR_ELEMENT_PADDING);
size_t unclamped_count = std::max(
(x_offset + element_padding) /
(button_provider_->GetToolbarButtonSize().width() + element_padding),
0);
return std::min(unclamped_count, pinned_buttons_.size());
}
const std::vector<actions::ActionId>&
PinnedToolbarActionsContainer::PinnedActionIds() const {
return model_->PinnedActionIds();
}
std::unique_ptr<PinnedActionToolbarButton>
PinnedToolbarActionsContainer::CreateOrGetButtonForAction(
actions::ActionId id) {
const auto iter = std::ranges::find(
permanent_buttons_, id,
[](const std::unique_ptr<PinnedActionToolbarButton>& button) {
return button->GetActionId();
});
if (iter != permanent_buttons_.end()) {
auto button = std::move(*iter);
button->SetVisible(true);
permanent_buttons_.erase(iter);
return button;
}
auto button = std::make_unique<PinnedActionToolbarButton>(
browser_view_->browser(), id, weak_ptr_factory_.GetWeakPtr());
action_view_controller_->CreateActionViewRelationship(
button.get(), GetActionItemFor(id)->GetAsWeakPtr());
button->SetPaintToLayer();
button->layer()->SetFillsBoundsOpaquely(false);
return button;
}
BEGIN_METADATA(PinnedToolbarActionsContainer)
END_METADATA