Commit 10bb7842 authored by Charlene Yan's avatar Charlene Yan Committed by Commit Bot

[Tab Groups] Adding menu items to tab group header bubble.

Translation screenshots followed from these instructions:
https://g.co/chrome/translation

This needs lots of polishing most notably:
- animations on each action
- polish hover highlight

Bug: 989174
Change-Id: I88658fc0587c1d4fa951c21a7166e1ff1443d884
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1856727
Commit-Queue: Charlene Yan <cyan@chromium.org>
Reviewed-by: default avatarTaylor Bergquist <tbergquist@chromium.org>
Cr-Commit-Position: refs/heads/master@{#706995}
parent 69690da6
......@@ -6216,6 +6216,30 @@ the Bookmarks menu.">
</message>
</if>
<!-- Tab Group Header Context Menu -->
<if expr="not use_titlecase">
<message name="IDS_TAB_GROUP_HEADER_CXMENU_NEW_TAB_IN_GROUP" desc="The label of the tab group header context menu item for adding a new tab into the current group.">
New tab in group
</message>
<message name="IDS_TAB_GROUP_HEADER_CXMENU_UNGROUP" desc="The label of the tab group header context menu item for ungrouping the current group.">
Ungroup
</message>
<message name="IDS_TAB_GROUP_HEADER_CXMENU_CLOSE_GROUP" desc="The label of the tab group header context menu item for closing all tabs in the current group.">
Close group
</message>
</if>
<if expr="use_titlecase">
<message name="IDS_TAB_GROUP_HEADER_CXMENU_NEW_TAB_IN_GROUP" desc="In Title Case: The label of the tab group header context menu item for adding a new tab into the current group.">
New Tab in Group
</message>
<message name="IDS_TAB_GROUP_HEADER_CXMENU_UNGROUP" desc="In Title Case: The label of the tab group header context menu item for ungrouping the current group.">
Ungroup
</message>
<message name="IDS_TAB_GROUP_HEADER_CXMENU_CLOSE_GROUP" desc="In Title Case: The label of the tab group header context menu item for closing all tabs in the current group.">
Close Group
</message>
</if>
<!-- Application window menu -->
<if expr="not use_titlecase">
<message name="IDS_APP_MENU_RELOAD" desc="The reload menu in application windows">
......
d9a476b4b939529db264dc55dc7975c5e822ff01
\ No newline at end of file
0d53c6f818e10191f4fcec953cfd73104ab78e5f
\ No newline at end of file
83d6e76428b3ee7f955e478b59cb305c1927f679
\ No newline at end of file
......@@ -315,6 +315,15 @@ void BrowserTabStripController::CloseTab(int model_index,
TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
}
void BrowserTabStripController::UngroupAllTabsInGroup(TabGroupId group) {
model_->RemoveFromGroup(ListTabsInGroup(group));
}
void BrowserTabStripController::AddNewTabInGroup(TabGroupId group) {
const std::vector<int> tabs = ListTabsInGroup(group);
model_->delegate()->AddTabAt(GURL(), tabs.back() + 1, true, group);
}
void BrowserTabStripController::MoveTab(int start_index, int final_index) {
model_->MoveWebContentsAt(start_index, final_index, false);
}
......
......@@ -61,6 +61,8 @@ class BrowserTabStripController : public TabStripController,
void AddSelectionFromAnchorTo(int model_index) override;
bool BeforeCloseTab(int model_index, CloseTabSource source) override;
void CloseTab(int model_index, CloseTabSource source) override;
void UngroupAllTabsInGroup(TabGroupId group) override;
void AddNewTabInGroup(TabGroupId group) override;
void MoveTab(int start_index, int final_index) override;
void ShowContextMenuForTab(Tab* tab,
const gfx::Point& p,
......
......@@ -84,6 +84,10 @@ void FakeBaseTabStripController::SetVisualDataForGroup(
fake_group_data_ = visual_data;
}
void FakeBaseTabStripController::UngroupAllTabsInGroup(TabGroupId group) {}
void FakeBaseTabStripController::AddNewTabInGroup(TabGroupId group) {}
std::vector<int> FakeBaseTabStripController::ListTabsInGroup(
TabGroupId group) const {
std::vector<int> result;
......
......@@ -61,7 +61,9 @@ class FakeBaseTabStripController : public TabStripController {
TabGroupId group_id) const override;
void SetVisualDataForGroup(TabGroupId group,
TabGroupVisualData visual_data) override;
std::vector<int> ListTabsInGroup(TabGroupId group_id) const override;
std::vector<int> ListTabsInGroup(TabGroupId group) const override;
void UngroupAllTabsInGroup(TabGroupId group) override;
void AddNewTabInGroup(TabGroupId group) override;
bool IsFrameCondensed() const override;
bool HasVisibleBackgroundTabShapes() const override;
bool EverHasVisibleBackgroundTabShapes() const override;
......
......@@ -191,6 +191,12 @@ class TabController {
virtual void SetVisualDataForGroup(TabGroupId group,
TabGroupVisualData visual_data) = 0;
virtual void CloseAllTabsInGroup(TabGroupId group) = 0;
virtual void UngroupAllTabsInGroup(TabGroupId group) = 0;
virtual void AddNewTabInGroup(TabGroupId group) = 0;
protected:
virtual ~TabController() {}
};
......
......@@ -18,20 +18,33 @@
#include "chrome/browser/ui/tabs/tab_group_visual_data.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/browser/ui/views/hover_button.h"
#include "chrome/browser/ui/views/hover_button_controller.h"
#include "chrome/browser/ui/views/tabs/color_picker_view.h"
#include "chrome/browser/ui/views/tabs/tab_controller.h"
#include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
#include "chrome/grit/generated_resources.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/separator.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/view_class_properties.h"
namespace {
constexpr int TAB_GROUP_HEADER_CXMENU_NEW_TAB_IN_GROUP = 13;
constexpr int TAB_GROUP_HEADER_CXMENU_UNGROUP = 14;
constexpr int TAB_GROUP_HEADER_CXMENU_CLOSE_GROUP = 15;
constexpr gfx::Insets kDefaultBorderInsets = gfx::Insets(12);
// Returns our hard-coded set of colors.
const std::vector<std::pair<SkColor, base::string16>>& GetColorPickerList() {
static const base::NoDestructor<
......@@ -47,10 +60,31 @@ const std::vector<std::pair<SkColor, base::string16>>& GetColorPickerList() {
return *list;
}
std::unique_ptr<views::LabelButton> CreateMenuItem(
int button_id,
const base::string16& name,
views::ButtonListener* listener) {
auto button = std::make_unique<views::LabelButton>(
listener, name, views::style::CONTEXT_BUTTON);
button->SetID(button_id);
button->SetButtonController(std::make_unique<HoverButtonController>(
button.get(), listener,
std::make_unique<views::Button::DefaultButtonControllerDelegate>(
button.get())));
// Items within a menu should not show focus rings.
button->SetInstallFocusRingOnFocus(false);
button->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
button->SetBorder(views::CreateEmptyBorder(kDefaultBorderInsets));
button->SetInkDropMode(views::InkDropHostView::InkDropMode::ON);
button->set_ink_drop_base_color(HoverButton::GetInkDropColor(button.get()));
return button;
}
} // namespace
// static
views::Widget* TabGroupEditorBubbleView::Show(views::View* anchor_view,
views::Widget* TabGroupEditorBubbleView::Show(TabGroupHeader* anchor_view,
TabController* tab_controller,
TabGroupId group) {
views::Widget* const widget = BubbleDialogDelegateView::CreateBubble(
......@@ -80,20 +114,45 @@ int TabGroupEditorBubbleView::GetDialogButtons() const {
}
TabGroupEditorBubbleView::TabGroupEditorBubbleView(
views::View* anchor_view,
TabGroupHeader* anchor_view,
TabController* tab_controller,
TabGroupId group)
: tab_controller_(tab_controller),
group_(group),
title_field_controller_(this) {
title_field_controller_(this),
menu_button_listener_(tab_controller, anchor_view, group) {
SetAnchorView(anchor_view);
// Title insets assume there is content (and thus have no bottom padding). Use
// dialog insets to get the bottom margin back.
set_title_margins(
ChromeLayoutProvider::Get()->GetInsetsMetric(views::INSETS_DIALOG));
set_margins(gfx::Insets());
const auto* layout_provider = ChromeLayoutProvider::Get();
const TabGroupVisualData* current_data =
tab_controller_->GetVisualDataForGroup(group_);
// Layout vertically with margin collapsing. This allows us to use spacer
// views with |DISTANCE_UNRELATED_CONTROL_VERTICAL| margins without worrying
// about the default |DISTANCE_RELATED_CONTROL_VERTICAL| spacing.
auto container_layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets(),
layout_provider->GetDistanceMetric(
views::DISTANCE_RELATED_CONTROL_VERTICAL),
true /* collapse_margins_spacing */);
container_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStretch);
views::View* group_modifier_container =
AddChildView(std::make_unique<views::View>());
group_modifier_container->SetBorder(
views::CreateEmptyBorder(kDefaultBorderInsets));
group_modifier_container->SetLayoutManager(std::move(container_layout));
// Add the text field for editing the title.
title_field_ = AddChildView(std::make_unique<views::Textfield>());
title_field_ = group_modifier_container->AddChildView(
std::make_unique<views::Textfield>());
title_field_->SetDefaultWidthInChars(15);
title_field_->SetText(current_data->title());
title_field_->SetAccessibleName(base::ASCIIToUTF16("Group title"));
......@@ -106,21 +165,36 @@ TabGroupEditorBubbleView::TabGroupEditorBubbleView(
views::DISTANCE_UNRELATED_CONTROL_VERTICAL),
0));
color_selector_ = AddChildView(std::make_unique<ColorPickerView>(
GetColorPickerList(), base::Bind(&TabGroupEditorBubbleView::UpdateGroup,
base::Unretained(this))));
// Layout vertically with margin collapsing. This allows us to use spacer
// views with |DISTANCE_UNRELATED_CONTROL_VERTICAL| margins without worrying
// about the default |DISTANCE_RELATED_CONTROL_VERTICAL| spacing.
auto layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets(),
layout_provider->GetDistanceMetric(
views::DISTANCE_RELATED_CONTROL_VERTICAL),
true /* collapse_margins_spacing */);
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStretch);
SetLayoutManager(std::move(layout));
color_selector_ =
group_modifier_container->AddChildView(std::make_unique<ColorPickerView>(
GetColorPickerList(),
base::Bind(&TabGroupEditorBubbleView::UpdateGroup,
base::Unretained(this))));
AddChildView(std::make_unique<views::Separator>());
std::unique_ptr<views::LabelButton> new_tab_menu_item = CreateMenuItem(
TAB_GROUP_HEADER_CXMENU_NEW_TAB_IN_GROUP,
l10n_util::GetStringUTF16(IDS_TAB_GROUP_HEADER_CXMENU_NEW_TAB_IN_GROUP),
&menu_button_listener_);
AddChildView(std::move(new_tab_menu_item));
std::unique_ptr<views::LabelButton> ungroup_menu_item = CreateMenuItem(
TAB_GROUP_HEADER_CXMENU_UNGROUP,
l10n_util::GetStringUTF16(IDS_TAB_GROUP_HEADER_CXMENU_UNGROUP),
&menu_button_listener_);
AddChildView(std::move(ungroup_menu_item));
std::unique_ptr<views::LabelButton> close_menu_item = CreateMenuItem(
TAB_GROUP_HEADER_CXMENU_CLOSE_GROUP,
l10n_util::GetStringUTF16(IDS_TAB_GROUP_HEADER_CXMENU_CLOSE_GROUP),
&menu_button_listener_);
AddChildView(std::move(close_menu_item));
views::FlexLayout* layout_manager_ =
SetLayoutManager(std::make_unique<views::FlexLayout>());
layout_manager_->SetOrientation(views::LayoutOrientation::kVertical)
.SetIgnoreDefaultMainAxisMargins(true);
}
TabGroupEditorBubbleView::~TabGroupEditorBubbleView() = default;
......@@ -142,3 +216,38 @@ void TabGroupEditorBubbleView::TitleFieldController::ContentsChanged(
DCHECK_EQ(sender, parent_->title_field_);
parent_->UpdateGroup();
}
TabGroupEditorBubbleView::ButtonListener::ButtonListener(
TabController* tab_controller,
TabGroupHeader* anchor_view,
TabGroupId group)
: tab_controller_(tab_controller),
anchor_view_(anchor_view),
group_(group) {}
void TabGroupEditorBubbleView::ButtonListener::ButtonPressed(
views::Button* sender,
const ui::Event& event) {
switch (sender->GetID()) {
case TAB_GROUP_HEADER_CXMENU_NEW_TAB_IN_GROUP:
tab_controller_->AddNewTabInGroup(group_);
break;
case TAB_GROUP_HEADER_CXMENU_UNGROUP:
anchor_view_->RemoveObserverFromWidget(sender->GetWidget());
tab_controller_->UngroupAllTabsInGroup(group_);
break;
case TAB_GROUP_HEADER_CXMENU_CLOSE_GROUP:
tab_controller_->CloseAllTabsInGroup(group_);
break;
default:
NOTREACHED();
}
// In the case of closing the tabs in a group or ungrouping the tabs, the
// widget should be closed because it is no longer applicable. In the case of
// opening a new tab in the group, the widget is closed to allow users to
// continue their work in their newly created tab.
sender->GetWidget()->CloseWithReason(
views::Widget::ClosedReason::kUnspecified);
}
......@@ -7,7 +7,9 @@
#include "base/strings/string16.h"
#include "chrome/browser/ui/tabs/tab_group_id.h"
#include "chrome/browser/ui/views/tabs/tab_group_header.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/controls/textfield/textfield_controller.h"
......@@ -27,7 +29,7 @@ class TabGroupEditorBubbleView : public views::BubbleDialogDelegateView {
public:
// Shows the editor for |group|. Returns an *unowned* pointer to the
// bubble's widget.
static views::Widget* Show(views::View* anchor_view,
static views::Widget* Show(TabGroupHeader* anchor_view,
TabController* tab_controller,
TabGroupId group);
......@@ -38,7 +40,7 @@ class TabGroupEditorBubbleView : public views::BubbleDialogDelegateView {
int GetDialogButtons() const override;
private:
TabGroupEditorBubbleView(views::View* anchor_view,
TabGroupEditorBubbleView(TabGroupHeader* anchor_view,
TabController* tab_controller,
TabGroupId group);
~TabGroupEditorBubbleView() override;
......@@ -63,6 +65,24 @@ class TabGroupEditorBubbleView : public views::BubbleDialogDelegateView {
};
TitleFieldController title_field_controller_;
class ButtonListener : public views::ButtonListener {
public:
explicit ButtonListener(TabController* tab_controller,
TabGroupHeader* anchor_view,
TabGroupId group);
// views::ButtonListener:
void ButtonPressed(views::Button* sender, const ui::Event& event) override;
private:
TabController* const tab_controller_;
TabGroupHeader* anchor_view_;
const TabGroupId group_;
};
ButtonListener menu_button_listener_;
views::Textfield* title_field_;
ColorPickerView* color_selector_;
......
......@@ -116,6 +116,10 @@ void TabGroupHeader::VisualsChanged() {
title_chip_->GetPreferredSize())));
}
void TabGroupHeader::RemoveObserverFromWidget(views::Widget* widget) {
widget->RemoveObserver(&editor_bubble_tracker_);
}
void TabGroupHeader::EditorBubbleTracker::Opened(views::Widget* bubble_widget) {
DCHECK(bubble_widget);
DCHECK(!is_open_);
......
......@@ -34,6 +34,9 @@ class TabGroupHeader : public TabSlotView {
// Updates our visual state according to the TabGroupVisualData for our group.
void VisualsChanged();
// Removes {editor_bubble_tracker_} from observing the widget.
void RemoveObserverFromWidget(views::Widget* widget);
private:
// Calculate the width for this View.
int CalculateWidth() const;
......
......@@ -1827,6 +1827,23 @@ void TabStrip::SetVisualDataForGroup(TabGroupId group,
controller_->SetVisualDataForGroup(group, visual_data);
}
void TabStrip::CloseAllTabsInGroup(TabGroupId group) {
UpdateHoverCard(nullptr);
std::vector<int> tabs = controller_->ListTabsInGroup(group);
for (int i = tabs.size() - 1; i >= 0; i--) {
controller_->CloseTab(tabs[i], CLOSE_TAB_FROM_MOUSE);
}
}
void TabStrip::UngroupAllTabsInGroup(TabGroupId group) {
UpdateHoverCard(nullptr);
controller_->UngroupAllTabsInGroup(group);
}
void TabStrip::AddNewTabInGroup(TabGroupId group) {
controller_->AddNewTabInGroup(group);
}
///////////////////////////////////////////////////////////////////////////////
// TabStrip, views::AccessiblePaneView overrides:
......
......@@ -294,6 +294,9 @@ class TabStrip : public views::AccessiblePaneView,
TabGroupId group) const override;
void SetVisualDataForGroup(TabGroupId group,
TabGroupVisualData visual_data) override;
void CloseAllTabsInGroup(TabGroupId group) override;
void UngroupAllTabsInGroup(TabGroupId group) override;
void AddNewTabInGroup(TabGroupId group) override;
// MouseWatcherListener:
void MouseMovedOutOfHost() override;
......
......@@ -78,6 +78,12 @@ class TabStripController {
// Closes the tab at the specified index in the model.
virtual void CloseTab(int index, CloseTabSource source) = 0;
// Ungroups the tabs at the specified index in the model.
virtual void UngroupAllTabsInGroup(TabGroupId group) = 0;
// Adds a new tab to end of the tab group.
virtual void AddNewTabInGroup(TabGroupId group) = 0;
// Moves the tab at |start_index| so that it is now at |final_index|, sliding
// any tabs in between left or right as appropriate.
virtual void MoveTab(int start_index, int final_index) = 0;
......
......@@ -127,6 +127,12 @@ class FakeTabController : public TabController {
void SetVisualDataForGroup(TabGroupId group,
TabGroupVisualData visual_data) override {}
void CloseAllTabsInGroup(TabGroupId group) override {}
void UngroupAllTabsInGroup(TabGroupId group) override {}
void AddNewTabInGroup(TabGroupId group) override {}
void SetTabColors(SkColor bg_color_active,
SkColor fg_color_active,
SkColor bg_color_inactive,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment