blob: c9d42fd288aae4a7414e36612c17008291810282 [file] [log] [blame]
// Copyright 2022 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_TAB_CONTAINER_IMPL_H_
#define CHROME_BROWSER_UI_VIEWS_TABS_TAB_CONTAINER_IMPL_H_
#include <memory>
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/timer/timer.h"
#include "chrome/browser/ui/views/frame/browser_root_view.h"
#include "chrome/browser/ui/views/tabs/tab.h"
#include "chrome/browser/ui/views/tabs/tab_container.h"
#include "chrome/browser/ui/views/tabs/tab_group_underline.h"
#include "chrome/browser/ui/views/tabs/tab_group_views.h"
#include "chrome/browser/ui/views/tabs/tab_slot_controller.h"
#include "chrome/browser/ui/views/tabs/tab_slot_view.h"
#include "chrome/browser/ui/views/tabs/tab_strip_layout_helper.h"
#include "components/tab_groups/tab_group_id.h"
#include "tab_container_controller.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/views/animation/bounds_animator.h"
#include "ui/views/animation/bounds_animator_observer.h"
#include "ui/views/mouse_watcher.h"
#include "ui/views/paint_info.h"
#include "ui/views/view.h"
#include "ui/views/view_model.h"
#include "ui/views/view_targeter_delegate.h"
class TabStrip;
class TabHoverCardController;
class TabDragContextBase;
// A View that contains a sequence of Tabs for the TabStrip.
class TabContainerImpl : public TabContainer,
public views::ViewTargeterDelegate,
public views::MouseWatcherListener,
public views::BoundsAnimatorObserver {
public:
METADATA_HEADER(TabContainerImpl);
TabContainerImpl(TabContainerController& controller,
TabHoverCardController* hover_card_controller,
TabDragContextBase* drag_context,
TabSlotController& tab_slot_controller,
views::View* scroll_contents_view);
~TabContainerImpl() override;
// TabContainer:
void SetAvailableWidthCallback(
base::RepeatingCallback<int()> available_width_callback) override;
Tab* AddTab(std::unique_ptr<Tab> tab,
int model_index,
TabPinned pinned) override;
void MoveTab(int from_model_index, int to_model_index) override;
void RemoveTab(int index, bool was_active) override;
void SetTabPinned(int model_index, TabPinned pinned) override;
void SetActiveTab(absl::optional<size_t> prev_active_index,
absl::optional<size_t> new_active_index) override;
Tab* RemoveTabFromViewModel(int model_index) override;
Tab* AddTabToViewModel(Tab* tab, int model_index, TabPinned pinned) override;
void ReturnTabSlotView(TabSlotView* view) override;
void ScrollTabToVisible(int model_index) override;
void ScrollTabContainerByOffset(int offset) override;
void OnGroupCreated(const tab_groups::TabGroupId& group) override;
void OnGroupEditorOpened(const tab_groups::TabGroupId& group) override;
void OnGroupMoved(const tab_groups::TabGroupId& group) override;
void OnGroupContentsChanged(const tab_groups::TabGroupId& group) override;
void OnGroupVisualsChanged(
const tab_groups::TabGroupId& group,
const tab_groups::TabGroupVisualData* old_visuals,
const tab_groups::TabGroupVisualData* new_visuals) override;
void ToggleTabGroup(const tab_groups::TabGroupId& group,
bool is_collapsing,
ToggleTabGroupCollapsedStateOrigin origin) override;
void OnGroupClosed(const tab_groups::TabGroupId& group) override;
void UpdateTabGroupVisuals(tab_groups::TabGroupId group_id) override;
void NotifyTabGroupEditorBubbleOpened() override;
void NotifyTabGroupEditorBubbleClosed() override;
absl::optional<int> GetModelIndexOf(
const TabSlotView* slot_view) const override;
Tab* GetTabAtModelIndex(int index) const override;
int GetTabCount() const override;
absl::optional<int> GetModelIndexOfFirstNonClosingTab(
Tab* tab) const override;
void UpdateHoverCard(
Tab* tab,
TabSlotController::HoverCardUpdateType update_type) override;
void HandleLongTap(ui::GestureEvent* event) override;
bool IsRectInContentArea(const gfx::Rect& rect) override;
absl::optional<ZOrderableTabContainerElement> GetLeadingElementForZOrdering()
const override;
absl::optional<ZOrderableTabContainerElement> GetTrailingElementForZOrdering()
const override;
void OnTabSlotAnimationProgressed(TabSlotView* view) override;
void OnTabCloseAnimationCompleted(Tab* tab) override;
void InvalidateIdealBounds() override;
void AnimateToIdealBounds() override;
bool IsAnimating() const override;
void CancelAnimation() override;
void CompleteAnimationAndLayout() override;
int GetAvailableWidthForTabContainer() const override;
void EnterTabClosingMode(absl::optional<int> override_width,
CloseTabSource source) override;
void ExitTabClosingMode() override;
void SetTabSlotVisibility() override;
bool InTabClose() override;
TabGroupViews* GetGroupViews(tab_groups::TabGroupId group_id) const override;
const std::map<tab_groups::TabGroupId, std::unique_ptr<TabGroupViews>>&
get_group_views_for_testing() const override;
int GetActiveTabWidth() const override;
int GetInactiveTabWidth() const override;
gfx::Rect GetIdealBounds(int model_index) const override;
gfx::Rect GetIdealBounds(tab_groups::TabGroupId group) const override;
// views::View
void Layout() override;
void PaintChildren(const views::PaintInfo& paint_info) override;
gfx::Size GetMinimumSize() const override;
gfx::Size CalculatePreferredSize() const override;
views::View* GetTooltipHandlerForPoint(const gfx::Point& point) override;
// BrowserRootView::DropTarget:
BrowserRootView::DropIndex GetDropIndex(
const ui::DropTargetEvent& event) override;
BrowserRootView::DropTarget* GetDropTarget(
gfx::Point loc_in_local_coords) override;
views::View* GetViewForDrop() override;
void HandleDragUpdate(
const absl::optional<BrowserRootView::DropIndex>& index) override;
void HandleDragExited() override;
// views::ViewTargeterDelegate:
views::View* TargetForRect(views::View* root, const gfx::Rect& rect) override;
// MouseWatcherListener:
void MouseMovedOutOfHost() override;
// views::BoundsAnimatorObserver:
void OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) override;
void OnBoundsAnimatorDone(views::BoundsAnimator* animator) override;
private:
// Used during a drop session of a url. Tracks the position of the drop as
// well as a window used to highlight where the drop occurs.
class DropArrow : public views::WidgetObserver {
public:
DropArrow(const BrowserRootView::DropIndex& index,
bool point_down,
views::Widget* context);
DropArrow(const DropArrow&) = delete;
DropArrow& operator=(const DropArrow&) = delete;
~DropArrow() override;
void set_index(const BrowserRootView::DropIndex& index) { index_ = index; }
BrowserRootView::DropIndex index() const { return index_; }
void SetPointDown(bool down);
bool point_down() const { return point_down_; }
void SetWindowBounds(const gfx::Rect& bounds);
// views::WidgetObserver:
void OnWidgetDestroying(views::Widget* widget) override;
private:
// Index of the tab to drop on.
BrowserRootView::DropIndex index_;
// Direction the arrow should point in. If true, the arrow is displayed
// above the tab and points down. If false, the arrow is displayed beneath
// the tab and points up.
bool point_down_ = false;
// Renders the drop indicator.
raw_ptr<views::Widget, DanglingUntriaged> arrow_window_ = nullptr;
raw_ptr<views::ImageView, DanglingUntriaged> arrow_view_ = nullptr;
base::ScopedObservation<views::Widget, views::WidgetObserver>
scoped_observation_{this};
};
class RemoveTabDelegate;
views::ViewModelT<Tab>* GetTabsViewModel();
// Private getter to retrieve the visible rect of the scroll container.
absl::optional<gfx::Rect> GetVisibleContentRect();
// Animates and scrolls the tab container from the start_edge to the
// target_edge. If the target_edge is beyond the tab strip it will be clamped
// bounds of the tabstrip.
void AnimateScrollToShowXCoordinate(const int start_edge,
const int target_edge);
// Animates |tab_slot_view| to |target_bounds|
void AnimateTabSlotViewTo(TabSlotView* tab_slot_view,
const gfx::Rect& target_bounds);
// Generates and sets the ideal bounds for each of the tabs. Note: Does not
// animate the tabs to those bounds so callers can use this information for
// other purposes - see AnimateToIdealBounds.
void UpdateIdealBounds();
// Teleports the tabs to their ideal bounds.
// NOTE: this does *not* invoke UpdateIdealBounds, it uses the bounds
// currently set in ideal_bounds.
void SnapToIdealBounds();
// Calculates the width that can be occupied by the tabs in the container.
// This can differ from GetAvailableWidthForTabContainer() when in tab closing
// mode.
int CalculateAvailableWidthForTabs() const;
// Invoked from |AddTab| after the newly created tab has been inserted.
void StartInsertTabAnimation(int model_index);
void StartRemoveTabAnimation(Tab* tab, int former_model_index);
// Computes the bounds that `tab` should animate towards as it closes.
gfx::Rect GetTargetBoundsForClosingTab(Tab* tab,
int former_model_index) const;
// Returns the largest x-value this TabContainer should contain, based on the
// ideal (i.e. post-animation) bounds of its contents.
int GetIdealTrailingX() const;
absl::optional<int> GetMidAnimationTrailingX() const;
// Update `layout_helper_` and remove the tab from `tabs_view_model_` (but
// *not* from the View hierarchy) so it can be animated closed.
void CloseTabInViewModel(int index);
// Call when `tab` is going away to remove the tab from data structures.
void OnTabRemoved(Tab* tab);
// Updates |override_available_width_for_tabs_|, if necessary, to account for
// the removal of the tab at |model_index|.
void UpdateClosingModeOnRemovedTab(int model_index, bool was_active);
// Perform an animated resize-relayout of the TabContainer immediately.
void ResizeLayoutTabs();
// Invokes ResizeLayoutTabs() as long as we're not in a drag session. If we
// are in a drag session this restarts the timer.
void ResizeLayoutTabsFromTouch();
// Restarts |resize_layout_timer_|.
void StartResizeLayoutTabsFromTouchTimer();
bool IsDragSessionActive() const;
bool IsDragSessionEnding() const;
// Ensure that the message loop observer used for event spying is added and
// removed appropriately so we can tell when to resize layout.
void AddMessageLoopObserver();
void RemoveMessageLoopObserver();
// Moves |slot_view| within children() to match |layout_helper_|'s slot
// ordering.
void OrderTabSlotView(TabSlotView* slot_view);
// Returns true if the specified point in TabStrip coords is within the
// hit-test region of the specified Tab.
bool IsPointInTab(Tab* tab, const gfx::Point& point_in_tabstrip_coords);
// For a given point, finds a tab that is hit by the point. If the point hits
// an area on which two tabs are overlapping, the tab is selected as follows:
// - If one of the tabs is active, select it.
// - Select the left one.
// If no tabs are hit, returns null.
Tab* FindTabHitByPoint(const gfx::Point& point);
// Returns true if the tab is not partly or fully clipped (due to overflow),
// and the tab couldn't become partly clipped due to changing the selected tab
// (for example, if currently the strip has the last tab selected, and
// changing that to the first tab would cause |tab| to be pushed over enough
// to clip).
bool ShouldTabBeVisible(const Tab* tab) const;
// Returns true iff `tab` is a member of a collapsed group and the collapse
// animation is finished.
bool IsTabCollapsed(const Tab* tab) const;
// -- Link Drag & Drop ------------------------------------------------------
// Returns the bounds to render the drop at, in screen coordinates. Sets
// |is_beneath| to indicate whether the arrow is beneath the tab, or above
// it.
gfx::Rect GetDropBounds(int drop_index,
bool drop_before,
bool drop_in_group,
bool* is_beneath);
// Show drop arrow with passed |tab_data_index| and |drop_before|.
// If |tab_data_index| is negative, the arrow will disappear.
void SetDropArrow(const absl::optional<BrowserRootView::DropIndex>& index);
// Updates the indexes and count for AX data on all tabs. Used by some screen
// readers (e.g. ChromeVox).
void UpdateAccessibleTabIndices();
bool IsValidModelIndex(int model_index) const;
std::map<tab_groups::TabGroupId, std::unique_ptr<TabGroupViews>> group_views_;
// There is a one-to-one mapping between each of the
// tabs in the TabStripModel and |tabs_view_model_|.
// Because we animate tab removal there exists a
// period of time where a tab is displayed but not
// in the model. When this occurs the tab is removed
// from |tabs_view_model_|, but remains in
// |layout_helper_| (and remains a View child) until
// the remove animation completes.
views::ViewModelT<Tab> tabs_view_model_;
const raw_ref<TabContainerController, DanglingUntriaged> controller_;
const raw_ptr<TabHoverCardController, DanglingUntriaged>
hover_card_controller_;
// May be nullptr in tests.
const raw_ptr<TabDragContextBase, DanglingUntriaged> drag_context_;
const raw_ref<TabSlotController> tab_slot_controller_;
// The View that is to be scrolled by |tab_scrolling_animation_|. May be
// nullptr in tests.
const raw_ptr<views::View> scroll_contents_view_;
// This view is animated by `bounds_animator_` to guarantee that this
// container's bounds change smoothly when tabs are animated into or out of
// this container.
const raw_ref<views::View, DanglingUntriaged> overall_bounds_view_;
// Responsible for animating tabs in response to model changes.
views::BoundsAnimator bounds_animator_;
// Responsible for animating the scroll of the tab container.
std::unique_ptr<gfx::LinearAnimation> tab_scrolling_animation_;
const std::unique_ptr<TabStripLayoutHelper> layout_helper_;
// MouseWatcher is used when a tab is closed to reset the layout.
std::unique_ptr<views::MouseWatcher> mouse_watcher_;
// Timer used when a tab is closed and we need to relayout. Only used when a
// tab close comes from a touch device.
base::OneShotTimer resize_layout_timer_;
// Valid for the lifetime of a link drag over us.
std::unique_ptr<DropArrow> drop_arrow_;
// Size we last laid out at.
gfx::Size last_layout_size_;
// The width available for tabs at the time of last layout.
int last_available_width_ = 0;
// If this value is defined, it is used as the width to lay out tabs
// (instead of GetAvailableWidthForTabStrip()). It is defined when closing
// tabs with the mouse, and is used to control which tab will end up under the
// cursor after the close animation completes.
absl::optional<int> override_available_width_for_tabs_;
// The TabContainer enters tab closing mode when a tab is closed or a tab
// group is collapsed with the mouse/touch. When in tab closing mode, remove
// animations preserve current tab bounds, making tabs move more predictably
// in case the user wants to perform more mouse-based actions.
bool in_tab_close_ = false;
base::RepeatingCallback<int()> available_width_callback_;
};
#endif // CHROME_BROWSER_UI_VIEWS_TABS_TAB_CONTAINER_H_