Commit 105ce465 authored by Brett Wilson's avatar Brett Wilson Committed by Commit Bot

Add basic grouing features.

Adds simple opener-based grouping to the experimental tab controller.

Clicks are not handled for activation. Tabs are rendered overlapped
properly.

Bug: 778461
Change-Id: Ic93cb109875d815018a6197e35b10b358959ac02
Reviewed-on: https://chromium-review.googlesource.com/777735Reviewed-by: default avatarScott Violet <sky@chromium.org>
Commit-Queue: Brett Wilson <brettw@chromium.org>
Cr-Commit-Position: refs/heads/master@{#517913}
parent 597b2a11
......@@ -58,10 +58,11 @@ TabDataExperimental::~TabDataExperimental() = default;
TabDataExperimental& TabDataExperimental::operator=(TabDataExperimental&&) =
default;
const base::string16& TabDataExperimental::GetTitle() const {
DCHECK(type() == Type::kSingle);
base::string16 TabDataExperimental::GetTitle() const {
// TODO(brettw) this will need to use TabUIHelper.
return contents_->GetTitle();
if (contents_)
return contents_->GetTitle();
return base::string16();
}
bool TabDataExperimental::CountsAsViewIndex() const {
......
......@@ -49,12 +49,13 @@ class TabDataExperimental {
TabDataExperimental* parent() { return parent_; }
Type type() const { return type_; }
void set_type(Type t) { type_ = t; }
bool expanded() const { return expanded_; }
content::WebContents* contents() { return contents_; }
// Valid when type() == kSingle or kHubAndSpoke.
const base::string16& GetTitle() const;
base::string16 GetTitle() const;
// Returns true if this tab data itself is counted as a enumerable item when
// going through the view.
......
......@@ -379,12 +379,30 @@ void TabStripModelExperimental::InsertWebContentsAt(
delegate_->WillAddWebContents(contents);
bool active = (add_types & ADD_ACTIVE) != 0;
TabDataExperimental* data = nullptr;
if ((add_types & ADD_INHERIT_GROUP) && active_index() >= 0 &&
active_index() < count()) {
// Add as a child following opener.
TabDataExperimental* parent = GetDataForViewIndex(active_index());
if (parent->type() == TabDataExperimental::Type::kSingle) {
// Promote parent to hub-and-spoke.
parent->set_type(TabDataExperimental::Type::kHubAndSpoke);
for (auto& observer : exp_observers_)
observer.TabChanged(parent);
}
parent->children_.push_back(std::make_unique<TabDataExperimental>(
parent, TabDataExperimental::Type::kSingle, contents, this));
data = parent->children_.back().get();
} else {
// Add at toplevel.
tabs_.push_back(std::make_unique<TabDataExperimental>(
nullptr, TabDataExperimental::Type::kSingle, contents, this));
data = tabs_.back().get();
}
// Always insert tabs at the end for now (so parent is always null).
TabDataExperimental* parent = nullptr;
tabs_.emplace_back(std::make_unique<TabDataExperimental>(
parent, TabDataExperimental::Type::kSingle, contents, this));
const TabDataExperimental* data = tabs_.back().get();
UpdateViewCount();
index = tab_view_count_ - 1;
......@@ -407,7 +425,6 @@ bool TabStripModelExperimental::CloseWebContentsAt(int view_index,
uint32_t close_types) {
ViewIterator found = FindViewIndex(view_index);
DCHECK(found != end());
DCHECK(found->type() == TabDataExperimental::Type::kSingle);
content::WebContents* closing = found->contents_;
if (closing)
InternalCloseTabs(base::span<content::WebContents*>(&closing, 1));
......@@ -527,8 +544,21 @@ TabDataExperimental* TabStripModelExperimental::GetDataForViewIndex(
return &*found;
}
int TabStripModelExperimental::GetViewIndexForData(
const TabDataExperimental* data) const {
int view_index = 0;
for (const auto& cur : *this) {
if (&cur == data)
return view_index;
++view_index;
}
return kNoTab;
}
void TabStripModelExperimental::ActivateTabAt(int index, bool user_gesture) {
DCHECK(ContainsIndex(index));
if (!ContainsIndex(index))
return;
ui::ListSelectionModel new_model = selection_model_;
new_model.SetSelectedIndex(index);
SetSelection(std::move(new_model),
......@@ -649,7 +679,6 @@ bool TabStripModelExperimental::IsTabPinned(int index) const {
}
bool TabStripModelExperimental::IsTabBlocked(int index) const {
NOTIMPLEMENTED();
return false;
}
......@@ -689,6 +718,10 @@ void TabStripModelExperimental::AddWebContents(content::WebContents* contents,
int index,
ui::PageTransition transition,
int add_types) {
// Force group inheritance for link click transitions.
if (ui::PageTransitionTypeIncludingQualifiersIs(transition,
ui::PAGE_TRANSITION_LINK))
add_types |= ADD_INHERIT_GROUP;
InsertWebContentsAt(index, contents, add_types);
}
......@@ -769,6 +802,7 @@ void TabStripModelExperimental::DetachWebContents(
NOTREACHED(); // WebContents not found in this model.
return;
}
TabDataExperimental* data = &*found;
bool was_selected;
if (view_index == kNoTab)
......@@ -777,12 +811,31 @@ void TabStripModelExperimental::DetachWebContents(
was_selected = IsTabSelected(view_index);
int next_selected_index = view_index;
if (found->parent_) {
// Erase in parent.
found->parent_->children_.erase(found->parent_->children_.begin() +
found.inner_index_);
if (data->parent_) {
TabDataExperimental* parent = data->parent_;
// Erase out of the parent.
parent->children_.erase(parent->children_.begin() + found.inner_index_);
if (parent->children_.empty()) {
if (parent->type() == TabDataExperimental::Type::kHubAndSpoke) {
// Erasing the last child of a hub and spoke one converts it back to
// a single.
parent->set_type(TabDataExperimental::Type::kSingle);
for (auto& observer : exp_observers_)
observer.TabChanged(parent);
} else {
DCHECK(parent->type() == TabDataExperimental::Type::kGroup);
// TODO(brettw) remove group. Notifications might be tricky.
}
}
} else if (data->type() == TabDataExperimental::Type::kHubAndSpoke) {
// Removing the "hub" from a hub and spoke converts to a group.
// TODO(brettw) remove the parent if it's empty!
data->set_type(TabDataExperimental::Type::kGroup);
data->contents_ =
nullptr; // TODO(brettw) does this delete things properly?
for (auto& observer : exp_observers_)
observer.TabChanged(data);
} else {
// Just remove from tabs.
tabs_.erase(tabs_.begin() + found.toplevel_index_);
......
......@@ -196,6 +196,9 @@ class TabStripModelExperimental : public TabStripModel {
const TabDataExperimental* GetDataForViewIndex(int view_index) const;
TabDataExperimental* GetDataForViewIndex(int view_index);
// Returns kNoTab if there isn't a view index for this data.
int GetViewIndexForData(const TabDataExperimental* data) const;
void AddExperimentalObserver(TabStripModelExperimentalObserver* observer);
void RemoveExperimentalObserver(TabStripModelExperimentalObserver* observer);
......
......@@ -264,8 +264,7 @@ class Tab::TabCloseButton : public views::ImageButton,
explicit TabCloseButton(Tab* tab)
: views::ImageButton(tab),
tab_(tab) {
SetEventTargeter(
std::unique_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
}
~TabCloseButton() override {}
......@@ -461,8 +460,7 @@ Tab::Tab(TabController* controller, gfx::AnimationContainer* container)
title_->SetText(CoreTabHelper::GetDefaultTitle());
AddChildView(title_);
SetEventTargeter(
std::unique_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
throbber_ = new ThrobberView(this);
throbber_->SetVisible(false);
......
......@@ -6,11 +6,13 @@
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/tab_contents/core_tab_helper.h"
#include "chrome/browser/ui/tabs/tab_data_experimental.h"
#include "chrome/browser/ui/tabs/tab_strip_model_experimental.h"
#include "chrome/browser/ui/view_ids.h"
#include "components/grit/components_scaled_resources.h"
#include "ui/views/border.h"
#include "ui/views/controls/label.h"
#include "ui/views/masked_targeter_delegate.h"
#include "ui/views/widget/widget.h"
namespace {
......@@ -27,9 +29,12 @@ float GetTabEndcapWidth() {
} // namespace
TabExperimental::TabExperimental(const TabDataExperimental* data)
TabExperimental::TabExperimental(TabStripModelExperimental* model,
const TabDataExperimental* data)
: views::View(),
model_(model),
data_(data),
type_(data->type()),
title_(new views::Label),
hover_controller_(this),
paint_(this) {
......@@ -40,6 +45,8 @@ TabExperimental::TabExperimental(const TabDataExperimental* data)
title_->SetText(CoreTabHelper::GetDefaultTitle());
AddChildView(title_);
SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
// So we get don't get enter/exit on children and don't prematurely stop the
// hover.
set_notify_enter_exit_on_child(true);
......@@ -73,23 +80,84 @@ void TabExperimental::SetSelected(bool selected) {
}
void TabExperimental::DataUpdated() {
type_ = data_->type();
title_->SetText(data_->GetTitle());
}
void TabExperimental::SetGroupLayoutParams(int first_child_begin_x) {
first_child_begin_x_ = first_child_begin_x;
}
int TabExperimental::GetOverlap() {
// We want to overlap the endcap portions entirely.
return gfx::ToCeiledInt(GetTabEndcapWidth());
}
bool TabExperimental::GetHitTestMask(gfx::Path* mask) const {
// When the window is maximized we don't want to shave off the edges or top
// shadow of the tab, such that the user can click anywhere along the top
// edge of the screen to select a tab. Ditto for immersive fullscreen.
const views::Widget* widget = GetWidget();
*mask = paint_.GetBorderPath(
GetWidget()->GetCompositor()->device_scale_factor(), true,
widget && (widget->IsMaximized() || widget->IsFullscreen()),
GetTabEndcapWidth(), size());
return true;
}
void TabExperimental::OnPaint(gfx::Canvas* canvas) {
paint_.PaintTabBackground(canvas, active_, 0, 0, nullptr);
if (type_ == TabDataExperimental::Type::kSingle)
paint_.PaintTabBackground(canvas, active_, 0, 0, nullptr);
else
paint_.PaintGroupBackground(canvas, active_);
}
void TabExperimental::Layout() {
// Space between the favicon and title.
constexpr int kTitleSpacing = 6;
const gfx::Rect bounds = GetContentsBounds();
title_->SetBoundsRect(gfx::Rect(bounds.x() + kTitleSpacing, bounds.y(),
bounds.width() - (kTitleSpacing * 2),
bounds.height()));
int title_left = bounds.x() + kTitleSpacing;
int title_right;
if (first_child_begin_x_ >= 0)
title_right = first_child_begin_x_;
else
title_right = bounds.width() - kTitleSpacing;
title_->SetBoundsRect(gfx::Rect(title_left, bounds.y(),
title_right - title_left, bounds.height()));
}
bool TabExperimental::OnMousePressed(const ui::MouseEvent& event) {
// TODO(brettw) the non-experimental one has some stuff about touch and
// multi-selection here.
if (event.IsOnlyLeftMouseButton())
model_->ActivateTabAt(model_->GetViewIndexForData(data_), true);
return true;
}
void TabExperimental::OnMouseReleased(const ui::MouseEvent& event) {
// Close tab on middle click, but only if the button is released over the tab
// (normal windows behavior is to discard presses of a UI element where the
// releases happen off the element).
if (event.IsMiddleMouseButton()) {
if (HitTestPoint(event.location())) {
// TODO(brettw) old one did PrepareForCloseAt which does some animation
// stuff.
model_->CloseWebContentsAt(
model_->GetViewIndexForData(data_),
TabStripModel::CLOSE_USER_GESTURE |
TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
} else if (closing_) {
// We're animating closed and a middle mouse button was pushed on us but
// we don't contain the mouse anymore. We assume the user is clicking
// quicker than the animation and we should close the tab that falls under
// the mouse.
/* TODO(brettw) fast closing.
Tab* closest_tab = controller_->GetTabAt(this, event.location());
if (closest_tab)
controller_->CloseTab(closest_tab, CLOSE_TAB_FROM_MOUSE);
*/
}
}
}
......@@ -6,24 +6,33 @@
#define CHROME_BROWSER_UI_VIEWS_TABS_TAB_EXPERIMENTAL_H_
#include "base/macros.h"
#include "chrome/browser/ui/tabs/tab_data_experimental.h"
#include "chrome/browser/ui/views/tabs/tab_experimental_paint.h"
#include "ui/views/controls/glow_hover_controller.h"
#include "ui/views/masked_targeter_delegate.h"
#include "ui/views/view.h"
class TabDataExperimental;
class TabStripModelExperimental;
namespace views {
class Label;
}
class TabExperimental : public views::View {
class TabExperimental : public views::MaskedTargeterDelegate,
public views::View {
public:
explicit TabExperimental(const TabDataExperimental* data);
explicit TabExperimental(TabStripModelExperimental* model,
const TabDataExperimental* data);
~TabExperimental() override;
// Will be null when closing.
const TabDataExperimental* data() const { return data_; }
// Cached stuff from data_ that can be used regardless of whether the data
// pointer is null or not.
TabDataExperimental::Type type() const { return type_; }
// The view order is stored on a tab for the use of TabStrip layout and
// computatations. It is not used directly by this class. It represents this
// tab's position in the current layout.
......@@ -52,17 +61,30 @@ class TabExperimental : public views::View {
// to redraw everything.
void DataUpdated();
// Called for group types when layout is done to set the bounds of the
// first tab. This is used to determine some painting parameters.
void SetGroupLayoutParams(int first_child_begin_x);
// Returns the overlap between adjacent tabs.
static int GetOverlap();
private:
// views::MaskedTargeterDelegate:
bool GetHitTestMask(gfx::Path* mask) const override;
// views::View:
void OnPaint(gfx::Canvas* canvas) override;
void Layout() override;
bool OnMousePressed(const ui::MouseEvent& event) override;
void OnMouseReleased(const ui::MouseEvent& event) override;
TabStripModelExperimental* model_;
// Will be null when closing.
const TabDataExperimental* data_;
TabDataExperimental::Type type_;
size_t view_order_ = static_cast<size_t>(-1);
gfx::Rect ideal_bounds_;
......@@ -72,6 +94,9 @@ class TabExperimental : public views::View {
views::Label* title_; // Non-owning (owned by View hierarchy).
// Location of the first child tab. Negative indicates unused.
int first_child_begin_x_ = -1;
views::GlowHoverController hover_controller_;
TabExperimentalPaint paint_;
......
......@@ -26,12 +26,26 @@ class TabExperimentalPaint {
explicit TabExperimentalPaint(views::View* view);
~TabExperimentalPaint();
// Returns a path corresponding to the tab's outer border for a given tab
// |size|, |scale|, and |endcap_width|. If |unscale_at_end| is true, this
// path will be normalized to a 1x scale by scaling by 1/scale before
// returning. If |extend_to_top| is true, the path is extended vertically to
// the top of the tab bounds. The caller uses this for Fitts' Law purposes
// in maximized/fullscreen mode.
gfx::Path GetBorderPath(float scale,
bool unscale_at_end,
bool extend_to_top,
float endcap_width,
const gfx::Size& size) const;
void PaintTabBackground(gfx::Canvas* canvas,
bool active,
int fill_id,
int y_offset,
gfx::Path* clip);
void PaintGroupBackground(gfx::Canvas* canvas, bool active);
private:
class BackgroundCache {
public:
......
......@@ -11,6 +11,7 @@
#include "base/compiler_specific.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/containers/span.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
......@@ -423,6 +424,12 @@ class TabStripExperimental : public TabStrip,
// Ensures that view_order_ corresponds to the latest tabs_ structure.
void EnsureViewOrderUpToDate() const;
// Recursive backend for EnsureViewOrderUpToDate.
void ComputeViewOrder(
base::span<const std::unique_ptr<TabDataExperimental>> tabs,
const TabDataExperimental* selected,
std::vector<TabExperimental*>* output) const;
// -- Member Variables ------------------------------------------------------
TabStripModelExperimental* model_;
......@@ -437,8 +444,9 @@ class TabStripExperimental : public TabStrip,
// this will cause them to be painted in order of their memory address!
base::flat_set<TabExperimental*> closing_tabs_;
// Cached ordered vector for painting the tabs. This will include everything
// in the tabs_ map and also the items in closing_tabs_.
// Cached ordered vector for painting the tabs. The topmost view is at the
// front(). This will include everything in the tabs_ map and also the items
// in closing_tabs_.
//
// This is computed by EnsureViewOrderUpToDate().
mutable std::vector<TabExperimental*> view_order_;
......
......@@ -1934,10 +1934,10 @@ void TabStripImpl::SetTabBoundsForDrag(
void TabStripImpl::AddMessageLoopObserver() {
if (!mouse_watcher_.get()) {
mouse_watcher_.reset(new views::MouseWatcher(
mouse_watcher_ = std::make_unique<views::MouseWatcher>(
new views::MouseWatcherViewHost(
this, gfx::Insets(0, 0, kTabStripAnimationVSlop, 0)),
this));
this);
}
mouse_watcher_->Start();
}
......
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