blob: 7d1a16dce78a4a7aa9d63bf76c4584cfdacc14a9 [file]
// 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/tabs/vertical/vertical_tab_drag_handler.h"
#include <algorithm>
#include <memory>
#include "base/check_deref.h"
#include "base/functional/bind.h"
#include "base/notreached.h"
#include "base/types/to_address.h"
#include "chrome/browser/ui/tabs/tab_group_model.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/tab_strip_region_view.h"
#include "chrome/browser/ui/views/frame/vertical_tab_strip_region_view.h"
#include "chrome/browser/ui/views/tabs/dragging/drag_session_data.h"
#include "chrome/browser/ui/views/tabs/dragging/tab_drag_controller.h"
#include "chrome/browser/ui/views/tabs/tab_slot_view.h"
#include "chrome/browser/ui/views/tabs/tab_strip_types.h"
#include "chrome/browser/ui/views/tabs/vertical/tab_collection_node.h"
#include "chrome/browser/ui/views/tabs/vertical/vertical_tab_link_drop_handler.h"
#include "chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller.h"
#include "components/tab_groups/tab_group_id.h"
#include "components/tabs/public/split_tab_collection.h"
#include "components/tabs/public/tab_collection.h"
#include "components/tabs/public/tab_group.h"
#include "components/tabs/public/tab_group_tab_collection.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/class_property.h"
#include "ui/base/clipboard/clipboard_constants.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/list_selection_model.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/views/view_utils.h"
DEFINE_UI_CLASS_PROPERTY_TYPE(gfx::Vector2d*)
namespace {
// Stores the offset of the view's initial position to the target position
// it should be, relative to the source dragged view's origin.
DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(gfx::Vector2d, kOffsetAtTabDragStart)
// This is a shim around `TabCollectionNode` to make it compatible with the
// tab dragging logic in `TabDragController`. Longer term, core tab dragging
// logic will be updated to remove the `TabSlotView` dependency, which
// should make this shim view no longer needed.
class VerticalTabSlotView : public TabSlotView {
METADATA_HEADER(VerticalTabSlotView, TabSlotView)
public:
explicit VerticalTabSlotView(const TabCollectionNode& node) : node_(node) {}
~VerticalTabSlotView() override = default;
VerticalTabSlotView(const VerticalTabSlotView&) = delete;
VerticalTabSlotView& operator=(const VerticalTabSlotView&) = delete;
TabSlotView::ViewType GetTabSlotViewType() const override {
switch (node_->type()) {
case TabCollectionNode::Type::TAB:
return ViewType::kTab;
case TabCollectionNode::Type::GROUP:
return ViewType::kTabGroupHeader;
default:
NOTREACHED();
}
}
TabSizeInfo GetTabSizeInfo() const override { return TabSizeInfo(); }
const TabCollectionNode& node() const { return node_.get(); }
private:
const raw_ref<const TabCollectionNode> node_;
};
BEGIN_METADATA(VerticalTabSlotView)
END_METADATA
ui::mojom::DragEventSource EventSourceFromEvent(const ui::LocatedEvent& event) {
return event.IsGestureEvent() ? ui::mojom::DragEventSource::kTouch
: ui::mojom::DragEventSource::kMouse;
}
const TabGroup& TabGroupDataFromNode(const TabCollectionNode& node) {
CHECK_EQ(node.type(), TabCollectionNode::Type::GROUP);
const auto* collection = static_cast<const tabs::TabGroupTabCollection*>(
std::get<const tabs::TabCollection*>(node.GetNodeData()));
CHECK(collection);
const auto* group_data = collection->GetTabGroup();
CHECK(group_data);
return *group_data;
}
} // namespace
VerticalTabDragHandlerImpl::VerticalTabDragHandlerImpl(
TabStripModel& tab_strip_model,
TabCollectionNode& root_node)
: tab_strip_model_(tab_strip_model),
root_node_(root_node),
link_drop_handler_(
std::make_unique<VerticalTabLinkDropHandler>(tab_strip_model)) {}
VerticalTabDragHandlerImpl::~VerticalTabDragHandlerImpl() = default;
void VerticalTabDragHandlerImpl::InitializeDrag(TabCollectionNode& node,
const ui::MouseEvent& event) {
ResetDragState();
drag_controller_ = std::make_unique<TabDragController>();
DragInitData drag_init_data;
switch (node.type()) {
case TabCollectionNode::Type::TAB:
drag_init_data = GetDragInitDataForTabDrag(node);
break;
case TabCollectionNode::Type::GROUP:
drag_init_data = GetDragInitDataForGroupHeaderDrag(node);
break;
default:
NOTREACHED();
}
CHECK(drag_init_data.source_dragged_view);
const gfx::Point offset_from_source = event.location();
if (drag_controller_->Init(this, drag_init_data.source_dragged_view,
drag_init_data.dragged_views, offset_from_source,
drag_init_data.list_selection_model,
EventSourceFromEvent(event)) ==
TabDragController::Liveness::kDeleted) {
ResetDragState();
}
}
VerticalTabDragHandlerImpl::DragInitData::DragInitData() = default;
VerticalTabDragHandlerImpl::DragInitData::~DragInitData() = default;
VerticalTabDragHandlerImpl::DragInitData::DragInitData(
const VerticalTabDragHandlerImpl::DragInitData&) = default;
VerticalTabDragHandlerImpl::DragInitData&
VerticalTabDragHandlerImpl::DragInitData::operator=(
const VerticalTabDragHandlerImpl::DragInitData&) = default;
VerticalTabDragHandlerImpl::DragInitData
VerticalTabDragHandlerImpl::GetDragInitDataForTabDrag(
TabCollectionNode& source_node) {
const auto& selected_tabs_indices =
tab_strip_model_->selection_model().GetListSelectionModel();
std::vector<tabs::TabInterface*> selected_tabs;
for (int index : selected_tabs_indices.selected_indices()) {
selected_tabs.push_back(tab_strip_model_->GetTabAtIndex(index));
}
DragInitData drag_init_data;
drag_init_data.dragged_views.reserve(selected_tabs.size());
const auto* source_tab =
std::get<const tabs::TabInterface*>(source_node.GetNodeData());
CHECK(source_tab);
std::map<tab_groups::TabGroupId, TabSlotView*> dragged_groups;
if (!source_tab->IsPinned()) {
// A TabSlotView must be added for each dragged group, in addition to the
// tabs themselves. These must be inserted into `dragged_views` in the
// order they appear within the tab strip.
dragged_groups = GetFullySelectedGroups(selected_tabs);
}
// Track the node and build a shim view for each selected node.
for (tabs::TabInterface* tab : selected_tabs) {
// Filter out selections that don't match the pinned state of the latest
// selected tab.
if (source_tab->IsPinned() != tab->IsPinned()) {
continue;
}
if (auto group_id = tab->GetGroup()) {
// If the tab belongs to a group that is fully selected, then add the
// group's TabSlotView first.
if (auto it = dragged_groups.find(*group_id);
it != dragged_groups.end()) {
drag_init_data.dragged_views.push_back(it->second);
dragged_groups.erase(it);
}
}
size_t index = tab_strip_model_->GetIndexOfTab(tab);
drag_init_data.list_selection_model.AddIndexToSelection(index);
TabCollectionNode* selected_node =
root_node_->GetNodeForHandle(tab->GetHandle());
CHECK(selected_node);
auto* slot_view = &GetOrCreateSlotViewForNode(*selected_node);
slot_view->SetBoundsRect(selected_node->view()->GetLocalBounds());
drag_init_data.dragged_views.push_back(slot_view);
if (selected_node == &source_node) {
drag_init_data.source_dragged_view = slot_view;
drag_init_data.list_selection_model.set_active(index);
}
}
drag_init_data.dragged_views.shrink_to_fit();
return drag_init_data;
}
VerticalTabDragHandlerImpl::DragInitData
VerticalTabDragHandlerImpl::GetDragInitDataForGroupHeaderDrag(
TabCollectionNode& source_node) {
DragInitData drag_init_data;
drag_init_data.source_dragged_view = &GetOrCreateSlotViewForNode(source_node);
drag_init_data.dragged_views.push_back(drag_init_data.source_dragged_view);
for (const auto& child : source_node.children()) {
if (child->type() == TabCollectionNode::Type::SPLIT) {
for (const auto& split_child : child->children()) {
drag_init_data.dragged_views.push_back(
&GetOrCreateSlotViewForNode(*split_child.get()));
}
} else {
drag_init_data.dragged_views.push_back(
&GetOrCreateSlotViewForNode(*child.get()));
}
}
return drag_init_data;
}
std::map<tab_groups::TabGroupId, TabSlotView*>
VerticalTabDragHandlerImpl::GetFullySelectedGroups(
const std::vector<tabs::TabInterface*>& selected_tabs) {
std::map<tab_groups::TabGroupId, int> dragged_group_tab_counts;
for (tabs::TabInterface* tab : selected_tabs) {
if (auto group_id = tab->GetGroup()) {
dragged_group_tab_counts[*group_id]++;
}
}
std::map<tab_groups::TabGroupId, TabSlotView*> selected_groups;
for (const auto& [group_id, dragged_tab_count] : dragged_group_tab_counts) {
const auto* group = tab_strip_model_->group_model()->GetTabGroup(group_id);
CHECK(group);
if (group->tab_count() == dragged_tab_count) {
auto* selected_node = GetNodeForTabGroup(group_id);
auto& slot_view = GetOrCreateSlotViewForNode(*selected_node);
slot_view.SetBoundsRect(selected_node->view()->GetLocalBounds());
selected_groups.insert({group_id, &slot_view});
}
}
return selected_groups;
}
bool VerticalTabDragHandlerImpl::ContinueDrag(views::View& event_source_view,
const ui::MouseEvent& event) {
if (!drag_controller_) {
return false;
}
gfx::Point screen_location(event.location());
ConvertPointToScreen(&event_source_view, &screen_location);
if (drag_controller_->Drag(screen_location) ==
TabDragController::Liveness::kDeleted) {
ResetDragState();
return false;
}
return true;
}
void VerticalTabDragHandlerImpl::EndDrag(EndDragReason reason) {
if (TabDragController::IsSystemDnDSessionRunning()) {
TabDragController::OnSystemDnDEnded();
return;
}
// Let TabDragController decide whether this reason should actually end the
// drag (e.g. capture loss while dragging a detached window) and destroy
// itself when appropriate.
if (drag_controller_) {
drag_controller_->EndDrag(reason);
}
}
void VerticalTabDragHandlerImpl::HandleDraggedTabsOverNode(
const TabCollectionNode& node,
std::optional<DragPositionHint> position_hint) {
CHECK(drag_controller_);
switch (node.type()) {
case TabCollectionNode::Type::TAB:
HandleTabDragOverTab(node);
break;
case TabCollectionNode::Type::SPLIT:
HandleTabDragOverSplit(node);
break;
case TabCollectionNode::Type::GROUP:
HandleTabDragOverGroup(node);
break;
default:
NOTREACHED();
}
}
void VerticalTabDragHandlerImpl::HandleDraggedTabsIntoNode(
const TabCollectionNode& node) {
CHECK(drag_controller_);
const auto& drag_session_data = drag_controller_->GetSessionData();
const TabDragData* source_drag_data =
drag_session_data.source_view_drag_data();
const content::WebContents* source_contents = source_drag_data->contents;
if (!source_contents) {
return;
}
int target_index;
std::optional<tab_groups::TabGroupId> target_group_id = std::nullopt;
if (node.type() == TabCollectionNode::Type::GROUP) {
// If dragging into a group, then either put the dragged tabs into the
// start/end if the source dragged tab is before/after the group, or move
// all dragged tabs to the source dragged tab's position if it's already
// in the group.
const auto& group = TabGroupDataFromNode(node);
target_group_id = group.id();
int source_tab_index =
tab_strip_model_->GetIndexOfWebContents(source_contents);
auto group_indexes = group.ListTabs();
if (source_tab_index < static_cast<int>(group_indexes.start())) {
target_index = group_indexes.start();
} else if (source_tab_index > static_cast<int>(group_indexes.end())) {
target_index = group_indexes.end();
} else {
target_index = source_tab_index;
}
} else if (auto source_tab_group_id =
tabs::TabInterface::GetFromContents(source_contents)
->GetGroup()) {
// If the source dragged tab is in a group, and we're not entering a
// group, then move the dragged tabs to be after the group.
const auto* group =
tab_strip_model_->group_model()->GetTabGroup(*source_tab_group_id);
target_index = group->ListTabs().end();
} else {
target_index = tab_strip_model_->GetIndexOfWebContents(source_contents);
}
int num_dragged_tabs_before_target = 0;
for (const TabDragData& tab_drag_data : drag_session_data.tab_drag_data_) {
const content::WebContents* contents = tab_drag_data.contents;
if (contents &&
tab_strip_model_->GetIndexOfWebContents(contents) < target_index) {
++num_dragged_tabs_before_target;
}
}
target_index = target_index - num_dragged_tabs_before_target;
target_index = std::clamp(target_index, 0, tab_strip_model_->count() - 1);
tab_strip_model_->MoveSelectedTabsTo(target_index, target_group_id);
}
void VerticalTabDragHandlerImpl::HandleDraggedTabsOutOfGroup(
const TabCollectionNode& node,
DragPositionHint position_hint) {
CHECK_EQ(node.type(), TabCollectionNode::Type::GROUP);
const auto& tab_group = TabGroupDataFromNode(node);
const auto& selection_model = tab_strip_model_->selection_model();
int insertion_idx;
switch (position_hint) {
case DragPositionHint::kAfter: {
int last_tab_in_group =
tab_strip_model_->GetIndexOfTab(tab_group.GetLastTab());
insertion_idx =
last_tab_in_group - selection_model.selected_tabs().size() + 1;
break;
}
case DragPositionHint::kBefore: {
int first_tab_in_group =
tab_strip_model_->GetIndexOfTab(tab_group.GetFirstTab());
insertion_idx = first_tab_in_group;
break;
}
default:
NOTREACHED();
}
insertion_idx = std::clamp(insertion_idx, 0, tab_strip_model_->count() - 1);
tab_strip_model_->MoveSelectedTabsTo(insertion_idx, std::nullopt);
}
void VerticalTabDragHandlerImpl::HandleDraggedTabsAtEndOfTabStrip() {
// If the tabs were dragging into the tab strip in an area where they did not
// overlap any nodes then update the model appropriately if the tabs are not
// already at the end.
if (!IsDraggingAtEndOfTabStrip()) {
tab_strip_model_->MoveSelectedTabsTo(tab_strip_model_->count() - 1,
std::nullopt);
}
}
void VerticalTabDragHandlerImpl::HandleTabDragOverTab(
const TabCollectionNode& node) {
const auto* tab = std::get<const tabs::TabInterface*>(node.GetNodeData());
CHECK(tab);
const auto& selection_model = tab_strip_model_->selection_model();
int first_selected_idx =
*selection_model.GetListSelectionModel().selected_indices().cbegin();
int insertion_idx = tab_strip_model_->GetIndexOfTab(tab);
if (first_selected_idx <= insertion_idx) {
insertion_idx -= selection_model.size();
++insertion_idx;
}
insertion_idx = std::clamp(insertion_idx, 0, tab_strip_model_->count() - 1);
if (auto group = GetDraggingGroupHeaderId(); group.has_value()) {
tab_strip_model_->MoveGroupTo(*group, insertion_idx);
} else {
tab_strip_model_->MoveSelectedTabsTo(insertion_idx, tab->GetGroup());
}
}
void VerticalTabDragHandlerImpl::HandleTabDragOverSplit(
const TabCollectionNode& node) {
const auto* split_collection = static_cast<const tabs::SplitTabCollection*>(
std::get<const tabs::TabCollection*>(node.GetNodeData()));
CHECK(split_collection);
split_tabs::SplitTabData* split_data = split_collection->data();
CHECK(split_data);
gfx::Range tab_range = split_data->GetIndexRange();
int first_tab_in_split = tab_range.GetMin();
int last_tab_in_split = tab_range.GetMax();
const auto& selection_model = tab_strip_model_->selection_model();
int first_selected_index =
*selection_model.GetListSelectionModel().selected_indices().cbegin();
int insertion_idx =
(first_selected_index < first_tab_in_split)
? last_tab_in_split - selection_model.selected_tabs().size()
: first_tab_in_split;
if (auto group = GetDraggingGroupHeaderId(); group.has_value()) {
tab_strip_model_->MoveGroupTo(*group, insertion_idx);
} else {
tab_strip_model_->MoveSelectedTabsTo(
insertion_idx, split_data->ListTabs().front()->GetGroup());
}
}
void VerticalTabDragHandlerImpl::HandleTabDragOverGroup(
const TabCollectionNode& node) {
const auto* tab_group =
static_cast<const tabs::TabGroupTabCollection*>(
std::get<const tabs::TabCollection*>(node.GetNodeData()))
->GetTabGroup();
CHECK(tab_group);
const auto& selection_model = tab_strip_model_->selection_model();
int first_tab_in_group =
tab_strip_model_->GetIndexOfTab(tab_group->GetFirstTab());
int last_tab_in_group =
tab_strip_model_->GetIndexOfTab(tab_group->GetLastTab());
int first_selected_index =
*selection_model.GetListSelectionModel().selected_indices().cbegin();
// If dragging over a collapsed group or dragging a group, then
// move the dragged tabs/header before or after the dragged-over group.
int insertion_idx =
(first_selected_index < first_tab_in_group)
? last_tab_in_group - selection_model.selected_tabs().size() + 1
: first_tab_in_group;
if (auto dragged_group = GetDraggingGroupHeaderId()) {
tab_strip_model_->MoveGroupTo(*dragged_group, insertion_idx);
} else {
tab_strip_model_->MoveSelectedTabsTo(insertion_idx, std::nullopt);
}
}
TabDragContext* VerticalTabDragHandlerImpl::GetDragContext() {
return this;
}
bool VerticalTabDragHandlerImpl::IsDragging() const {
// We check if the drag controller is attached to this context instead of
// `started_drag()` because `started_drag()` only becomes true after the
// initial selection reset that occurs when a drag truly begins. If we
// relied on `started_drag()`, the vertical tab strip might incorrectly
// expand a collapsed group during that initial selection change.
return drag_controller_ && drag_controller_->attached_context() == this &&
drag_controller_->active();
}
bool VerticalTabDragHandlerImpl::IsViewDragging(const views::View& view) const {
if (!drag_controller_) {
return false;
}
for (TabSlotView* slot_view :
drag_controller_->GetSessionData().attached_views()) {
if (&view == ViewFromTabSlot(slot_view)) {
return true;
}
}
return false;
}
bool VerticalTabDragHandlerImpl::IsDraggingPinnedTabs() const {
if (!drag_controller_) {
return false;
}
const auto& drag_data = drag_controller_->GetSessionData().tab_drag_data_;
return std::any_of(drag_data.cbegin(), drag_data.cend(),
[](const auto& tab_data) { return tab_data.pinned; });
}
bool VerticalTabDragHandlerImpl::IsDraggingGroups() const {
if (!drag_controller_) {
return false;
}
return !drag_controller_->GetSessionData().dragging_groups.empty();
}
bool VerticalTabDragHandlerImpl::IsDraggingAtEndOfTabStrip() const {
if (!drag_controller_) {
return false;
}
const auto& drag_data = drag_controller_->GetSessionData().tab_drag_data_;
const auto* last_web_contents =
tab_strip_model_->GetWebContentsAt(tab_strip_model_->count() - 1);
return std::any_of(drag_data.cbegin(), drag_data.cend(),
[last_web_contents](const auto& tab_data) {
return tab_data.contents == last_web_contents;
});
}
std::optional<tab_groups::TabGroupId>
VerticalTabDragHandlerImpl::GetDraggingGroupHeaderId() const {
return drag_controller_ ? drag_controller_->GetSessionData().group_header_id()
: std::nullopt;
}
views::View* VerticalTabDragHandlerImpl::ViewFromTabSlot(
TabSlotView* view) const {
CHECK(drag_controller_);
auto* slot_view = views::AsViewClass<VerticalTabSlotView>(view);
CHECK(slot_view);
const TabCollectionNode& node = slot_view->node();
if (node.type() == TabCollectionNode::Type::TAB) {
const auto* tab = std::get<const tabs::TabInterface*>(node.GetNodeData());
CHECK(tab);
if (auto group_id = tab->GetGroup();
group_id && drag_controller_->GetSessionData().dragging_groups.contains(
*group_id)) {
// If the tab belongs to a group that is being dragged, return the group's
// view instead.
const TabCollectionNode* group_node = GetNodeForTabGroup(*group_id);
CHECK(group_node);
return group_node->view();
}
if (tab->IsSplit()) {
// If the dragged tab view is in a split, return the split's tab view
// instead.
const TabCollectionNode* split_node =
root_node_->GetParentNodeForHandle(tab->GetHandle());
CHECK(split_node);
CHECK_EQ(split_node->type(), TabCollectionNode::Type::SPLIT);
return split_node->view();
}
}
return node.view();
}
std::optional<gfx::Vector2d>
VerticalTabDragHandlerImpl::GetOffsetFromSourceAtDragStart(View* view) const {
gfx::Vector2d* offset = view->GetProperty(kOffsetAtTabDragStart);
return offset ? std::make_optional(*offset) : std::nullopt;
}
std::optional<BrowserRootView::DropIndex>
VerticalTabDragHandlerImpl::GetLinkDropIndexForNode(
const TabCollectionNode& node,
std::optional<DragPositionHint> position_hint) const {
return link_drop_handler_->GetDropIndexForNode(node, position_hint);
}
void VerticalTabDragHandlerImpl::OnTabWillBeAdded() {
if (drag_controller_) {
drag_controller_->EndDrag(EndDragReason::kModelAddedTab);
}
}
void VerticalTabDragHandlerImpl::OnTabWillBeRemoved(
content::WebContents* contents) {
if (drag_controller_) {
drag_controller_->OnTabWillBeRemoved(contents);
}
}
bool VerticalTabDragHandlerImpl::CanAcceptEvent(const ui::Event& event) {
// The drag context has to be able to process mouse events during the drag.
// By default, this is predicated on visibility, but the handler should not
// be visible. Instead, defer the check to the parent.
return parent()->CanAcceptEvent(event);
}
bool VerticalTabDragHandlerImpl::OnMouseDragged(const ui::MouseEvent& event) {
return ContinueDrag(*this, event);
}
void VerticalTabDragHandlerImpl::OnMouseReleased(const ui::MouseEvent& event) {
EndDrag(EndDragReason::kComplete);
}
bool VerticalTabDragHandlerImpl::GetDropFormats(
int* formats,
std::set<ui::ClipboardFormatType>* format_types) {
if (!TabDragController::IsSystemDnDSessionRunning()) {
return false;
}
format_types->insert(
ui::ClipboardFormatType::CustomPlatformType(ui::kMimeTypeWindowDrag));
return true;
}
bool VerticalTabDragHandlerImpl::CanDrop(const OSExchangeData& data) {
return TabDragController::IsSystemDnDSessionRunning() &&
data.HasCustomFormat(ui::ClipboardFormatType::CustomPlatformType(
ui::kMimeTypeWindowDrag));
}
void VerticalTabDragHandlerImpl::OnDragEntered(
const ui::DropTargetEvent& event) {
CHECK(TabDragController::IsSystemDnDSessionRunning());
TabDragController::OnSystemDnDUpdated(event);
}
int VerticalTabDragHandlerImpl::OnDragUpdated(
const ui::DropTargetEvent& event) {
// This can be false because we can still receive drag events after
// TabDragController is destroyed due to the asynchronous nature of the
// platform DnD.
if (TabDragController::IsSystemDnDSessionRunning()) {
TabDragController::OnSystemDnDUpdated(event);
return ui::DragDropTypes::DRAG_MOVE;
}
return ui::DragDropTypes::DRAG_NONE;
}
void VerticalTabDragHandlerImpl::OnDragExited() {
// See comment in OnDragUpdated().
if (TabDragController::IsSystemDnDSessionRunning()) {
TabDragController::OnSystemDnDExited();
}
}
void VerticalTabDragHandlerImpl::OnMouseCaptureLost() {
EndDrag(EndDragReason::kCaptureLost);
}
TabDragContext* VerticalTabDragHandlerImpl::GetContextForNewBrowser(
BrowserView* browser_view) const {
return browser_view->tab_strip_view()->GetDragContext();
}
TabSlotView* VerticalTabDragHandlerImpl::GetTabForContents(
content::WebContents* contents) {
TabCollectionNode* node = GetNodeForContents(contents);
return node ? &GetOrCreateSlotViewForNode(*node) : nullptr;
}
content::WebContents* VerticalTabDragHandlerImpl::GetContentsForTab(
TabSlotView* view) {
auto* slot_view = views::AsViewClass<VerticalTabSlotView>(view);
CHECK(slot_view);
if (slot_view->node().type() != TabCollectionNode::Type::TAB) {
return nullptr;
}
const tabs::TabInterface* tab =
std::get<const tabs::TabInterface*>(slot_view->node().GetNodeData());
CHECK(tab);
return tab->GetContents();
}
bool VerticalTabDragHandlerImpl::IsTabDetachable(
const TabSlotView* view) const {
return true;
}
TabSlotView* VerticalTabDragHandlerImpl::GetTabGroupHeader(
const tab_groups::TabGroupId& group_id) {
TabCollectionNode* node = GetNodeForTabGroup(group_id);
return node ? &GetOrCreateSlotViewForNode(*node) : nullptr;
}
TabStripModel* VerticalTabDragHandlerImpl::GetTabStripModel() {
return base::to_address(tab_strip_model_);
}
TabDragController* VerticalTabDragHandlerImpl::GetDragController() {
return drag_controller_.get();
}
void VerticalTabDragHandlerImpl::OwnDragController(
std::unique_ptr<TabDragController> controller) {
drag_controller_ = std::move(controller);
}
std::unique_ptr<TabDragController>
VerticalTabDragHandlerImpl::ReleaseDragController() {
return std::move(drag_controller_);
}
void VerticalTabDragHandlerImpl::DestroyDragController() {
drag_controller_.reset();
}
void VerticalTabDragHandlerImpl::StartedDragging(
const std::vector<TabSlotView*>& views) {
CHECK(drag_controller_);
auto* source_dragged_view = ViewFromTabSlot(drag_controller_->GetSessionData()
.source_view_drag_data()
->attached_view);
CHECK(source_dragged_view);
gfx::Point source_view_origin_in_screen =
source_dragged_view->GetBoundsInScreen().origin();
for (auto* view : views) {
auto* slot_view = views::AsViewClass<VerticalTabSlotView>(view);
CHECK(slot_view);
views::View* dragged_view = ViewFromTabSlot(slot_view);
CHECK(dragged_view);
dragged_view->SetPaintToLayer();
dragged_view->layer()->SetFillsBoundsOpaquely(false);
gfx::Vector2d offset = dragged_view->GetBoundsInScreen().origin() -
source_view_origin_in_screen;
dragged_view->SetProperty(kOffsetAtTabDragStart, offset);
// Update the height to use preferred size because newly added tabs will
// animate in from 0, which affects the window offset for newly-detached
// windows.
gfx::Rect bounds = slot_view->node().view()->GetLocalBounds();
bounds.set_height(slot_view->node().view()->GetPreferredSize({}).height());
slot_view->SetBoundsRect(bounds);
}
}
void VerticalTabDragHandlerImpl::DraggedTabsDetached() {}
void VerticalTabDragHandlerImpl::StoppedDragging() {
for (auto& [_, slot_view] : slot_views_) {
views::View* dragged_view = ViewFromTabSlot(slot_view);
CHECK(dragged_view);
dragged_view->DestroyLayer();
dragged_view->ClearProperty(kOffsetAtTabDragStart);
}
if (!drag_controller_) {
return;
}
const DragSessionData& drag_data = drag_controller_->GetSessionData();
if (!drag_data.group_header_drag_data_.has_value()) {
return;
}
// Offset by 1 to account for the group header.
const int drag_data_index =
1 + drag_data.group_header_drag_data_->active_tab_index_within_group;
const int index = tab_strip_model_->GetIndexOfWebContents(
drag_data.tab_drag_data_[drag_data_index].contents);
// The tabs in the group may have been closed during the drag.
if (index != TabStripModel::kNoTab) {
ui::ListSelectionModel selection;
CHECK_GE(index, 0);
selection.AddIndexToSelection(static_cast<size_t>(index));
selection.set_active(static_cast<size_t>(index));
selection.set_anchor(static_cast<size_t>(index));
tab_strip_model_->SetSelectionFromModel(selection);
}
}
void VerticalTabDragHandlerImpl::SetDragControllerCallbackForTesting(
base::OnceCallback<void(TabDragController*)> callback) {}
TabDragPositioningDelegate*
VerticalTabDragHandlerImpl::GetPositioningDelegate() {
// Positioning is implemented through `TabDragDelegate` on individual
// containers.
return nullptr;
}
TabCollectionNode* VerticalTabDragHandlerImpl::GetNodeForContents(
content::WebContents* contents) {
tabs::TabInterface* tab = tabs::TabInterface::GetFromContents(contents);
CHECK(tab);
return root_node_->GetNodeForHandle(tab->GetHandle());
}
TabCollectionNode* VerticalTabDragHandlerImpl::GetNodeForTabGroup(
const tab_groups::TabGroupId& group_id) {
return const_cast<TabCollectionNode*>(
std::as_const(*this).GetNodeForTabGroup(group_id));
}
const TabCollectionNode* VerticalTabDragHandlerImpl::GetNodeForTabGroup(
const tab_groups::TabGroupId& group_id) const {
const auto* group = tab_strip_model_->group_model()->GetTabGroup(group_id);
CHECK(group);
return root_node_->GetNodeForHandle(group->GetCollectionHandle());
}
TabSlotView& VerticalTabDragHandlerImpl::GetOrCreateSlotViewForNode(
TabCollectionNode& node) {
auto update_tab_slot_view = [&node](TabSlotView& slot_view) -> void {
switch (node.type()) {
case TabCollectionNode::Type::TAB: {
const tabs::TabInterface* tab =
std::get<const tabs::TabInterface*>(node.GetNodeData());
CHECK(tab);
slot_view.SetGroup(tab->GetGroup());
slot_view.SetSplit(tab->GetSplit());
} break;
case TabCollectionNode::Type::GROUP:
slot_view.SetGroup(TabGroupDataFromNode(node).id());
break;
default:
NOTREACHED();
}
slot_view.SetBoundsRect(node.view()->GetLocalBounds());
};
CHECK(node.view());
auto it = slot_views_.find(&node);
if (it != slot_views_.end()) {
update_tab_slot_view(*it->second);
return *it->second;
}
auto tab_slot_view = std::make_unique<VerticalTabSlotView>(node);
auto& tab_slot_view_ref = *tab_slot_view.get();
update_tab_slot_view(tab_slot_view_ref);
slot_views_.insert(
{&node, node.view()->AddChildView(std::move(tab_slot_view))});
node_destroyed_callbacks_.push_back(node.RegisterWillDestroyCallback(
base::BindOnce(&VerticalTabDragHandlerImpl::OnNodeWillDestroy,
base::Unretained(this), std::ref(node))));
tab_slot_view_ref.SetVisible(false);
return tab_slot_view_ref;
}
void VerticalTabDragHandlerImpl::OnNodeWillDestroy(TabCollectionNode& node) {
auto it = slot_views_.find(&node);
CHECK(it != slot_views_.end());
auto view = node.view()->RemoveChildViewT(it->second);
slot_views_.erase(it);
}
void VerticalTabDragHandlerImpl::ResetDragState() {
drag_controller_.reset();
}
BEGIN_METADATA(VerticalTabDragHandlerImpl)
END_METADATA