blob: 851f8c2a464cd1e5f561aac6f93c405e4ca72ebb [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.
#ifndef CHROME_BROWSER_UI_VIEWS_TABS_VERTICAL_VERTICAL_TAB_DRAG_HANDLER_H_
#define CHROME_BROWSER_UI_VIEWS_TABS_VERTICAL_VERTICAL_TAB_DRAG_HANDLER_H_
#include <memory>
#include <set>
#include <vector>
#include "base/callback_list.h"
#include "chrome/browser/ui/views/frame/browser_root_view.h"
#include "chrome/browser/ui/views/tabs/dragging/tab_drag_context.h"
#include "chrome/browser/ui/views/tabs/dragging/tab_drag_controller.h"
#include "chrome/browser/ui/views/tabs/tab_strip_types.h"
#include "components/tab_groups/tab_group_id.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/events/event.h"
#include "ui/gfx/geometry/vector2d.h"
class TabCollectionNode;
class TabStripModel;
class VerticalTabLinkDropHandler;
enum class DragPositionHint {
kBefore, // The drag is before the drag target.
kAfter // The drag is after the drag target.
};
// Interface for views to interact with drag handling.
class VerticalTabDragHandler {
public:
virtual ~VerticalTabDragHandler() = default;
// Initializes a drag using `node` as the tab node that received `event`.
virtual void InitializeDrag(TabCollectionNode& node,
const ui::MouseEvent& event) = 0;
// Triggers updates to tab dragging state based on the latest mouse event.
// Returns a bool indicating whether the drag was successfully handled.
virtual bool ContinueDrag(views::View& event_source_view,
const ui::MouseEvent& event) = 0;
// Ends the drag, if started.
virtual void EndDrag(EndDragReason reason) = 0;
// Handles tab strip model updates to reflect a drag over a given node.
// Position hint is used to determine where the drag is, relative to the node.
virtual void HandleDraggedTabsOverNode(
const TabCollectionNode& node,
std::optional<DragPositionHint> position_hint) = 0;
// Handles tab strip model updates to reflect dragged tabs entering a node.
// This reparents them to become direct children of the node.
virtual void HandleDraggedTabsIntoNode(const TabCollectionNode& node) = 0;
// Handles tab strip model updates to reflect a drag exiting a group.
// Position hint is used to determine where the drag is, relative to the node.
virtual void HandleDraggedTabsOutOfGroup(const TabCollectionNode& node,
DragPositionHint position_hint) = 0;
// Handles the case where tabs are dragged to the end of the tab strip, which
// is a special case because there is no node there to handle the drag.
virtual void HandleDraggedTabsAtEndOfTabStrip() = 0;
// Returns the drag context for this handler.
virtual TabDragContext* GetDragContext() = 0;
// Whether this is is handling a drag.
virtual bool IsDragging() const = 0;
// Returns true if `view` belongs to a TabCollectionNode currently being
// dragged.
virtual bool IsViewDragging(const views::View& view) const = 0;
// Returns true if there is an ongoing drag that includes a pinned tab.
virtual bool IsDraggingPinnedTabs() const = 0;
// Returns true if there is an ongoing drag where a group is being moved.
virtual bool IsDraggingGroups() const = 0;
// Returns true if the drag is currently at the end of the tab strip.
virtual bool IsDraggingAtEndOfTabStrip() const = 0;
// For vertical tabs, `TabSlotView` doesn't represent the actual tab
// view. This method converts `view` to its actual tab view, or nullptr
// if this handler doesn't manage it.
virtual views::View* ViewFromTabSlot(TabSlotView* view) const = 0;
// Returns the starting position of the view when dragging started.
// The position is in screen coordinates.
virtual std::optional<gfx::Vector2d> GetOffsetFromSourceAtDragStart(
views::View* view) const = 0;
// Returns the DropIndex for a given node and position hint.
// For tab nodes, a nullopt position hint indicates that the drop is over the
// middle of the tab and should be interpreted as a "replace" operation.
virtual std::optional<BrowserRootView::DropIndex> GetLinkDropIndexForNode(
const TabCollectionNode& node,
std::optional<DragPositionHint> position_hint) const = 0;
// Called before a tab is added to the tabstrip. Cancels the current drag if
// there is one.
virtual void OnTabWillBeAdded() = 0;
// Called before a tab is removed from the tabstrip. Cancels the current drag
// if the tab is one of the dragged tabs.
virtual void OnTabWillBeRemoved(content::WebContents* contents) = 0;
};
// Implements a minimal drag context to interact with the central
// `TabDragController`.
class VerticalTabDragHandlerImpl : public VerticalTabDragHandler,
public TabDragContext {
METADATA_HEADER(VerticalTabDragHandlerImpl, TabDragContext)
public:
explicit VerticalTabDragHandlerImpl(TabStripModel& tab_strip_model,
TabCollectionNode& root_node);
~VerticalTabDragHandlerImpl() override;
VerticalTabDragHandlerImpl(const VerticalTabDragHandlerImpl&) = delete;
VerticalTabDragHandlerImpl& operator=(const VerticalTabDragHandlerImpl&) =
delete;
// VerticalTabDragHandler
void InitializeDrag(TabCollectionNode& node,
const ui::MouseEvent& event) override;
bool ContinueDrag(views::View& event_source_view,
const ui::MouseEvent& event) override;
void EndDrag(EndDragReason reason) override;
void HandleDraggedTabsOverNode(
const TabCollectionNode& node,
std::optional<DragPositionHint> position_hint) override;
void HandleDraggedTabsIntoNode(const TabCollectionNode& node) override;
void HandleDraggedTabsOutOfGroup(const TabCollectionNode& node,
DragPositionHint position_hint) override;
void HandleDraggedTabsAtEndOfTabStrip() override;
TabDragContext* GetDragContext() override;
bool IsDragging() const override;
bool IsViewDragging(const views::View& view) const override;
bool IsDraggingPinnedTabs() const override;
bool IsDraggingGroups() const override;
bool IsDraggingAtEndOfTabStrip() const override;
views::View* ViewFromTabSlot(TabSlotView* view) const override;
std::optional<gfx::Vector2d> GetOffsetFromSourceAtDragStart(
views::View* view) const override;
std::optional<BrowserRootView::DropIndex> GetLinkDropIndexForNode(
const TabCollectionNode& node,
std::optional<DragPositionHint> position_hint) const override;
void OnTabWillBeAdded() override;
void OnTabWillBeRemoved(content::WebContents* contents) override;
// TabDragContext
bool CanAcceptEvent(const ui::Event& event) override;
void OnMouseReleased(const ui::MouseEvent& event) override;
bool OnMouseDragged(const ui::MouseEvent& event) override;
void OnMouseCaptureLost() override;
TabDragContext* GetContextForNewBrowser(
BrowserView* browser_view) const override;
TabSlotView* GetTabForContents(content::WebContents* contents) override;
content::WebContents* GetContentsForTab(TabSlotView* tab) override;
bool IsTabDetachable(const TabSlotView* view) const override;
TabSlotView* GetTabGroupHeader(
const tab_groups::TabGroupId& group_id) override;
TabStripModel* GetTabStripModel() override;
TabDragController* GetDragController() override;
void OwnDragController(
std::unique_ptr<TabDragController> controller) override;
std::unique_ptr<TabDragController> ReleaseDragController() override;
void DestroyDragController() override;
void StartedDragging(const std::vector<TabSlotView*>& views) override;
void DraggedTabsDetached() override;
void StoppedDragging() override;
void SetDragControllerCallbackForTesting(
base::OnceCallback<void(TabDragController*)> callback) override;
TabDragPositioningDelegate* GetPositioningDelegate() override;
// The following overrides are necessary to support SystemDnD.
bool CanDrop(const OSExchangeData& data) override;
bool GetDropFormats(int* formats,
std::set<ui::ClipboardFormatType>* format_types) override;
void OnDragEntered(const ui::DropTargetEvent& event) override;
int OnDragUpdated(const ui::DropTargetEvent& event) override;
void OnDragExited() override;
private:
// Encapsulates data needed to initialize a drag session.
struct DragInitData {
DragInitData();
~DragInitData();
DragInitData(const DragInitData&);
DragInitData& operator=(const DragInitData&);
raw_ptr<TabSlotView> source_dragged_view = nullptr;
std::vector<TabSlotView*> dragged_views;
ui::ListSelectionModel list_selection_model;
};
// Helpers for initialize a drag, according to the type of `source_node`.
// These helpers will create the `TabSlotView`s for the dragged
// tabs as needed.
DragInitData GetDragInitDataForTabDrag(TabCollectionNode& source_node);
DragInitData GetDragInitDataForGroupHeaderDrag(
TabCollectionNode& source_node);
std::map<tab_groups::TabGroupId, TabSlotView*> GetFullySelectedGroups(
const std::vector<tabs::TabInterface*>& selected_tabs);
TabCollectionNode* GetNodeForContents(content::WebContents* contents);
TabCollectionNode* GetNodeForTabGroup(const tab_groups::TabGroupId& group_id);
const TabCollectionNode* GetNodeForTabGroup(
const tab_groups::TabGroupId& group_id) const;
// Creates a `TabSlotView` for `node`, used for compatibility with the
// core dragging system. The created slot view is cached in `slot_views_`.
TabSlotView& GetOrCreateSlotViewForNode(TabCollectionNode& node);
// Resets member variables that track a drag being managed by this handler.
void ResetDragState();
// Cleans up state tracked by this handler for a given node.
void OnNodeWillDestroy(TabCollectionNode& node);
// Handlers for drag operations over various node types.
void HandleTabDragOverTab(const TabCollectionNode& node);
void HandleTabDragOverSplit(const TabCollectionNode& node);
void HandleTabDragOverGroup(const TabCollectionNode& node);
// Returns the group id of the dragged group header, or null if the drag
// was not initiated by a group header.
std::optional<tab_groups::TabGroupId> GetDraggingGroupHeaderId() const;
const raw_ref<TabStripModel> tab_strip_model_;
const raw_ref<TabCollectionNode> root_node_;
std::unique_ptr<VerticalTabLinkDropHandler> link_drop_handler_;
// Null if this handler is not managing a dragging session.
std::unique_ptr<TabDragController> drag_controller_ = nullptr;
// A mapping from nodes to their `TabSlotView`, used for compatibility
// with the core dragging system.
std::map<raw_ptr<const TabCollectionNode>, raw_ptr<TabSlotView>> slot_views_;
std::vector<base::CallbackListSubscription> node_destroyed_callbacks_;
};
#endif // CHROME_BROWSER_UI_VIEWS_TABS_VERTICAL_VERTICAL_TAB_DRAG_HANDLER_H_