chromium/ash/wm/overview/overview_window_drag_controller.h

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef ASH_WM_OVERVIEW_OVERVIEW_WINDOW_DRAG_CONTROLLER_H_
#define ASH_WM_OVERVIEW_OVERVIEW_WINDOW_DRAG_CONTROLLER_H_

#include <memory>

#include "ash/ash_export.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/splitview/split_view_types.h"
#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/timer/timer.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/size_f.h"

namespace aura {
class Window;
}  // namespace aura

namespace ui {
class PresentationTimeRecorder;
}  // namespace ui

namespace ash {

class OverviewGrid;
class OverviewItemBase;
class OverviewSession;
class SplitViewController;

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// Workflows of dragging windows from overview (not from the top or shelf).
enum class OverviewDragAction {
  kToGridSameDisplayClamshellMouse = 0,
  kToGridSameDisplayClamshellTouch = 1,
  kToDeskSameDisplayClamshellMouse = 2,
  kToDeskSameDisplayClamshellTouch = 3,
  kToSnapSameDisplayClamshellMouse = 4,
  kToSnapSameDisplayClamshellTouch = 5,
  kSwipeToCloseSuccessfulClamshellTouch = 6,
  kSwipeToCloseCanceledClamshellTouch = 7,
  kFlingToCloseClamshellTouch = 8,
  kToGridOtherDisplayClamshellMouse = 9,
  kToDeskOtherDisplayClamshellMouse = 10,
  kToSnapOtherDisplayClamshellMouse = 11,
  kToGridSameDisplayTabletTouch = 12,
  kToDeskSameDisplayTabletTouch = 13,
  kToSnapSameDisplayTabletTouch = 14,
  kSwipeToCloseSuccessfulTabletTouch = 15,
  kSwipeToCloseCanceledTabletTouch = 16,
  kFlingToCloseTabletTouch = 17,
  kMaxValue = kFlingToCloseTabletTouch,
};

// The drag controller for an overview window item in overview mode. It updates
// the position of the corresponding window item using transform while dragging.
// It also updates the split view drag indicators, which handles showing
// indicators where to drag, and preview areas showing the bounds of the
// window about to be snapped.
class ASH_EXPORT OverviewWindowDragController {
 public:
  enum class DragBehavior {
    // No drag has started.
    kNoDrag,
    // Drag has started, but it is undecided whether we want to drag to snap or
    // drag to close yet.
    kUndefined,
    // On drag complete, the window will be snapped, if it meets requirements,
    // or moved to another desk if dropped on one of the desks' mini_views. This
    // mode is triggered if the the window is initially dragged horizontally
    // more than vertically (more in X than Y), or if the window item in the
    // overview grid was gesture long pressed.
    kNormalDrag,
    // On drag complete, the window will be closed, if it meets requirements.
    // This mode is triggered when the window is initially dragged vertically
    // more than horizontally (more in Y than in X).
    kDragToClose,
  };

  enum class DragResult {
    // The drag ended without ever being disambiguated between a normal drag and
    // drag-to-close.
    kNeverDisambiguated,
    // The drag was considered as a normal drag, and then the window was dropped
    // back into overview, in the same grid or another one.
    kDropIntoOverview,
    // The drag resulted in snapping the window.
    kSnap,
    // The drag resulted in moving the window to another desk.
    kDragToDesk,
    // The drag resulted in closing the window.
    kSuccessfulDragToClose,
    // The drag was considered as drag-to-close, but did not result in closing
    // the window.
    kCanceledDragToClose,
  };

  OverviewWindowDragController(OverviewSession* overview_session,
                               OverviewItemBase* item,
                               bool is_touch_dragging,
                               OverviewItemBase* event_source_item);

  OverviewWindowDragController(const OverviewWindowDragController&) = delete;
  OverviewWindowDragController& operator=(const OverviewWindowDragController&) =
      delete;

  ~OverviewWindowDragController();

  static base::AutoReset<bool> SkipNewDeskButtonScaleUpDurationForTesting();

  OverviewItemBase* item() { return item_; }

  bool is_touch_dragging() const { return is_touch_dragging_; }

  void InitiateDrag(const gfx::PointF& location_in_screen);
  void Drag(const gfx::PointF& location_in_screen);
  DragResult CompleteDrag(const gfx::PointF& location_in_screen);
  void StartNormalDragMode(const gfx::PointF& location_in_screen);
  DragResult Fling(const gfx::PointF& location_in_screen,
                   float velocity_x,
                   float velocity_y);
  void ActivateDraggedWindow();

  // Called when a gesture event is reset or when the dragged window is being
  // destroyed.
  void ResetGesture();

  // Resets |overview_session_| to nullptr. It's needed since we defer the
  // deletion of OverviewWindowDragController in Overview destructor and
  // we need to reset |overview_session_| to nullptr to avoid null pointer
  // dereference.
  void ResetOverviewSession();

  DragBehavior current_drag_behavior_for_testing() const {
    return current_drag_behavior_;
  }

  base::OneShotTimer* new_desk_button_scale_up_timer_for_test() {
    return &new_desk_button_scale_up_timer_;
  }

 private:
  enum NormalDragAction {
    kToGrid = 0,
    kToDesk = 1,
    kToSnap = 2,
    kNormalDragActionEnumSize = 3,
  };
  enum DragToCloseAction {
    kSwipeToCloseSuccessful = 0,
    kSwipeToCloseCanceled = 1,
    kFlingToClose = 2,
    kDragToCloseActionEnumSize = 3,
  };

