blob: 6012fea112813e2105886f0fa6f98d015eb43918 [file]
// Copyright 2025 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/tabs/vertical/vertical_tab_strip_top_container.h"
#include "chrome/browser/ui/actions/chrome_action_id.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/tabs/vertical_tab_strip_state_controller.h"
#include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/views/actions/action_view_controller.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/layout/delegating_layout_manager.h"
#include "ui/views/layout/proposed_layout.h"
#include "ui/views/view_class_properties.h"
namespace {
constexpr int kTopButtonContainerHeight = 28;
constexpr int kTopButtonPadding = 4;
} // namespace
namespace {
class TopContainerButton : public views::LabelButton {
METADATA_HEADER(TopContainerButton, views::LabelButton)
public:
TopContainerButton() { ConfigureInkDropForToolbar(this); }
// views::LabelButton:
std::unique_ptr<views::ActionViewInterface> GetActionViewInterface() override;
};
BEGIN_METADATA(TopContainerButton)
END_METADATA
class TopContainerButtonActionViewInterface
: public views::LabelButtonActionViewInterface {
public:
explicit TopContainerButtonActionViewInterface(
TopContainerButton* action_view)
: views::LabelButtonActionViewInterface(action_view),
action_view_(action_view) {}
void ActionItemChangedImpl(actions::ActionItem* action_item) override {
ButtonActionViewInterface::ActionItemChangedImpl(action_item);
action_view_->SetImageModel(views::Button::STATE_NORMAL,
action_item->GetImage());
}
private:
raw_ptr<TopContainerButton> action_view_ = nullptr;
};
std::unique_ptr<views::ActionViewInterface>
TopContainerButton::GetActionViewInterface() {
return std::make_unique<TopContainerButtonActionViewInterface>(this);
}
} // namespace
VerticalTabStripTopContainer::VerticalTabStripTopContainer(
tabs::VerticalTabStripStateController* state_controller,
actions::ActionItem* root_action_item)
: state_controller_(state_controller),
root_action_item_(root_action_item),
action_view_controller_(std::make_unique<views::ActionViewController>()) {
SetLayoutManager(std::make_unique<views::DelegatingLayoutManager>(this));
tab_search_button_ = AddChildButtonFor(kActionTabSearch);
collapse_button_ = AddChildButtonFor(kActionToggleCollapseVertical);
tab_search_button_->SetProperty(views::kElementIdentifierKey,
kTabSearchButtonElementId);
collapse_button_->SetProperty(views::kElementIdentifierKey,
kVerticalTabStripCollapseButtonElementId);
SetProperty(views::kElementIdentifierKey,
kVerticalTabStripTopContainerElementId);
}
VerticalTabStripTopContainer::~VerticalTabStripTopContainer() = default;
// TODO(crbug.com/445528000): Update height calculation after child components
// are added
views::ProposedLayout VerticalTabStripTopContainer::CalculateProposedLayout(
const views::SizeBounds& size_bounds) const {
views::ProposedLayout layout;
// TODO(crbug.com/465857622): Implement smarter reflow around caption buttons.
const int exclusion_height = exclusion_width_ > 0 ? toolbar_height_ : 0;
layout.host_size =
gfx::Size(size_bounds.width().is_bounded() ? size_bounds.width().value()
: parent()->width(),
kTopButtonContainerHeight + exclusion_height);
CHECK(tab_search_button_);
CHECK(collapse_button_);
const gfx::Size tab_search_button_pref_size =
tab_search_button_->GetPreferredSize(views::SizeBounds(layout.host_size));
const gfx::Size collapse_button_pref_size =
collapse_button_->GetPreferredSize(views::SizeBounds(layout.host_size));
int current_x = layout.host_size.width();
int current_y = layout.host_size.height() - exclusion_height;
// Calculate bounds to right-align the button horizontally and center it
// vertically within the available space.
gfx::Rect tab_search_button_bounds(
current_x - tab_search_button_pref_size.width(),
(current_y - tab_search_button_pref_size.height()) / 2 + exclusion_height,
tab_search_button_pref_size.width(),
tab_search_button_pref_size.height());
layout.child_layouts.emplace_back(
tab_search_button_.get(), tab_search_button_->GetVisible(),
tab_search_button_bounds, views::SizeBounds(tab_search_button_pref_size));
current_x = tab_search_button_bounds.x() - kTopButtonPadding;
// Re-calculate bounds based on new x value, offset by the tab search button.
gfx::Rect collapse_button_bounds(
current_x - collapse_button_pref_size.width(),
(current_y - collapse_button_pref_size.height()) / 2 + exclusion_height,
collapse_button_pref_size.width(), collapse_button_pref_size.height());
layout.child_layouts.emplace_back(
collapse_button_.get(), collapse_button_->GetVisible(),
collapse_button_bounds, views::SizeBounds(collapse_button_pref_size));
return layout;
}
views::LabelButton* VerticalTabStripTopContainer::AddChildButtonFor(
actions::ActionId action_id) {
std::unique_ptr<TopContainerButton> container_button =
std::make_unique<TopContainerButton>();
actions::ActionItem* action_item =
actions::ActionManager::Get().FindAction(action_id, root_action_item_);
CHECK(action_item);
action_view_controller_->CreateActionViewRelationship(
container_button.get(), action_item->GetAsWeakPtr());
TopContainerButton* raw_container_button =
AddChildView(std::move(container_button));
raw_container_button->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
return raw_container_button;
}
bool VerticalTabStripTopContainer::IsPositionInWindowCaption(
const gfx::Point& point) {
const auto get_target_rect = [&](views::View* target) {
const gfx::Rect& rect = gfx::Rect(point, gfx::Size(1, 1));
gfx::RectF rect_in_target_coords_f(rect);
View::ConvertRectToTarget(this, target, &rect_in_target_coords_f);
return gfx::ToEnclosingRect(rect_in_target_coords_f);
};
if (tab_search_button_ && tab_search_button_->GetLocalBounds().Intersects(
get_target_rect(tab_search_button_))) {
return !tab_search_button_->HitTestRect(
get_target_rect(tab_search_button_));
}
if (collapse_button_ && collapse_button_->GetLocalBounds().Intersects(
get_target_rect(collapse_button_))) {
return !collapse_button_->HitTestRect(get_target_rect(collapse_button_));
}
return true;
}
void VerticalTabStripTopContainer::SetToolbarHeightForLayout(
const int toolbar_height) {
toolbar_height_ = toolbar_height;
}
void VerticalTabStripTopContainer::SetExclusionWidthForLayout(
const int exclusion_width) {
exclusion_width_ = exclusion_width;
}
BEGIN_METADATA(VerticalTabStripTopContainer)
END_METADATA