910e62b5创建于 1月15日历史提交
// 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/toolbar/split_tabs_button.h"

#include <memory>

#include "base/check_op.h"
#include "base/containers/fixed_flat_map.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/actions/chrome_action_id.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/tabs/split_tab_menu_model.h"
#include "chrome/browser/ui/tabs/split_tab_metrics.h"
#include "chrome/browser/ui/tabs/split_tab_util.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/toolbar/pinned_action_toolbar_button_menu_model.h"
#include "chrome/browser/ui/views/toolbar/pinned_toolbar_button_status_indicator.h"
#include "chrome/browser/ui/views/toolbar/toolbar_button.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
#include "components/tabs/public/split_tab_data.h"
#include "components/tabs/public/split_tab_visual_data.h"
#include "components/tabs/public/tab_interface.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/menu_source_utils.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/geometry/rect.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/controls/button/menu_button_controller.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/view_class_properties.h"

namespace {
// Width of the status indicator shown across the button.
constexpr int kStatusIndicatorWidth = 14;
// Height of the status indicator shown across the button.
constexpr int kStatusIndicatorHeight = 2;
// Spacing between the button's icon and the status indicator.
constexpr int kStatusIndicatorSpacing = 1;
}  // namespace

DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(SplitTabsToolbarButton,
                                      kUpdatePinStateMenu);

SplitTabsToolbarButton::SplitTabsToolbarButton(Browser* browser)
    : ToolbarButton(
          views::Button::PressedCallback(),
          std::make_unique<PinnedActionToolbarButtonMenuModel>(browser,
                                                               kActionSplitTab),
          nullptr,
          false),
      browser_(browser) {
  SetProperty(views::kElementIdentifierKey,
              kToolbarSplitTabsToolbarButtonElementId);
  set_menu_identifier(kUpdatePinStateMenu);
  SetButtonController(std::make_unique<views::MenuButtonController>(
      this,
      base::BindRepeating(&SplitTabsToolbarButton::ButtonPressed,
                          base::Unretained(this)),
      std::make_unique<views::Button::DefaultButtonControllerDelegate>(this)));
  pin_state_.Init(
      prefs::kPinSplitTabButton, browser_->profile()->GetPrefs(),
      base::BindRepeating(&SplitTabsToolbarButton::UpdateButtonVisibility,
                          base::Unretained(this)));
  views::View* const image_container = image_container_view();
  status_indicator_ =
      PinnedToolbarButtonStatusIndicator::Install(image_container);
  status_indicator_->SetColorId(kColorToolbarActionItemEngaged,
                                kColorToolbarButtonIconInactive);
  UpdateButtonVisibility();
  split_tab_menu_ = std::make_unique<SplitTabMenuModel>(
      browser_->tab_strip_model(),
      SplitTabMenuModel::MenuSource::kToolbarButton);
  browser->tab_strip_model()->AddObserver(this);
}

SplitTabsToolbarButton::~SplitTabsToolbarButton() {
  browser_->tab_strip_model()->RemoveObserver(this);
}

void SplitTabsToolbarButton::OnTabStripModelChanged(
    TabStripModel* tab_strip_model,
    const TabStripModelChange& change,
    const TabStripSelectionChange& selection) {
  if (selection.active_tab_changed()) {
    UpdateButtonVisibility();
  }
}

void SplitTabsToolbarButton::OnSplitTabChanged(const SplitTabChange& change) {
  if (change.type == SplitTabChange::Type::kAdded ||
      change.type == SplitTabChange::Type::kRemoved ||
      change.type == SplitTabChange::Type::kContentsChanged) {
    UpdateButtonVisibility();
  }
}

void SplitTabsToolbarButton::Layout(PassKey) {
  LayoutSuperclass<ToolbarButton>(this);
  gfx::Rect status_rect(kStatusIndicatorWidth, kStatusIndicatorHeight);
  const gfx::Rect image_container_bounds =
      image_container_view()->GetLocalBounds();
  const int new_x =
      image_container_bounds.x() +
      (image_container_bounds.width() - kStatusIndicatorWidth) / 2;
  const int new_y = image_container_bounds.bottom() + kStatusIndicatorSpacing;
  status_rect.set_origin(gfx::Point(new_x, new_y));
  status_indicator_->SetBoundsRect(status_rect);
}

