blob: a81a2993deaeefec8d3957a9caa9b2c1abb5f43c [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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_layout_helper.h"
#include <set>
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_style.h"
#include "chrome/browser/ui/views/tabs/tab.h"
#include "chrome/browser/ui/views/tabs/tab_animation_state.h"
#include "chrome/browser/ui/views/tabs/tab_group_header.h"
#include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
#include "chrome/browser/ui/views/tabs/tab_strip_layout.h"
#include "chrome/browser/ui/views/tabs/tab_style_views.h"
#include "ui/base/material_design/material_design_controller.h"
#include "ui/views/view_model.h"
namespace {
const TabSizeInfo& GetTabSizeInfo() {
static TabSizeInfo tab_size_info, touch_tab_size_info;
TabSizeInfo* info = ui::MaterialDesignController::touch_ui()
? &touch_tab_size_info
: &tab_size_info;
if (info->standard_size.IsEmpty()) {
info->pinned_tab_width = TabStyle::GetPinnedWidth();
info->min_active_width = TabStyleViews::GetMinimumActiveWidth();
info->min_inactive_width = TabStyleViews::GetMinimumInactiveWidth();
info->standard_size =
gfx::Size(TabStyle::GetStandardWidth(), GetLayoutConstant(TAB_HEIGHT));
info->tab_overlap = TabStyle::GetTabOverlap();
}
return *info;
}
} // namespace
TabStripLayoutHelper::TabStripLayoutHelper()
: active_tab_width_(TabStyle::GetStandardWidth()),
inactive_tab_width_(TabStyle::GetStandardWidth()),
first_non_pinned_tab_index_(0),
first_non_pinned_tab_x_(0) {}
TabStripLayoutHelper::~TabStripLayoutHelper() = default;
int TabStripLayoutHelper::GetPinnedTabCount(
const views::ViewModelT<Tab>* tabs) const {
int pinned_count = 0;
while (pinned_count < tabs->view_size() &&
tabs->view_at(pinned_count)->data().pinned) {
pinned_count++;
}
return pinned_count;
}
namespace {
// Helper types for UpdateIdealBounds.
enum class TabSlotType {
kTab,
kGroupHeader,
};
struct TabSlot {
static TabSlot CreateForTab(int tab, bool pinned) {
TabSlot slot;
slot.type = TabSlotType::kTab;
slot.tab = tab;
slot.pinned = pinned;
return slot;
}
static TabSlot CreateForGroupHeader(TabGroupId group, bool pinned) {
TabSlot slot;
slot.type = TabSlotType::kGroupHeader;
slot.group = group;
slot.pinned = pinned;
return slot;
}
int GetTab() const {
DCHECK(tab.has_value());
return tab.value();
}
TabGroupId GetGroup() const {
DCHECK(group.has_value());
return group.value();
}
TabSlotType type;
base::Optional<int> tab;
base::Optional<TabGroupId> group;
bool pinned;
};
} // namespace
void TabStripLayoutHelper::UpdateIdealBounds(
TabStripController* controller,
views::ViewModelT<Tab>* tabs,
std::map<TabGroupId, TabGroupHeader*> group_headers,
int available_width) {
std::vector<base::Optional<TabGroupId>> tab_to_group_mapping(
tabs->view_size());
for (const auto& header_pair : group_headers) {
const TabGroupId group = header_pair.first;
std::vector<int> tabs = controller->ListTabsInGroup(group);
for (int tab : tabs)
tab_to_group_mapping[tab] = group;
}
const int num_pinned_tabs = GetPinnedTabCount(tabs);
const int active_tab_index = controller->GetActiveIndex();
std::vector<TabSlot> slots;
std::set<TabGroupId> headers_already_added;
int active_index_in_slots = TabStripModel::kNoTab;
for (int i = 0; i < tabs->view_size(); ++i) {
const bool pinned = i < num_pinned_tabs;
base::Optional<TabGroupId> group = tab_to_group_mapping[i];
if (group.has_value() &&
!base::Contains(headers_already_added, group.value())) {
// Start of a group.
slots.push_back(TabSlot::CreateForGroupHeader(group.value(), pinned));
headers_already_added.insert(group.value());
}
slots.push_back(TabSlot::CreateForTab(i, pinned));
if (i == active_tab_index)
active_index_in_slots = slots.size() - 1;
}
std::vector<TabAnimationState> ideal_animation_states;
for (int i = 0; i < int{slots.size()}; ++i) {
ideal_animation_states.push_back(TabAnimationState::ForIdealTabState(
TabAnimationState::TabOpenness::kOpen,
slots[i].pinned ? TabAnimationState::TabPinnedness::kPinned
: TabAnimationState::TabPinnedness::kUnpinned,
i == active_index_in_slots
? TabAnimationState::TabActiveness::kActive
: TabAnimationState::TabActiveness::kInactive,
0));
}
const std::vector<gfx::Rect> bounds = CalculateTabBounds(
GetTabSizeInfo(), ideal_animation_states, available_width);
DCHECK_EQ(slots.size(), bounds.size());
for (size_t i = 0; i < bounds.size(); ++i) {
const TabSlot& slot = slots[i];
switch (slot.type) {
case TabSlotType::kTab:
tabs->set_ideal_bounds(slot.GetTab(), bounds[i]);
UpdateCachedTabWidth(i, bounds[i].width(), active_index_in_slots);
break;
case TabSlotType::kGroupHeader:
group_headers[slot.GetGroup()]->SetBoundsRect(bounds[i]);
break;
}
}
}
void TabStripLayoutHelper::UpdateIdealBoundsForPinnedTabs(
views::ViewModelT<Tab>* tabs) {
const int pinned_tab_count = GetPinnedTabCount(tabs);
first_non_pinned_tab_index_ = pinned_tab_count;
first_non_pinned_tab_x_ = 0;
if (pinned_tab_count > 0) {
std::vector<TabAnimationState> ideal_animation_states;
for (int tab_index = 0; tab_index < pinned_tab_count; tab_index++) {
ideal_animation_states.push_back(TabAnimationState::ForIdealTabState(
TabAnimationState::TabOpenness::kOpen,
TabAnimationState::TabPinnedness::kPinned,
TabAnimationState::TabActiveness::kInactive, 0));
}
const std::vector<gfx::Rect> tab_bounds =
CalculatePinnedTabBounds(GetTabSizeInfo(), ideal_animation_states);
for (int i = 0; i < pinned_tab_count; ++i)
tabs->set_ideal_bounds(i, tab_bounds[i]);
}
}
int TabStripLayoutHelper::LayoutTabs(views::ViewModelT<Tab>* tabs,
std::vector<TabAnimationState> tab_states,
int available_width,
int active_tab_model_index) {
std::vector<gfx::Rect> bounds =
CalculateTabBounds(GetTabSizeInfo(), tab_states, available_width);
// TODO(958173): Assume for now that there are no closing tabs.
// TODO(958173): Assume for now that there are no group headers.
DCHECK_EQ(bounds.size(), static_cast<size_t>(tabs->view_size()));
int trailing_x = 0;
for (size_t i = 0; i < bounds.size(); i++) {
if (tabs->view_at(i)->dragging())
continue;
tabs->view_at(i)->SetBoundsRect(bounds[i]);
trailing_x = std::max(trailing_x, bounds[i].right());
// TODO(958173): We shouldn't need to update the cached widths here, since
// they're also updated in UpdateIdealBounds. However, tests will fail
// without this line; we should investigate why.
UpdateCachedTabWidth(i, bounds[i].width(), active_tab_model_index);
}
return trailing_x;
}
void TabStripLayoutHelper::UpdateCachedTabWidth(int tab_index,
int tab_width,
int active_tab_index) {
if (tab_index == active_tab_index)
active_tab_width_ = tab_width;
else
inactive_tab_width_ = tab_width;
}