chromium/ash/wm/snap_group/snap_group_controller.h

// 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_SNAP_GROUP_SNAP_GROUP_CONTROLLER_H_
#define ASH_WM_SNAP_GROUP_SNAP_GROUP_CONTROLLER_H_

#include <memory>
#include <vector>

#include "ash/ash_export.h"
#include "ash/wm/overview/overview_observer.h"
#include "ash/wm/snap_group/snap_group_metrics.h"
#include "ash/wm/wm_metrics.h"
#include "base/containers/flat_map.h"
#include "base/observer_list.h"
#include "base/time/time.h"
#include "ui/aura/window.h"
#include "ui/display/display_observer.h"

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

namespace ash {

class SnapGroup;
class SnapGroupObserver;

// Works as the centralized place to manage the `SnapGroup`. A single instance
// of this class will be created and owned by `Shell`. It controls the creation
// and destruction of the `SnapGroup`.
class ASH_EXPORT SnapGroupController : public OverviewObserver,
                                       public display::DisplayObserver {
 public:
  using SnapGroups = std::vector<std::unique_ptr<SnapGroup>>;
  using WindowToSnapGroupMap = base::flat_map<aura::Window*, SnapGroup*>;

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

  // Convenience function to get the snap group controller instance, which is
  // created and owned by Shell.
  static SnapGroupController* Get();

  // Returns true if `window1` and `window2` are in the same snap group.
  bool AreWindowsInSnapGroup(aura::Window* window1,
                             aura::Window* window2) const;

  // Called by `SplitViewController` when `window` is snapped. Returns true if
  // `window` was added to a group, either by normal group creation or snap
  // to replace.
  bool OnWindowSnapped(aura::Window* window,
                       WindowSnapActionSource snap_action_source);

  // Attempts to add `window1` and `window2` as a `SnapGroup`. Returns the
  // `SnapGroup`, if the creation is successful. Returns nullptr, otherwise.
  // Currently, both windows must reside within the same parent container for
  // successful creation. If `replace` is true, the group was snapped to replace
  // and we shouldn't record the count change. `carry_over_creation_time`
  // indicates the creation time of a prior Snap Group from which the current
  // one was derived using the Snap to Replace feature.
  // TODO(b/333772909): Remove `replace` param when snap to replace updates
  // window in SnapGroup instead of removing and re-adding a SnapGroup.
  SnapGroup* AddSnapGroup(
      aura::Window* window1,
      aura::Window* window2,
      bool replace,
      std::optional<base::TimeTicks> carry_over_creation_time);

  // Removes the specified `snap_group`, recording the `exit_point` metric.
  // Returns true if the corresponding `snap_group` has been successfully
  // removed from the `snap_groups_` and `window_to_snap_group_map_`. False
  // otherwise.
  bool RemoveSnapGroup(SnapGroup* snap_group, SnapGroupExitPoint exit_point);

  // Returns true if the corresponding snap group that contains the
  // given `window` has been removed successfully. Returns false otherwise.
  bool RemoveSnapGroupContainingWindow(aura::Window* window,
                                       SnapGroupExitPoint exit_point);

  // Returns the corresponding `SnapGroup` if the given `window` belongs to a
  // snap group or nullptr otherwise.
  SnapGroup* GetSnapGroupForGivenWindow(const aura::Window* window) const;

  // Returns the topmost fully visible non-occluded snap group on `target_root`.
  SnapGroup* GetTopmostVisibleSnapGroup(const aura::Window* target_root) const;

  // Returns the topmost snap group in unminimized state.
  SnapGroup* GetTopmostSnapGroup() const;

  // Determines which windows can be used for snap-to-replace with keyboard
  // shortcut:
  // 1. Finds the topmost snapped window.
  // 2. Identifies the window within a partially obscured Snap Group that isn't
  // hidden by the topmost snapped window.
  //  Returns the window pair for snap-to-replace: [primary snapped window,
  //  secondary snapped window].
  std::optional<std::pair<aura::Window*, aura::Window*>>
  GetWindowPairForSnapToReplaceWithKeyboardShortcut();

  void AddObserver(SnapGroupObserver* observer);
  void RemoveObserver(SnapGroupObserver* observer);

  // Called by `WindowState` when a float or unfloat event for `window` has
  // completed.
  void OnFloatUnfloatCompleted(aura::Window* window);

  // OverviewObserver:
  void OnOverviewModeStarting() override;
  void OnOverviewModeEnding(OverviewSession* overview_session) override;
  void OnOverviewModeEndingAnimationComplete(bool canceled) override;

  // display::DisplayObserver:
  void OnDisplayTabletStateChanged(display::TabletState state) override;

  const SnapGroups& snap_groups_for_testing() const { return snap_groups_; }
  const WindowToSnapGroupMap& window_to_snap_group_map_for_testing() const {
    return window_to_snap_group_map_;
  }

 private:
  // Returns true if the attempt to replace the window within the snap group of
  // `opposite_snapped_window` positioned directly below with the given
  // `to_be_snapped_window` is successful, returns false otherwise. The
  // `snap_action_source` determines the need for snap ratio difference
  // calculations during 'snap to replace'.
  bool MaybeSnapToReplace(aura::Window* to_be_snapped_window,
                          aura::Window* opposite_snapped_window,
                          WindowSnapActionSource snap_action_source);

  // Retrieves the other window that is in the same snap group if any. Returns
  // nullptr if such window can't be found i.e. the window is not in a snap
  // group.
  aura::Window* RetrieveTheOtherWindowInSnapGroup(aura::Window* window) const;

  // Restores the snapped state of the `snap_groups_` upon completion of certain
  // transitions such as overview mode or tablet mode. Disallow overview to be
  // shown on the other side of the screen when restoring snap groups so that
  // the restore will be instant and the recursive snapping behavior will be
  // avoided.
  void RestoreSnapGroups();

  // Restore the snap state of the windows in the given `snap_group`.
  void RestoreSnapState(SnapGroup* snap_group);

  // Called when the display tablet state is changed.
  void OnTabletModeStarted();

  // Contains all the `SnapGroup`(s), we will have one `SnapGroup` globally for
  // the first iteration but will have multiple in the future iteration.
  SnapGroups snap_groups_;

  // Maps the `SnapGroup` by the `aura::Window*`. It will be used to get the
  // `SnapGroup` with the `aura::Window*` and can also be used to decide if a
  // window is in a `SnapGroup` or not.
  WindowToSnapGroupMap window_to_snap_group_map_;

  base::ObserverList<SnapGroupObserver> observers_;

  display::ScopedDisplayObserver display_observer_{this};
};

}  // namespace ash

#endif  // ASH_WM_SNAP_GROUP_SNAP_GROUP_CONTROLLER_H_