  void StartDragToCloseMode();

  // Methods to continue and complete the drag when the drag mode is
  // kDragToClose.
  void ContinueDragToClose(const gfx::PointF& location_in_screen);
  DragResult CompleteDragToClose(const gfx::PointF& location_in_screen);

  // Methods to continue and complete the drag when the drag mode is
  // kNormalDrag.
  void ContinueNormalDrag(const gfx::PointF& location_in_screen);
  DragResult CompleteNormalDrag(const gfx::PointF& location_in_screen);

  // Updates visuals for the user while dragging items around.
  void UpdateDragIndicatorsAndOverviewGrid(
      const gfx::PointF& location_in_screen);

  aura::Window* GetRootWindowBeingDraggedIn() const;

  SnapPosition GetSnapPosition(const gfx::PointF& location_in_screen) const;

  // Snaps and activates the window. Uses the divider spawn animation (see
  // |SplitViewController::SnapWindow|). Sets |item_| to null because the
  // overview item is destroyed.
  void SnapWindow(SplitViewController* split_view_controller,
                  SnapPosition snap_position);

  // Returns the item's overview grid, or the grid in which the item is being
  // dragged if the multi display overview and split view feature is enabled.
  OverviewGrid* GetCurrentGrid() const;

  // Records the histogram Ash.Overview.WindowDrag.Workflow.
  void RecordNormalDrag(NormalDragAction action,
                        bool is_dragged_to_other_display) const;
  void RecordDragToClose(DragToCloseAction action) const;

  // Creates `float_drag_helper_` if needed. The helper will temporarily stack
  // the float container under the active desk container, so that dragging
  // regular windows appear above overview items of floated windows.
  void MaybeCreateFloatDragHelper();

  // Scale up the new desk button on the desks bar from expanded state to active
  // state to make the new desk button a drop target for the window being
  // dragged. It's triggered by `new_desk_button_scale_up_timer_`. Refer to
  // `new_desk_button_scale_up_timer_` for more information.
  void MaybeScaleUpNewDeskButton();

  raw_ptr<OverviewSession> overview_session_;

  // The drag target item in the overview mode.
  raw_ptr<OverviewItemBase, DanglingUntriaged> item_ = nullptr;

  // The source item of the drag event.
  raw_ptr<OverviewItemBase, DanglingUntriaged> event_source_item_ = nullptr;

  DragBehavior current_drag_behavior_ = DragBehavior::kNoDrag;

  // The location of the initial mouse/touch/gesture event in screen.
  gfx::PointF initial_event_location_;

  // Stores the bounds of |item_| when a drag is started. Used to calculate the
  // new bounds on a drag event.
  gfx::PointF initial_centerpoint_;

  // The original size of the dragged item after we scale it up when we start
  // dragging it. The item is restored to this size once it no longer intersects
  // with the OverviewDeskBarView.
  gfx::SizeF original_scaled_size_;

  // Track the per-overview-grid desks bar data used to perform the window
  // sizing operations when it is moved towards or on the desks bar.
  struct GridDesksBarData {
    // The scaled-down size of the dragged item once the drag location is on the
    // OverviewDeskBarView of the corresponding grid. We size the item down so
    // that it fits inside the desks' preview view.
    gfx::SizeF on_desks_bar_item_size;

    // Cached values related to dragging items while the desks bar is shown.
    // |desks_bar_bounds| is the bounds of the desks bar in screen coordinates.
    // |shrink_bounds| is a rectangle around the desks bar which the items
    // starts shrinking when the event location is contained. The item will
    // shrink until it is contained in |desks_bar_bounds|, at which it has
    // reached its minimum size and will no longer shrink.
    // |shrink_region_distance| is a vector contained the distance from the
    // origin of |desks_bar_bounds| to the origin of |shrink_bounds|. It's
    // used to determine the size of the dragged item when it's within
    // |shrink_bounds|.
    gfx::RectF desks_bar_bounds;
    gfx::RectF shrink_bounds;
    gfx::Vector2dF shrink_region_distance;
  };
  base::flat_map<OverviewGrid*, GridDesksBarData> per_grid_desks_bar_data_;

  const size_t display_count_;

  // Indicates touch dragging, as opposed to mouse dragging. The drag-to-close
  // mode is only allowed when |is_touch_dragging_| is true.
  const bool is_touch_dragging_;

  // True if the `item_` can be snapped by dragging.
  const bool is_eligible_for_drag_to_snap_;

  // True if the Virtual Desks bar is created and dragging to desks is enabled.
  const bool virtual_desks_bar_enabled_;

  // The opacity of |item_| changes if we are in drag to close mode. Store the
  // original opacity of |item_| and restore it to the item when we leave drag
  // to close mode.
  float original_opacity_ = 1.f;

  // Set to true once the bounds of |item_| change.
  bool did_move_ = false;

  // Records the presentation time of window drag operation in overview mode.
  std::unique_ptr<ui::PresentationTimeRecorder> presentation_time_recorder_;

  SnapPosition snap_position_ = SnapPosition::kNone;

  std::optional<OverviewController::ScopedOcclusionPauser> occlusion_pauser_;

  // A timer used to scale up the new desk button to make it a drop target for
  // the window being dragged if the window is hovered on the button over a
  // period of time.
  base::OneShotTimer new_desk_button_scale_up_timer_;
};

}  // namespace ash

#endif  // ASH_WM_OVERVIEW_OVERVIEW_WINDOW_DRAG_CONTROLLER_H_