blob: 48c636efab94d247bf5ec8eae0d14d6c8cb40ddd [file] [log] [blame]
// Copyright 2019 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/webui_tab_counter_button.h"
#include <memory>
#include <string>
#include "base/functional/bind.h"
#include "base/i18n/message_formatter.h"
#include "base/i18n/number_formatting.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/tabs/tab_enums.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/browser/ui/views/flying_indicator.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
#include "chrome/grit/generated_resources.h"
#include "components/user_education/common/user_education_class_properties.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/base/models/menu_separator_types.h"
#include "ui/base/mojom/menu_source_type.mojom-forward.h"
#include "ui/base/theme_provider.h"
#include "ui/gfx/animation/multi_animation.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/favicon_size.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/menus/simple_menu_model.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/ink_drop_highlight.h"
#include "ui/views/border.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/context_menu_controller.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_model_adapter.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/throbber.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/style/typography.h"
#include "ui/views/style/typography_provider.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/native_widget.h"
#include "ui/views/widget/widget.h"
namespace {
// The distance to move a label so it appears "offscreen" - that is, the text
// will be clipped by the border and not visible.
constexpr int kOffscreenLabelDistance = 16;
constexpr base::TimeDelta kFirstPartDuration = base::Milliseconds(100);
// Returns whether |change| to |tab_strip_mode| should start the tab counter
// throbber animation.
bool ShouldChangeStartThrobber(TabStripModel* tab_strip_model,
const TabStripModelChange& change) {
if (change.type() != TabStripModelChange::kInserted) {
return false;
}
const auto& contents = change.GetInsert()->contents;
return contents.size() > 1 ||
tab_strip_model->GetActiveWebContents() != contents[0].contents;
}
std::u16string GetTabCounterLabelText(int num_tabs) {
// In the triple-digit case, fall back to ':D' to match Android.
if (num_tabs >= 100) {
return std::u16string(u":D");
}
return base::FormatNumber(num_tabs);
}
//------------------------------------------------------------------------
// NumberLabel
// Label to display a number of tabs. Because there is limited space within the
// tab counter border, the font shrinks when the count is 10 or higher.
class NumberLabel : public views::Label {
METADATA_HEADER(NumberLabel, views::Label)
public:
NumberLabel() : Label(std::u16string(), CONTEXT_TAB_COUNTER) {
single_digit_font_ = font_list();
double_digit_font_ = views::TypographyProvider::Get().GetFont(
CONTEXT_TAB_COUNTER, views::style::STYLE_SECONDARY);
}
~NumberLabel() override = default;
void SetText(std::u16string_view text) override {
SetFontList(text.length() > 1 ? double_digit_font_ : single_digit_font_);
Label::SetText(text);
}
private:
gfx::FontList single_digit_font_;
gfx::FontList double_digit_font_;
};
BEGIN_METADATA(NumberLabel)
END_METADATA
///////////////////////////////////////////////////////////////////////////////
// InteractionTracker
// Listens in on the widget event stream (as a pre target event handler) and
// records user interactions (mouse clicks, taps, etc.) Used so that we know
// where a link that was opened in a background tab was opened from so that we
// can play a "flying link" animation.
class InteractionTracker : public ui::EventHandler,
public views::WidgetObserver {
public:
explicit InteractionTracker(views::Widget* widget)
: native_window_(widget->GetNativeWindow()) {
if (native_window_) {
native_window_->AddPreTargetHandler(this);
}
scoped_widget_observation_.Observe(widget);
}
InteractionTracker(const InteractionTracker& other) = delete;
void operator=(const InteractionTracker& other) = delete;
~InteractionTracker() override {
if (native_window_) {
native_window_->RemovePreTargetHandler(this);
}
}
const std::optional<gfx::Point>& last_interaction_location() const {
return last_interaction_location_;
}
private:
// ui::EventHandler:
void OnEvent(ui::Event* event) override {
if (event->type() == ui::EventType::kMousePressed ||
event->type() == ui::EventType::kMouseReleased ||
event->type() == ui::EventType::kTouchPressed) {
const ui::LocatedEvent* const located = event->AsLocatedEvent();
last_interaction_location_ =
located->target()->GetScreenLocation(*located);
}
}
// views::WidgetObserver:
void OnWidgetBoundsChanged(views::Widget* widget,
const gfx::Rect& new_bounds) override {
last_interaction_location_.reset();
}
void OnWidgetDestroying(views::Widget* widget) override {
// Clean up all of our observers and event handlers before the native window
// disappears.
DCHECK(scoped_widget_observation_.IsObservingSource(widget));
scoped_widget_observation_.Reset();
if (widget->GetNativeWindow()) {
widget->GetNativeWindow()->RemovePreTargetHandler(this);
native_window_ = nullptr;
}
}
std::optional<gfx::Point> last_interaction_location_;
gfx::NativeWindow native_window_;
base::ScopedObservation<views::Widget, views::WidgetObserver>
scoped_widget_observation_{this};
};
//------------------------------------------------------------------------
// TabCounterAnimator
// Animates the label and border. |border_view_| does a little bounce. At the
// peak of |border_view_|'s bounce, the |disappearing_label_| begins to scroll
// away in the same direction and is replaced with |appearing_label_|, which
// shows the new number of tabs. This animation is played upside-down when a tab
// is added vs. removed.
class TabCounterAnimator : public gfx::AnimationDelegate {
public:
TabCounterAnimator(views::Label* appearing_label,
views::Label* disappearing_label,
views::View* border_view,
views::Throbber* throbber);
TabCounterAnimator(const TabCounterAnimator&) = delete;
void operator=(const TabCounterAnimator&) = delete;
~TabCounterAnimator() override = default;
void Animate(int new_num_tabs, bool should_start_throbber);
void StartFlyingLinkFrom(const gfx::Point& screen_position);
void LayoutIfAnimating();
private:
// Describes the current counter animation (if any). The animation is played
// one way to show a decrease, and upside down from that to show an increase.
enum class TabCounterAnimationType { kNone, kIncreasing, kDecreasing };
// AnimationDelegate:
void AnimationProgressed(const gfx::Animation* animation) override;
void AnimationEnded(const gfx::Animation* animation) override;
void MaybeStartPendingAnimation();
void StartAnimation();
int GetBorderTargetYDelta() const;
int GetBorderOvershootYDelta() const;
int GetAppearingLabelStartPosition() const;
int GetDisappearingLabelTargetPosition() const;
int GetBorderStartingY() const;
std::optional<int> last_num_tabs_;
std::optional<int> pending_num_tabs_ = 0;
bool pending_throbber_ = false;
TabCounterAnimationType current_animation_ = TabCounterAnimationType::kNone;
// The label that will be animated into view, showing the new value.
const raw_ptr<views::Label> appearing_label_;
// The label that will be animated out of view, showing the old value.
const raw_ptr<views::Label> disappearing_label_;
gfx::MultiAnimation label_animation_;
const raw_ptr<views::View> border_view_;
gfx::MultiAnimation border_animation_;
const raw_ptr<views::Throbber> throbber_;
base::OneShotTimer throbber_timer_;
std::unique_ptr<FlyingIndicator> flying_link_;
};
TabCounterAnimator::TabCounterAnimator(views::Label* appearing_label,
views::Label* disappearing_label,
views::View* border_view,
views::Throbber* throbber)
: appearing_label_(appearing_label),
disappearing_label_(disappearing_label),
label_animation_(gfx::MultiAnimation::Parts{
// Stay in place.
{kFirstPartDuration, gfx::Tween::Type::ZERO},
// Swap out to the new label.
{base::Milliseconds(200), gfx::Tween::Type::EASE_IN_OUT}}),
border_view_(border_view),
border_animation_(gfx::MultiAnimation::Parts{
{kFirstPartDuration, gfx::Tween::Type::EASE_OUT},
{base::Milliseconds(150), gfx::Tween::Type::EASE_IN_OUT},
{base::Milliseconds(50), gfx::Tween::Type::EASE_IN_OUT}}),
throbber_(throbber) {
label_animation_.set_delegate(this);
label_animation_.set_continuous(false);
border_animation_.set_delegate(this);
border_animation_.set_continuous(false);
}
void TabCounterAnimator::Animate(int new_num_tabs, bool should_start_throbber) {
pending_num_tabs_ = new_num_tabs;
pending_throbber_ |= should_start_throbber;
MaybeStartPendingAnimation();
}
void TabCounterAnimator::MaybeStartPendingAnimation() {
if (flying_link_ && flying_link_->is_flying()) {
return;
}
if (pending_throbber_) {
// Start the throbber if it is not already showing.
if (!throbber_timer_.IsRunning()) {
throbber_->Start();
}
// Automatically stop the throbber after 1 second. This will reset the timer
// if it is already running. Currently we do not check the real loading
// state of the new tab(s), as that adds unnecessary complexity. The purpose
// of the throbber is just to indicate to the user that some activity has
// happened in the background, which may not otherwise have been obvious
// because the tab strip is hidden in this mode.
throbber_timer_.Start(FROM_HERE, base::Milliseconds(1000), throbber_.get(),
&views::Throbber::Stop);
pending_throbber_ = false;
}
if (pending_num_tabs_.has_value()) {
if (last_num_tabs_.has_value() &&
last_num_tabs_.value() != pending_num_tabs_.value()) {
current_animation_ = pending_num_tabs_.value() > last_num_tabs_.value()
? TabCounterAnimationType::kIncreasing
: TabCounterAnimationType::kDecreasing;
disappearing_label_->SetText(appearing_label_->GetText());
appearing_label_->SetText(
GetTabCounterLabelText(pending_num_tabs_.value()));
border_animation_.Stop();
border_animation_.Start();
label_animation_.Stop();
label_animation_.Start();
appearing_label_->InvalidateLayout();
LayoutIfAnimating();
} else if (!last_num_tabs_.has_value()) {
appearing_label_->SetText(
GetTabCounterLabelText(pending_num_tabs_.value()));
}
last_num_tabs_ = pending_num_tabs_;
}
}
void TabCounterAnimator::StartFlyingLinkFrom(
const gfx::Point& screen_position) {
flying_link_ = FlyingIndicator::StartFlyingIndicator(
kWebIcon, screen_position, throbber_,
base::BindOnce(&TabCounterAnimator::MaybeStartPendingAnimation,
base::Unretained(this)));
}
void TabCounterAnimator::LayoutIfAnimating() {
if (!border_animation_.is_animating() && !label_animation_.is_animating()) {
return;
}
// |border_view_| does a hop or a dip based on animation type.
int border_y_delta = 0;
switch (border_animation_.current_part_index()) {
case 0:
// Move away.
border_y_delta = gfx::Tween::IntValueBetween(
border_animation_.GetCurrentValue(), 0, GetBorderTargetYDelta());
break;
case 1:
// Return, slightly overshooting the start position.
border_y_delta = gfx::Tween::IntValueBetween(
border_animation_.GetCurrentValue(), GetBorderTargetYDelta(),
GetBorderOvershootYDelta());
break;
case 2:
// Return back to the start position.
border_y_delta = gfx::Tween::IntValueBetween(
border_animation_.GetCurrentValue(), GetBorderOvershootYDelta(), 0);
break;
default:
NOTREACHED();
}
border_view_->SetY(GetBorderStartingY() + border_y_delta);
// |appearing_label_| scrolls into view - from above if the counter is
// increasing, below if it is decreasing.
const int appearing_label_position = gfx::Tween::IntValueBetween(
label_animation_.GetCurrentValue(), GetAppearingLabelStartPosition(), 0);
appearing_label_->SetY(appearing_label_position - border_y_delta);
// |disappearing_label_| scrolls out of view - out the bottom if
// |appearing_label_| is decreasing, and from below if increasing.
const int disappearing_label_position =
gfx::Tween::IntValueBetween(label_animation_.GetCurrentValue(), 0,
GetDisappearingLabelTargetPosition());
disappearing_label_->SetY(disappearing_label_position - border_y_delta);
}
void TabCounterAnimator::AnimationProgressed(const gfx::Animation* animation) {
LayoutIfAnimating();
}
void TabCounterAnimator::AnimationEnded(const gfx::Animation* animation) {
AnimationProgressed(animation);
}
int TabCounterAnimator::GetBorderTargetYDelta() const {
constexpr int kBorderBounceDistance = 4;
switch (current_animation_) {
case TabCounterAnimationType::kIncreasing:
return kBorderBounceDistance;
case TabCounterAnimationType::kDecreasing:
return -kBorderBounceDistance;
default:
NOTREACHED();
}
}
int TabCounterAnimator::GetBorderOvershootYDelta() const {
constexpr int kBorderBounceOvershoot = 2;
switch (current_animation_) {
case TabCounterAnimationType::kIncreasing:
return -kBorderBounceOvershoot;
case TabCounterAnimationType::kDecreasing:
return kBorderBounceOvershoot;
default:
NOTREACHED();
}
}
int TabCounterAnimator::GetAppearingLabelStartPosition() const {
switch (current_animation_) {
case TabCounterAnimationType::kIncreasing:
return -kOffscreenLabelDistance;
case TabCounterAnimationType::kDecreasing:
return kOffscreenLabelDistance;
default:
NOTREACHED();
}
}
int TabCounterAnimator::GetDisappearingLabelTargetPosition() const {
// We want to exit out the opposite side that |appearing_label_| entered
// from.
return -GetAppearingLabelStartPosition();
}
int TabCounterAnimator::GetBorderStartingY() const {
// When at rest, |border_view_| should be vertically centered within its
// container.
views::View* border_container = border_view_->parent();
int border_available_space = border_container->GetLocalBounds().height();
return (border_available_space - border_view_->GetLocalBounds().height()) / 2;
}
//------------------------------------------------------------------------
// WebUITabCounterButton
class WebUITabCounterButton : public views::Button,
public TabStripModelObserver,
public views::ContextMenuController,
public ui::SimpleMenuModel::Delegate {
METADATA_HEADER(WebUITabCounterButton, views::Button)
public:
static constexpr int WEBUI_TAB_COUNTER_CXMENU_CLOSE_TAB = 13;
static constexpr int WEBUI_TAB_COUNTER_CXMENU_NEW_TAB = 14;
WebUITabCounterButton(PressedCallback pressed_callback,
BrowserView* browser_view);
~WebUITabCounterButton() override;
void UpdateTooltip(int tab_count);
void UpdateColors();
void Init();
private:
// views::Button:
void AddedToWidget() override;
void AfterPropertyChange(const void* key, int64_t old_value) override;
void AddLayerToRegion(ui::Layer* new_layer,
views::LayerRegion region) override;
void RemoveLayerFromRegions(ui::Layer* old_layer) override;
void OnThemeChanged() override;
void Layout(PassKey) override;
// TabStripModelObserver:
void OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) override;
// views::ContextMenuController:
void ShowContextMenuForViewImpl(
views::View* source,
const gfx::Point& point,
ui::mojom::MenuSourceType source_type) override;
// ui::SimpleMenuModel::Delegate:
void ExecuteCommand(int command_id, int event_flags) override;
void MaybeStartFlyingLink(WindowOpenDisposition disposition);
raw_ptr<views::InkDropContainerView> ink_drop_container_;
raw_ptr<views::Label> appearing_label_;
raw_ptr<views::Label> disappearing_label_;
raw_ptr<views::View> border_view_;
std::unique_ptr<TabCounterAnimator> animator_;
raw_ptr<views::Throbber> throbber_;
std::unique_ptr<ui::SimpleMenuModel> menu_model_;
std::unique_ptr<views::MenuRunner> menu_runner_;
std::unique_ptr<InteractionTracker> interaction_tracker_;
const raw_ptr<TabStripModel> tab_strip_model_;
const raw_ptr<BrowserView> browser_view_;
base::CallbackListSubscription link_opened_from_gesture_subscription_;
};
WebUITabCounterButton::WebUITabCounterButton(PressedCallback pressed_callback,
BrowserView* browser_view)
: Button(std::move(pressed_callback)),
tab_strip_model_(browser_view->browser()->tab_strip_model()),
browser_view_(browser_view) {
ConfigureInkDropForToolbar(this);
// Not focusable by default, only for accessibility.
SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
SetProperty(views::kElementIdentifierKey, kToolbarTabCounterButtonElementId);
}
WebUITabCounterButton::~WebUITabCounterButton() {
// TODO(pbos): Revisit explicit removal of InkDrop for classes that override
// Add/RemoveLayerFromRegionsw(). This is done so that the InkDrop doesn't
// access the non-override versions in ~View.
views::InkDrop::Remove(this);
}
void WebUITabCounterButton::UpdateTooltip(int num_tabs) {
SetTooltipText(base::i18n::MessageFormatter::FormatWithNumberedArgs(
l10n_util::GetStringUTF16(IDS_TOOLTIP_WEBUI_TAB_STRIP_TAB_COUNTER),
num_tabs));
}
void WebUITabCounterButton::UpdateColors() {
const auto* const color_provider = GetColorProvider();
const SkColor toolbar_color = color_provider->GetColor(kColorToolbar);
appearing_label_->SetBackgroundColor(toolbar_color);
disappearing_label_->SetBackgroundColor(toolbar_color);
const SkColor normal_text_color =
color_provider->GetColor(kColorToolbarButtonIcon);
const SkColor current_text_color =
GetProperty(user_education::kHasInProductHelpPromoKey)
? color_provider->GetColor(kColorToolbarFeaturePromoHighlight)
: normal_text_color;
appearing_label_->SetEnabledColor(current_text_color);
disappearing_label_->SetEnabledColor(current_text_color);
border_view_->SetBorder(views::CreateRoundedRectBorder(
2,
views::LayoutProvider::Get()->GetCornerRadiusMetric(
views::Emphasis::kMedium),
current_text_color));
}
void WebUITabCounterButton::Init() {
SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToMinimum,
views::MaximumFlexSizeRule::kPreferred)
.WithOrder(1));
const int button_height = GetLayoutConstant(TOOLBAR_BUTTON_HEIGHT);
SetPreferredSize(gfx::Size(button_height, button_height));
ink_drop_container_ =
AddChildView(std::make_unique<views::InkDropContainerView>());
throbber_ = AddChildView(std::make_unique<views::Throbber>());
throbber_->SetCanProcessEventsWithinSubtree(false);
border_view_ = AddChildView(std::make_unique<views::View>());
border_view_->SetCanProcessEventsWithinSubtree(false);
appearing_label_ =
border_view_->AddChildView(std::make_unique<NumberLabel>());
disappearing_label_ =
border_view_->AddChildView(std::make_unique<NumberLabel>());
animator_ = std::make_unique<TabCounterAnimator>(
appearing_label_, disappearing_label_, border_view_, throbber_);
set_context_menu_controller(this);
menu_model_ = std::make_unique<ui::SimpleMenuModel>(this);
menu_model_->AddItemWithIcon(
WEBUI_TAB_COUNTER_CXMENU_CLOSE_TAB,
l10n_util::GetStringUTF16(
IDS_WEBUI_TAB_STRIP_TAB_COUNTER_CXMENU_CLOSE_TAB),
ui::ImageModel::FromVectorIcon(vector_icons::kCloseIcon,
ui::kColorMenuIcon, gfx::kFaviconSize));
menu_model_->AddSeparator(ui::MenuSeparatorType::NORMAL_SEPARATOR);
menu_model_->AddItemWithIcon(
WEBUI_TAB_COUNTER_CXMENU_NEW_TAB,
l10n_util::GetStringUTF16(IDS_WEBUI_TAB_STRIP_TAB_COUNTER_CXMENU_NEW_TAB),
ui::ImageModel::FromVectorIcon(kAddIcon, ui::kColorMenuIcon,
gfx::kFaviconSize));
menu_runner_ = std::make_unique<views::MenuRunner>(
menu_model_.get(), views::MenuRunner::HAS_MNEMONICS |
views::MenuRunner::CONTEXT_MENU |
views::MenuRunner::FIXED_ANCHOR);
tab_strip_model_->AddObserver(this);
const int tab_count = tab_strip_model_->count();
UpdateTooltip(tab_count);
appearing_label_->SetText(GetTabCounterLabelText(tab_count));
}
void WebUITabCounterButton::AddedToWidget() {
interaction_tracker_ = std::make_unique<InteractionTracker>(GetWidget());
link_opened_from_gesture_subscription_ =
browser_view_->AddOnLinkOpeningFromGestureCallback(
base::BindRepeating(&WebUITabCounterButton::MaybeStartFlyingLink,
base::Unretained(this)));
}
void WebUITabCounterButton::AfterPropertyChange(const void* key,
int64_t old_value) {
View::AfterPropertyChange(key, old_value);
if (key != user_education::kHasInProductHelpPromoKey) {
return;
}
UpdateColors();
}
void WebUITabCounterButton::AddLayerToRegion(ui::Layer* new_layer,
views::LayerRegion region) {
ink_drop_container_->AddLayerToRegion(new_layer, region);
}
void WebUITabCounterButton::RemoveLayerFromRegions(ui::Layer* old_layer) {
ink_drop_container_->RemoveLayerFromRegions(old_layer);
}
void WebUITabCounterButton::OnThemeChanged() {
views::Button::OnThemeChanged();
UpdateColors();
}
void WebUITabCounterButton::Layout(PassKey) {
const gfx::Rect view_bounds = GetLocalBounds();
ink_drop_container_->SetBoundsRect(view_bounds);
// Position views from the outside in (because it's easier).
// Start with the throbber.
const int throbber_height = GetLayoutConstant(LOCATION_BAR_HEIGHT);
gfx::Rect throbber_rect = view_bounds;
throbber_rect.ClampToCenteredSize(
gfx::Size(throbber_height, throbber_height));
throbber_->SetBoundsRect(throbber_rect);
// Next is the rounded rect border around the counter.
constexpr gfx::Size kDesiredBorderSize(22, 22);
gfx::Rect border_bounds = view_bounds;
border_bounds.ClampToCenteredSize(kDesiredBorderSize);
border_view_->SetBoundsRect(border_bounds);
// Finally is the numbers themselves, which nest inside the label view.
appearing_label_->SetBoundsRect(gfx::Rect(kDesiredBorderSize));
disappearing_label_->SetBoundsRect(
gfx::Rect(gfx::Point(0, -kOffscreenLabelDistance), kDesiredBorderSize));
// Adjust label positions for animation.
animator_->LayoutIfAnimating();
}
void WebUITabCounterButton::MaybeStartFlyingLink(
WindowOpenDisposition disposition) {
if (disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB &&
interaction_tracker_ &&
interaction_tracker_->last_interaction_location().has_value()) {
animator_->StartFlyingLinkFrom(
interaction_tracker_->last_interaction_location().value());
}
}
void WebUITabCounterButton::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) {
const int num_tabs = tab_strip_model->count();
UpdateTooltip(num_tabs);
animator_->Animate(num_tabs,
ShouldChangeStartThrobber(tab_strip_model, change));
}
void WebUITabCounterButton::ShowContextMenuForViewImpl(
views::View* source,
const gfx::Point& point,
ui::mojom::MenuSourceType source_type) {
menu_runner_->RunMenuAt(GetWidget(), nullptr,
border_view_->GetBoundsInScreen(),
views::MenuAnchorPosition::kTopRight, source_type);
}
void WebUITabCounterButton::ExecuteCommand(int command_id, int event_flags) {
switch (command_id) {
case WEBUI_TAB_COUNTER_CXMENU_CLOSE_TAB: {
tab_strip_model_->CloseWebContentsAt(
tab_strip_model_->active_index(),
TabCloseTypes::CLOSE_USER_GESTURE |
TabCloseTypes::CLOSE_CREATE_HISTORICAL_TAB);
break;
}
case WEBUI_TAB_COUNTER_CXMENU_NEW_TAB:
tab_strip_model_->delegate()->AddTabAt(GURL(), -1, true);
break;
default:
NOTREACHED();
}
}
BEGIN_METADATA(WebUITabCounterButton)
END_METADATA
} // namespace
std::unique_ptr<views::View> CreateWebUITabCounterButton(
views::Button::PressedCallback pressed_callback,
BrowserView* browser_view) {
auto tab_counter = std::make_unique<WebUITabCounterButton>(
std::move(pressed_callback), browser_view);
tab_counter->Init();
return tab_counter;
}