1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194

ash / wm / tile_group / window_splitter.h [blame]

// Copyright 2023 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_TILE_GROUP_WINDOW_SPLITTER_H_
#define ASH_WM_TILE_GROUP_WINDOW_SPLITTER_H_

#include <memory>
#include <optional>

#include "ash/ash_export.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "ui/aura/window_observer.h"
#include "ui/events/velocity_tracker/velocity_tracker.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"

namespace aura {
class Window;
}  // namespace aura

namespace ash {

class PhantomWindowController;

// The WindowSplitter is responsible for detecting when a window can be split,
// showing the split preview, and performing the actual window splitting.
// It is meant to be used during dragging by a WindowResizer.
class ASH_EXPORT WindowSplitter : public aura::WindowObserver {
 public:
  // The region of a window from which to initiate a split.
  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  // Keep this in sync with `WindowSplittingSplitRegion` in
  // tools/metrics/histograms/metadata/ash/enums.xml.
  enum class SplitRegion {
    kNone = 0,
    kLeft = 1,
    kRight = 2,
    kTop = 3,
    kBottom = 4,
    kMaxValue = kBottom,
  };

  // The type of action resulting from a completed drag, for logging only.
  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  // Keep this in sync with `WindowSplittingDragType` in
  // tools/metrics/histograms/metadata/ash/enums.xml.
  enum class DragType {
    kIncomplete = 0,
    kNoSplit = 1,
    kSplit = 2,
    kMaxValue = kSplit,
  };

  // Holds info about windows after splitting.
  struct SplitWindowInfo {
    gfx::Rect topmost_window_bounds;
    gfx::Rect dragged_window_bounds;
    SplitRegion split_region = SplitRegion::kNone;

    bool operator==(const SplitWindowInfo&) const;
  };

  // The default inset region of a window that could trigger window splitting.
  static constexpr gfx::Insets kBaseTriggerMargins = gfx::Insets::VH(35, 45);

  // Amount of time the cursor has to dwell to activate the window splitting
  // phantom window.
  static constexpr base::TimeDelta kDwellActivationDuration =
      base::Milliseconds(450);

  // Amount of time the phantom window stays shown before cancelling.
  static constexpr base::TimeDelta kDwellCancellationDuration =
      base::Milliseconds(1500);

  // Max cursor movement velocity threshold, which if exceeded will reset window
  // splitting activation.
  static constexpr double kDwellMaxVelocityPixelsPerSec = 60.0;

  // Calculates window bounds and other info resulting from window splitting.
  // `topmost_window` is the window to be split.
  // `dragged_window` is the window being dragged over the `topmost_window`.
  // `screen_location` is the screen coordinate of the input event. It must be
  // within the `topmost_window`.
  // Returns nullopt if window can't be split, e.g. the location is not within
  // any trigger area, or the resulting size is smaller than minimum size, etc.
  static std::optional<SplitWindowInfo> MaybeSplitWindow(
      aura::Window* topmost_window,
      aura::Window* dragged_window,
      const gfx::PointF& screen_location);

  explicit WindowSplitter(aura::Window* dragged_window);
  WindowSplitter(const WindowSplitter&) = delete;
  WindowSplitter& operator=(const WindowSplitter&) = delete;
  ~WindowSplitter() override;

  // Called during drag to determine window splitting activation.
  void UpdateDrag(const gfx::PointF& location_in_screen, bool can_split);

  // Called when drag is completed to apply splitting.
  void CompleteDrag(const gfx::PointF& last_location_in_screen);

  // Disengages window splitting.
  void Disengage();

  // aura::WindowObserver:
  void OnWindowDestroying(aura::Window* window) override;

  const PhantomWindowController* GetPhantomWindowControllerForTesting() const {
    return phantom_window_controller_.get();
  }

 private:
  aura::Window* dragged_window() {
    return dragged_window_observation_.GetSource();
  }

  aura::Window* topmost_window() {
    return topmost_window_observation_.GetSource();
  }

  void UpdateTopMostWindow(aura::Window* topmost_window);

  // Starts or refreshes the dwell timer.
  void RestartDwellTimer();

  // Removes any phantom window and any running dwell timer.
  void RemovePhantomWindow();

  // Callback to show the phantom window with precondition checking.
  void ShowPhantomWindowCallback();

  // Shows the phantom window.
  void ShowPhantomWindow(const gfx::Rect& bounds);

  // Whether the window is ready to split upon completing drag.
  bool ReadyToSplit() const { return !!phantom_window_controller_; }

  void RecordMetricsOnEndDrag();

  DragType GetDragType() const;

  void UpdateCursorLocation(const gfx::PointF& location_in_screen);

  double GetCursorVelocitySquared() const;

  base::ScopedObservation<aura::Window, aura::WindowObserver>
      dragged_window_observation_{this};
  base::ScopedObservation<aura::Window, aura::WindowObserver>
      topmost_window_observation_{this};

  gfx::PointF last_location_in_screen_;

  std::optional<SplitWindowInfo> last_split_window_info_;

  // Whether the cursor actually moved enough to be considered a drag.
  bool is_drag_updated_ = false;

  // Whether the drag operation was completed successfully (instead of e.g.
  // cancelled).
  bool is_drag_completed_ = false;

  // The region of a window the split happened, if any;
  SplitRegion completed_split_region_ = SplitRegion::kNone;

  // Gives a preview of how the window will be split.
  std::unique_ptr<PhantomWindowController> phantom_window_controller_;

  // Number of times the phantom window was shown.
  uint32_t phantom_window_shown_count_ = 0;

  // Time ticks when the drag action started.
  const base::TimeTicks drag_start_time_;

  // Timer for activating phantom window.
  base::OneShotTimer dwell_activation_timer_;

  // Timer for cancelling window splitting.
  base::OneShotTimer dwell_cancellation_timer_;

  // Tracks cursor velocity.
  ui::VelocityTracker velocity_tracker_;

  base::WeakPtrFactory<WindowSplitter> weak_ptr_factory_{this};
};

}  // namespace ash

#endif  // ASH_WM_TILE_GROUP_WINDOW_SPLITTER_H_