chromium/ash/wm/workspace/multi_window_resize_controller.h

// Copyright 2012 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_WORKSPACE_MULTI_WINDOW_RESIZE_CONTROLLER_H_
#define ASH_WM_WORKSPACE_MULTI_WINDOW_RESIZE_CONTROLLER_H_

#include <memory>
#include <vector>

#include "ash/ash_export.h"
#include "ash/wm/overview/overview_observer.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_state_observer.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_multi_source_observation.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "ui/aura/window_observer.h"
#include "ui/views/mouse_watcher.h"

namespace aura {
class Window;
}  // namespace aura

namespace gfx {
class PointF;
class Rect;
}  // namespace gfx

namespace views {
class Widget;
}  // namespace views

namespace ash {

class MultiWindowResizeControllerTest;
class WorkspaceWindowResizer;

// MultiWindowResizeController is responsible for determining and showing a
// widget that allows resizing multiple windows at the same time.
// MultiWindowResizeController is driven by WorkspaceEventHandler.
class ASH_EXPORT MultiWindowResizeController
    : public views::MouseWatcherListener,
      public aura::WindowObserver,
      public WindowStateObserver,
      public OverviewObserver {
 public:
  // Delay before showing the `resize_widget_`.
  static constexpr base::TimeDelta kShowDelay = base::Milliseconds(400);

  MultiWindowResizeController();

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

  ~MultiWindowResizeController() override;

  // If necessary, shows the resize widget. |window| is the window the mouse
  // is over, |component| the edge and |point| the location of the mouse.
  void Show(aura::Window* window, int component, const gfx::Point& point);

  // MouseWatcherListener:
  void MouseMovedOutOfHost() override;

  // WindowObserver:
  void OnWindowPropertyChanged(aura::Window* window,
                               const void* key,
                               intptr_t old) override;
  void OnWindowVisibilityChanged(aura::Window* window, bool visible) override;
  void OnWindowDestroying(aura::Window* window) override;

  // WindowStateObserver:
  void OnPostWindowStateTypeChange(WindowState* window_state,
                                   chromeos::WindowStateType old_type) override;

  // OverviewObserver:
  void OnOverviewModeStarting() override;
  void OnOverviewModeEndingAnimationComplete(bool canceled) override;

 private:
  friend class MultiWindowResizeControllerTest;
  friend class SnapGroupTest;
  class ResizeMouseWatcherHost;
  class ResizeView;

  // Two directions resizes happen in.
  enum class Direction {
    kTopBottom,
    kLeftRight,
  };

  // Used to track the two resizable windows and direction.
  struct ResizeWindows {
    ResizeWindows();
    ResizeWindows(const ResizeWindows& other);
    ~ResizeWindows();

    // Returns true if |other| equals this ResizeWindows. This does *not*
    // consider the windows in |other_windows|.
    bool Equals(const ResizeWindows& other) const;

    // Returns true if this ResizeWindows is valid.
    bool is_valid() const { return window1 && window2; }

    // The left/top window to resize.
    raw_ptr<aura::Window> window1 = nullptr;

    // Other window to resize.
    raw_ptr<aura::Window> window2 = nullptr;

    // Direction
    Direction direction;

    // Windows after |window2| that are to be resized. Determined at the time
    // the resize starts.
    std::vector<raw_ptr<aura::Window, VectorExperimental>> other_windows;
  };

  void CreateMouseWatcher();

  // Returns a ResizeWindows based on the specified arguments. Use is_valid()
  // to test if the return value is a valid multi window resize location.
  ResizeWindows DetermineWindows(aura::Window* window,
                                 int window_component,
                                 const gfx::Point& point) const;

  // Variant of DetermineWindows() that uses the current location of the mouse
  // to determine the resize windows.
  ResizeWindows DetermineWindowsFromScreenPoint(aura::Window* window) const;

  // Finds a window by edge (one of the constants HitTestCompat.
  aura::Window* FindWindowByEdge(aura::Window* window_to_ignore,
                                 int edge_want,
                                 int x_in_parent,
                                 int y_in_parent) const;

  // Returns the first window touching `window`.
  aura::Window* FindWindowTouching(aura::Window* window,
                                   Direction direction) const;

  // Places any windows touching `start` into `others`.
  void FindWindowsTouching(
      aura::Window* start,
      Direction direction,
      std::vector<raw_ptr<aura::Window, VectorExperimental>>* others) const;

  // Starts/Stops observing `window`.
  void StartObserving(aura::Window* window);
  void StopObserving(aura::Window* window);

  // Check if we're observing `window`.
  bool IsObserving(aura::Window* window) const;

  // Shows the resizer if the mouse is still at a valid location. This is called
  // from the `show_timer_`.
  void ShowIfValidMouseLocation();

  // Shows the `resize_widget_` immediately.
  void ShowNow();

  // Returns true if the `resize_widget_` is showing.
  bool IsShowing() const;

  // Hides the `resize_widget_` if it gets created.
  void Hide();

  // Resets the window resizer and hides the widget.
  void ResetResizer();

  // Initiates a resize.
  void StartResize(const gfx::PointF& location_in_screen);

  // Resizes to the new location.
  void Resize(const gfx::PointF& location_in_screen, int event_flags);

  // Completes the resize.
  void CompleteResize();

  // Cancels the resize.
  void CancelResize();

  // Returns the bounds for the resize widget.
  gfx::Rect CalculateResizeWidgetBounds(
      const gfx::PointF& location_in_parent) const;

  // Returns true if `location_in_screen` is over the resize widget.
  bool IsOverResizeWidget(const gfx::Point& location_in_screen) const;

  // Returns true if `location_in_screen` is over the resize windows
  // (or the resize widget itself).
  bool IsOverWindows(const gfx::Point& location_in_screen) const;

  // Returns true if |location_in_screen| is over |component| in |window|.
  bool IsOverComponent(aura::Window* window,
                       const gfx::Point& location_in_screen,
                       int component) const;

  // Windows and direction to resize.
  ResizeWindows windows_;

  // Timer used before showing.
  base::OneShotTimer show_timer_;

  std::unique_ptr<views::Widget> resize_widget_;

  // If non-null we're in a resize loop.
  std::unique_ptr<WorkspaceWindowResizer> window_resizer_;

  // Mouse coordinate passed to Show() in container's coodinates.
  gfx::Point show_location_in_parent_;

  // Bounds the resize widget was last shown at in screen coordinates.
  gfx::Rect resize_widget_show_bounds_in_screen_;

  // Used to detect whether the mouse is over the windows. While
  // |resize_widget_| is non-NULL (ie the widget is showing) we ignore calls
  // to Show().
  std::unique_ptr<views::MouseWatcher> mouse_watcher_;

  base::ScopedMultiSourceObservation<aura::Window, aura::WindowObserver>
      window_observations_{this};
  base::ScopedMultiSourceObservation<WindowState, WindowStateObserver>
      window_state_observations_{this};
};

}  // namespace ash

#endif  // ASH_WM_WORKSPACE_MULTI_WINDOW_RESIZE_CONTROLLER_H_