blob: a1e3d3a3b386d8bbde79b17b5da67eb68c547974 [file] [log] [blame]
// Copyright 2024 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/tab_strip_combo_button.h"
#include "base/time/time.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/views/tabs/tab_search_button.h"
#include "chrome/browser/ui/views/tabs/tab_search_container.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/browser/ui/views/tabs/tab_strip_control_button.h"
#include "chrome/common/chrome_features.h"
#include "chrome/grit/generated_resources.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/color/color_id.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/view_class_properties.h"
namespace {
// LINT.IfChange(AccidentalClickType)
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class AccidentalClickType {
kClick = 0,
kAccidentalClick = 1,
kMaxValue = kAccidentalClick,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/tab/enums.xml:AccidentalClickType)
constexpr int kButtonGapNoBackground = 14;
constexpr int kSeparatorBorderRadius = 2;
constexpr int kSeparatorWidth = 2;
constexpr int kSeparatorHeight = 16;
constexpr base::TimeDelta kAccidentalClickThreshold = base::Seconds(1);
constexpr char kNewTabButtonAccidentalClickName[] =
"Tabs.NewTabButton.AccidentalClicks";
constexpr char kTabSearchAccidentalClickName[] =
"Tabs.TabSearch.AccidentalClicks";
} // namespace
TabStripComboButton::TabStripComboButton(BrowserWindowInterface* browser,
TabStrip* tab_strip) {
Edge new_tab_button_flat_edge = Edge::kNone;
if (features::HasTabstripComboButtonWithBackground()) {
new_tab_button_flat_edge = base::i18n::IsRTL() ? Edge::kLeft : Edge::kRight;
}
std::unique_ptr<TabStripControlButton> new_tab_button =
std::make_unique<TabStripControlButton>(
tab_strip->controller(),
base::BindRepeating(&TabStrip::NewTabButtonPressed,
base::Unretained(tab_strip)),
vector_icons::kAddIcon, new_tab_button_flat_edge);
new_tab_button->SetProperty(views::kElementIdentifierKey,
kNewTabButtonElementId);
if (features::HasTabstripComboButtonWithBackground()) {
new_tab_button->SetForegroundFrameActiveColorId(
kColorNewTabButtonForegroundFrameActive);
new_tab_button->SetForegroundFrameInactiveColorId(
kColorNewTabButtonForegroundFrameInactive);
new_tab_button->SetBackgroundFrameActiveColorId(
kColorNewTabButtonCRBackgroundFrameActive);
new_tab_button->SetBackgroundFrameInactiveColorId(
kColorNewTabButtonCRBackgroundFrameInactive);
} else {
// Add a gap between the new tab button and tab search container.
new_tab_button->SetProperty(
views::kMarginsKey, gfx::Insets::TLBR(0, 0, 0, kButtonGapNoBackground));
}
new_tab_button->SetTooltipText(
l10n_util::GetStringUTF16(IDS_TOOLTIP_NEW_TAB));
new_tab_button->GetViewAccessibility().SetName(
l10n_util::GetStringUTF16(IDS_ACCNAME_NEWTAB));
subscriptions_.push_back(new_tab_button->AddStateChangedCallback(
base::BindRepeating(&TabStripComboButton::OnNewTabButtonStateChanged,
base::Unretained(this))));
#if BUILDFLAG(IS_LINUX)
// The New Tab Button can be middle-clicked on Linux.
new_tab_button->SetTriggerableEventFlags(
new_tab_button->GetTriggerableEventFlags() | ui::EF_MIDDLE_MOUSE_BUTTON);
#endif
std::unique_ptr<views::Separator> separator =
std::make_unique<views::Separator>();
const int color_id = features::HasTabstripComboButtonWithBackground()
? kColorTabStripComboButtonSeparator
: kColorTabStripComboButtonSeparatorOnHeader;
separator->SetColorId(color_id);
separator->SetBorderRadius(kSeparatorBorderRadius);
separator->SetPreferredSize(gfx::Size(kSeparatorWidth, kSeparatorHeight));
std::unique_ptr<TabSearchContainer> tab_search_container =
std::make_unique<TabSearchContainer>(
tab_strip->controller(), browser->GetTabStripModel(), true, this,
browser, browser->GetFeatures().tab_declutter_controller(), this,
tab_strip);
tab_search_container->SetProperty(views::kCrossAxisAlignmentKey,
views::LayoutAlignment::kCenter);
subscriptions_.push_back(
tab_search_container->tab_search_button()->AddStateChangedCallback(
base::BindRepeating(
&TabStripComboButton::OnTabSearchButtonStateChanged,
base::Unretained(this))));
auto* button_container = AddChildView(std::make_unique<views::View>());
auto* separator_container = AddChildView(std::make_unique<views::View>());
button_container->SetLayoutManager(std::make_unique<views::FlexLayout>());
separator_container->SetLayoutManager(std::make_unique<views::FlexLayout>())
->SetOrientation(views::LayoutOrientation::kHorizontal)
.SetMainAxisAlignment(views::LayoutAlignment::kCenter)
.SetCrossAxisAlignment(views::LayoutAlignment::kCenter);
separator_container->SetCanProcessEventsWithinSubtree(false);
new_tab_button_ = button_container->AddChildView(std::move(new_tab_button));
tab_search_container_ =
button_container->AddChildView(std::move(tab_search_container));
separator_ = separator_container->AddChildView(std::move(separator));
SetLayoutManager(std::make_unique<views::FillLayout>());
SetNotifyEnterExitOnChild(true);
}
TabStripComboButton::~TabStripComboButton() = default;
void TabStripComboButton::OnNewTabButtonStateChanged() {
if (new_tab_button_->GetState() == views::Button::STATE_PRESSED) {
new_tab_button_last_pressed_ = base::TimeTicks::Now();
base::UmaHistogramEnumeration(kNewTabButtonAccidentalClickName,
AccidentalClickType::kClick);
if (!tab_search_button_last_pressed_.is_null() &&
(new_tab_button_last_pressed_ - tab_search_button_last_pressed_) <
kAccidentalClickThreshold) {
base::UmaHistogramEnumeration(kTabSearchAccidentalClickName,
AccidentalClickType::kAccidentalClick);
}
}
UpdateSeparatorVisibility();
}
void TabStripComboButton::OnTabSearchButtonStateChanged() {
if (tab_search_container_->tab_search_button()->GetState() ==
views::Button::STATE_PRESSED) {
tab_search_button_last_pressed_ = base::TimeTicks::Now();
base::UmaHistogramEnumeration(kTabSearchAccidentalClickName,
AccidentalClickType::kClick);
if (!new_tab_button_last_pressed_.is_null() &&
(tab_search_button_last_pressed_ - new_tab_button_last_pressed_) <
kAccidentalClickThreshold) {
base::UmaHistogramEnumeration(kNewTabButtonAccidentalClickName,
AccidentalClickType::kAccidentalClick);
}
}
UpdateSeparatorVisibility();
}
void TabStripComboButton::UpdateSeparatorVisibility() {
const views::Button::ButtonState new_tab_button_state =
new_tab_button_->GetState();
const views::Button::ButtonState tab_search_button_state =
tab_search_container_->tab_search_button()->GetState();
const bool is_visible =
features::HasTabstripComboButtonWithBackground()
? new_tab_button_state != views::Button::STATE_HOVERED &&
new_tab_button_state != views::Button::STATE_PRESSED &&
tab_search_button_state != views::Button::STATE_HOVERED &&
tab_search_button_state != views::Button::STATE_PRESSED
: true;
separator_->SetVisible(is_visible);
}
BEGIN_METADATA(TabStripComboButton)
END_METADATA