void SplitTabsToolbarButton::UpdateIcon() {
  const std::optional<VectorIcons>& icons = GetVectorIcons();
  if (!icons.has_value() || !GetColorProvider()) {
    return;
  }

  if (IsActiveTabInSplit()) {
    const gfx::VectorIcon& icon = ui::TouchUiController::Get()->touch_ui()
                                      ? icons->touch_icon
                                      : icons->icon;
    SkColor engaged_color =
        GetColorProvider()->GetColor(kColorToolbarActionItemEngaged);
    UpdateIconsWithColors(icon, engaged_color, engaged_color, engaged_color,
                          GetForegroundColor(ButtonState::STATE_DISABLED));
  } else {
    ToolbarButton::UpdateIcon();
  }
}

const std::optional<ToolbarButton::VectorIcons>&
SplitTabsToolbarButton::GetIconsForTesting() {
  return GetVectorIcons();
}

bool SplitTabsToolbarButton::IsActiveTabInSplit() {
  TabStripModel* const tab_strip_model = browser_->tab_strip_model();
  return tab_strip_model && tab_strip_model->GetActiveTab() &&
         tab_strip_model->GetActiveTab()->IsSplit();
}

void SplitTabsToolbarButton::ButtonPressed(const ui::Event& event) {
  if (IsActiveTabInSplit()) {
    menu_runner_ = std::make_unique<views::MenuRunner>(
        split_tab_menu_.get(), views::MenuRunner::HAS_MNEMONICS);
    menu_runner_->RunMenuAt(
        GetWidget(),
        static_cast<views::MenuButtonController*>(button_controller()),
        GetAnchorBoundsInScreen(), views::MenuAnchorPosition::kTopLeft,
        ui::GetMenuSourceTypeForEvent(event));
  } else {
    chrome::NewSplitTab(browser_,
                        split_tabs::SplitTabCreatedSource::kToolbarButton);
  }
}

void SplitTabsToolbarButton::UpdateButtonVisibility() {
  const bool is_active_tab_in_split = IsActiveTabInSplit();
  UpdateButtonIcon();
  UpdateStatusIndicator(is_active_tab_in_split);
  SetVisible(pin_state_.GetValue() || is_active_tab_in_split);
  UpdateAccessibilityRole(is_active_tab_in_split);
  UpdateAccessibilityLabel(is_active_tab_in_split);
}

void SplitTabsToolbarButton::UpdateButtonIcon() {
  TabStripModel* const tab_strip_model = browser_->tab_strip_model();
  tabs::TabInterface* const active_tab = tab_strip_model->GetActiveTab();
  if (active_tab && active_tab->IsSplit()) {
    const split_tabs::SplitTabActiveLocation location =
        split_tabs::GetLastActiveTabLocation(tab_strip_model,
                                             active_tab->GetSplit().value());
    constexpr auto icons =
        base::MakeFixedFlatMap<split_tabs::SplitTabActiveLocation,
                               const gfx::VectorIcon*>({
            {split_tabs::SplitTabActiveLocation::kStart, &kSplitSceneLeftIcon},
            {split_tabs::SplitTabActiveLocation::kEnd, &kSplitSceneRightIcon},
            {split_tabs::SplitTabActiveLocation::kTop, &kSplitSceneUpIcon},
            {split_tabs::SplitTabActiveLocation::kBottom, &kSplitSceneDownIcon},
        });
    SetVectorIcon(*icons.at(location));
  } else {
    SetVectorIcon(kSplitSceneIcon);
  }
}

void SplitTabsToolbarButton::UpdateStatusIndicator(bool show_status_indicator) {
  if (show_status_indicator) {
    status_indicator_->Show();
  } else {
    status_indicator_->Hide();
  }
}

void SplitTabsToolbarButton::UpdateAccessibilityRole(bool has_menu) {
  auto role =
      has_menu ? ax::mojom::Role::kPopUpButton : ax::mojom::Role::kButton;

  if (role == GetViewAccessibility().GetCachedRole()) {
    return;
  }

  GetViewAccessibility().SetRole(role);
  GetViewAccessibility().SetHasPopup(has_menu ? ax::mojom::HasPopup::kMenu
                                              : ax::mojom::HasPopup::kFalse);
}

void SplitTabsToolbarButton::UpdateAccessibilityLabel(bool is_enabled) {
  auto string_id = is_enabled ? IDS_ACCNAME_SPLIT_TABS_TOOLBAR_BUTTON_ENABLED
                              : IDS_ACCNAME_SPLIT_TABS_TOOLBAR_BUTTON_PINNED;

  GetViewAccessibility().SetName(l10n_util::GetStringUTF16(string_id));
  SetTooltipText(l10n_util::GetStringUTF16(string_id));
}

BEGIN_METADATA(SplitTabsToolbarButton)
END_METADATA