chromium/ash/wm/float/float_controller.h

// Copyright 2021 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_FLOAT_FLOAT_CONTROLLER_H_
#define ASH_WM_FLOAT_FLOAT_CONTROLLER_H_

#include <memory>

#include "ash/ash_export.h"
#include "ash/rotator/screen_rotation_animator_observer.h"
#include "ash/shell_observer.h"
#include "ash/wm/desks/desks_controller.h"
#include "base/gtest_prod_util.h"
#include "base/scoped_multi_source_observation.h"
#include "base/scoped_observation.h"
#include "chromeos/ui/base/window_state_type.h"
#include "chromeos/ui/frame/multitask_menu/float_controller_base.h"
#include "ui/display/display_observer.h"

namespace aura {
class Window;
}  // namespace aura

namespace display {
enum class TabletState;
}  // namespace display

namespace views {
class Widget;
}  // namespace views

namespace ash {

class Shell;
class WorkspaceEventHandler;

// This controller allows windows to be on top of all app windows, but below
// pips. When a window is 'floated', it remains always on top for the user so
// that they can complete secondary tasks. Floated window stays in the
// `kShellWindowId_FloatContainer`.
class ASH_EXPORT FloatController : public display::DisplayObserver,
                                   public ShellObserver,
                                   public DesksController::Observer,
                                   public chromeos::FloatControllerBase,
                                   public ScreenRotationAnimatorObserver {
 public:
  // The possible corners that a floated window can be placed in tablet mode.
  // The default is `kBottomRight` and this is changed by dragging the window.
  enum class MagnetismCorner {
    kTopLeft = 0,
    kTopRight,
    kBottomLeft,
    kBottomRight,
  };

  FloatController();
  FloatController(const FloatController&) = delete;
  FloatController& operator=(const FloatController&) = delete;
  ~FloatController() override;

  // Returns float window bounds in clamshell mode in root window coordinates.
  static gfx::Rect GetFloatWindowClamshellBounds(
      aura::Window* window,
      chromeos::FloatStartLocation location);

  // Gets the ideal float bounds of `window` in tablet mode if it were to be
  // floated, in root window coordinates.
  static gfx::Rect GetFloatWindowTabletBounds(aura::Window* window);

  // Float the `window` if it's not floated, otherwise unfloat it. If called in
  // clamshell mode, the default location is the bottom right.
  void ToggleFloat(aura::Window* window);

  // Untucks `floated_window`. Does nothing if the window is already untucked.
  void MaybeUntuckFloatedWindowForTablet(aura::Window* floated_window);

  // Checks if `floated_window` is tucked.
  bool IsFloatedWindowTuckedForTablet(const aura::Window* floated_window) const;

  // Returns true if `floated_window` is not tucked and magnetized to the
  // bottom. Used by the shelf layout manager to determine what window to use
  // for the drag window from shelf feature.
  bool IsFloatedWindowAlignedWithShelf(aura::Window* floated_window) const;

  // Gets the tuck handle for a floated and tucked window.
  views::Widget* GetTuckHandleWidget(const aura::Window* floated_window) const;

  // Called by the resizer when a drag is completed. Updates the bounds and
  // magnetism of the `floated_window`.
  void OnDragCompletedForTablet(aura::Window* floated_window);

  // TODO(shidi): Temporary passing `floated_window` here, will follow-up in
  // desk logic to use only `active_floated_window_`.
  // Called by the resizer when a drag is completed by a fling or swipe gesture
  // event. Updates the magnetism of the window and then tucks the window
  // offscreen based on `velocity_x` and `velocity_y`.
  void OnFlingOrSwipeForTablet(aura::Window* floated_window,
                               float velocity_x,
                               float velocity_y);

  // Returns the desk where floated window belongs to if window is floated and
  // registered under `floated_window_info_map_`, otherwise returns nullptr.
  const Desk* FindDeskOfFloatedWindow(const aura::Window* window) const;
  // Returns the floated window that belongs to `desk`. If `desk` doesn't have a
  // floated window, returns nullptr.
  aura::Window* FindFloatedWindowOfDesk(const Desk* desk) const;

  // Called when moving all `original_desk`'s windows out to `target_desk` due
  // to the removal of `original_desk`. This function takes care of floated
  // window (if any) since it doesn't belong to the desk container. Note: during
  // desk removal/combination, `floated_window` will be unfloated if
  // `target_desk` has a floated window.
  void OnMovingAllWindowsOutToDesk(Desk* original_desk, Desk* target_desk);

  // Called when moving the `floated_window` from `active_desk` to
  // `target_desk`. This function takes care of floated window since it doesn't
  // belong to the desk container. Note: Unlike `OnMovingAllWindowsOutToDesk`
  // above, if `target_desk` has a floated window, it will be unfloated, while
  // `floated_window` remains floated. Note: When dragging `floated_window` to a
  // different display, we need to map `floated_window` to the desk container
  // with same ID on target display's root.
  void OnMovingFloatedWindowToDesk(aura::Window* floated_window,
                                   Desk* active_desk,
                                   Desk* target_desk,
                                   aura::Window* target_root);

  void ClearWorkspaceEventHandler(aura::Window* root);

  // DesksController::Observer:
  void OnDeskActivationChanged(const Desk* activated,
                               const Desk* deactivated) override;

  // display::DisplayObserver:
  void OnDisplayMetricsChanged(const display::Display& display,
                               uint32_t metrics) override;
  void OnDisplayTabletStateChanged(display::TabletState state) override;

  // ShellObserver:
  void OnRootWindowAdded(aura::Window* root_window) override;
  void OnRootWindowWillShutdown(aura::Window* root_window) override;
  void OnPinnedStateChanged(aura::Window* pinned_window) override;

  // chromeos::FloatControllerBase:
  void SetFloat(aura::Window* window,
                chromeos::FloatStartLocation float_start_location) override;
  void UnsetFloat(aura::Window* window) override;

  // ScreenRotationAnimatorObserver:
  void OnScreenCopiedBeforeRotation() override;
  void OnScreenRotationAnimationFinished(ScreenRotationAnimator* animator,
                                         bool canceled) override;

 private:
  class FloatedWindowInfo;
  friend class ClientControlledState;
  friend class DefaultState;
  friend class FloatTestApi;
  friend class TabletModeWindowState;

  static MagnetismCorner GetMagnetismCornerForBounds(
      const gfx::Rect& bounds_in_screen);

  // Calls `FloatImpl()` and additionally updates the magnetism if needed.
  void FloatForTablet(aura::Window* window,
                      chromeos::WindowStateType old_state_type);

  // Floats/Unfloats `window`. Only one floating window is allowed per desk,
  // floating a new window on the same desk or moving a floated window to that
  // desk will unfloat the other floated window (if any).
  // Note: currently window can only be floated from an active desk.
  void FloatImpl(aura::Window* window);
  void UnfloatImpl(aura::Window* window);

  // Unfloats `floated_window` from the desk it belongs to.
  void ResetFloatedWindow(aura::Window* floated_window);

  // Returns the `FloatedWindowInfo` for the given window if it's floated, or
  // nullptr otherwise.
  FloatedWindowInfo* MaybeGetFloatedWindowInfo(
      const aura::Window* window) const;

  // This is called by `FloatedWindowInfo::OnWindowDestroying` to remove
  // `floated_window` from `floated_window_info_map_`.
  void OnFloatedWindowDestroying(aura::Window* floated_window);

  // Called by `OnDisplayTabletStateChanged.
  void OnTabletModeStarted();
  void OnTabletModeEnding();

  // Used to map floated window to to its FloatedWindowInfo.
  // Contains extra info for a floated window such as its pre-float auto managed
  // state and tablet mode magnetism.
  base::flat_map<aura::Window*, std::unique_ptr<FloatedWindowInfo>>
      floated_window_info_map_;

  // Workspace event handler which handles double click events to change to
  // maximized state as well as horizontally and vertically maximize. We create
  // one per root window.
  base::flat_map<aura::Window*, std::unique_ptr<WorkspaceEventHandler>>
      workspace_event_handlers_;

  // Float window counter within a session, used for
  // `kFloatWindowCountsPerSessionHistogramName`.
  int floated_window_counter_ = 0;
  // Counts of how many floated window are moved to another desk within a
  // session. `kFloatWindowMoveToAnotherDeskCountsHistogramName`
  int floated_window_move_to_another_desk_counter_ = 0;

  bool disable_tuck_education_for_testing_{false};

  base::ScopedMultiSourceObservation<ScreenRotationAnimator,
                                     ScreenRotationAnimatorObserver>
      screen_rotation_observations_{this};

  base::ScopedObservation<DesksController, DesksController::Observer>
      desks_controller_observation_{this};

  std::optional<display::ScopedOptionalDisplayObserver> display_observer_;
  base::ScopedObservation<Shell, ShellObserver> shell_observation_{this};
};

}  // namespace ash

#endif  // ASH_WM_FLOAT_FLOAT_CONTROLLER_H_