chromium/ash/wm/window_state.h

// Copyright 2013 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_WINDOW_STATE_H_
#define ASH_WM_WINDOW_STATE_H_

#include <memory>
#include <optional>
#include <vector>

#include "ash/ash_export.h"
#include "ash/public/cpp/presentation_time_recorder.h"
#include "ash/wm/drag_details.h"
#include "ash/wm/multi_display/persistent_window_info.h"
#include "ash/wm/wm_metrics.h"
#include "base/auto_reset.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/observer_list.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/ui/base/window_state_type.h"
#include "ui/aura/window_observer.h"
#include "ui/base/ui_base_types.h"
#include "ui/display/display.h"
#include "ui/gfx/animation/tween.h"

namespace chromeos {
enum class WindowPinType;
enum class WindowStateType;
}  // namespace chromeos

namespace gfx {
class Rect;
}

namespace ash {
class ClientControlledState;
class LockWindowState;
class TabletModeWindowState;
class WindowState;
class WindowStateDelegate;
class WindowStateObserver;
class WMEvent;

// WindowState manages and defines ash specific window state and
// behavior. Ash specific per-window state (such as ones that controls
// window manager behavior) and ash specific window behavior (such as
// maximize, minimize, snap sizing etc) should be added here instead
// of defining separate functions (like |MaximizeWindow(aura::Window*
// window)|) or using aura Window property.
// The WindowState gets created when first accessed by
// |WindowState::Get()|, and deleted when the window is deleted.
// Prefer using this class instead of passing aura::Window* around in
// ash code as this is often what you need to interact with, and
// accessing the window using |window()| is cheap.
class ASH_EXPORT WindowState : public aura::WindowObserver {
 public:
  // A subclass of State class represents one of the window's states
  // that corresponds to chromeos::WindowStateType in Ash environment, e.g.
  // maximized, minimized or side snapped, as subclass.
  // Each subclass defines its own behavior and transition for each WMEvent.
  class State {
   public:
    State() = default;

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

    virtual ~State() {}

    // Update WindowState based on |event|.
    virtual void OnWMEvent(WindowState* window_state, const WMEvent* event) = 0;

    virtual chromeos::WindowStateType GetType() const = 0;

    // Gets called when the state object became active and the managed window
    // needs to be adjusted to the State's requirement.
    // The passed |previous_state| may be used to properly implement state
    // transitions such as bound animations from the previous state.
    // Note: This only gets called when the state object gets changed.
    virtual void AttachState(WindowState* window_state,
                             State* previous_state) = 0;

    // Gets called before the state objects gets deactivated / detached from the
    // window, so that it can save the various states it is interested in.
    // Note: This only gets called when the state object gets changed.
    virtual void DetachState(WindowState* window_state) = 0;

    // Called when the window is being destroyed.
    virtual void OnWindowDestroying(WindowState* window_state) {}
  };

  // Type of animation type to be applied when changing bounds locally.
  // TODO(oshima): Use transform animation for snapping.
  enum class BoundsChangeAnimationType {
    // No animation (`SetBoundsDirect()`).
    kNone,
    // Cross fade animation. Copies old layer, and fades it out while fading the
    // new layer in.
    kCrossFade,
    // Custom cross fade animations when floating/unfloating a window.
    kCrossFadeFloat,
    kCrossFadeUnfloat,
    // Bounds animation.
    kAnimate,
    // Bounds animation with zero tween. Updates the bounds once at the end of
    // the animation.
    kAnimateZero,
  };

  // The default duration for an animation between two sets of bounds.
  static constexpr base::TimeDelta kBoundsChangeSlideDuration =
      base::Milliseconds(120);

  // Returns the WindowState for |window|. Creates WindowState if it doesn't
  // exist. The returned value is owned by |window| (you should not delete it).
  static WindowState* Get(aura::Window* window);
  static const WindowState* Get(const aura::Window* window);

  // Returns the WindowState for the active window, null if there is no active
  // window.
  static WindowState* ForActiveWindow();

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

  // Call WindowState::Get() to instantiate this class.
  ~WindowState() override;

  aura::Window* window() { return window_; }
  const aura::Window* window() const { return window_; }

  bool is_moving_to_another_display() const {
    return is_moving_to_another_display_;
  }
  void set_is_moving_to_another_display(bool moving) {
    is_moving_to_another_display_ = moving;
  }

  void set_can_update_snap_ratio(bool val) { can_update_snap_ratio_ = val; }

  std::optional<float> snap_ratio() const { return snap_ratio_; }

  std::optional<WindowSnapActionSource> snap_action_source() const {
    return snap_action_source_;
  }

  // True if the window should be unminimized to the restore bounds, as
  // opposed to the window's current bounds. |unminimized_to_restore_bounds_| is
  // reset to the default value after the window is unminimized.
  bool unminimize_to_restore_bounds() const {
    return unminimize_to_restore_bounds_;
  }
  void set_unminimize_to_restore_bounds(bool value) {
    unminimize_to_restore_bounds_ = value;
  }

  // Gets/sets whether the shelf should be autohidden when this window is
  // fullscreen or active.
  // Note: if true, this will override the logic controlled by
  // hide_shelf_when_fullscreen.
  bool autohide_shelf_when_maximized_or_fullscreen() const {
    return autohide_shelf_when_maximized_or_fullscreen_;
  }
  void set_autohide_shelf_when_maximized_or_fullscreen(bool value) {
    autohide_shelf_when_maximized_or_fullscreen_ = value;
  }

  // Gets/Sets the bounds of the window before it was moved by the auto window
  // management. As long as it was not auto-managed, it will return NULL.
  const std::optional<gfx::Rect> pre_auto_manage_window_bounds() {
    return pre_auto_manage_window_bounds_;
  }
  void set_pre_auto_manage_window_bounds(const gfx::Rect& bounds) {
    pre_auto_manage_window_bounds_ = std::make_optional(bounds);
  }

  // Gets/Sets the property that is used on window added to workspace event.
  const std::optional<gfx::Rect> pre_added_to_workspace_window_bounds() {
    return pre_added_to_workspace_window_bounds_;
  }
  void set_pre_added_to_workspace_window_bounds(const gfx::Rect& bounds) {
    pre_added_to_workspace_window_bounds_ = std::make_optional(bounds);
  }

  // Gets the persistent window info that is used on restoring persistent
  // window bounds in multi-displays scenario.
  PersistentWindowInfo* persistent_window_info_of_display_removal() {
    return persistent_window_info_of_display_removal_.get();
  }
  void reset_persistent_window_info_of_display_removal() {
    persistent_window_info_of_display_removal_.reset();
  }

  // Gets the persistent window info that is used to restore persistent
  // window bounds on screen rotation.
  PersistentWindowInfo* persistent_window_info_of_screen_rotation() {
    return persistent_window_info_of_screen_rotation_.get();
  }

  // Whether the window is being dragged.
  bool is_dragged() const { return !!drag_details_; }

  // Whether or not the window's position or size was changed by a user.
  bool bounds_changed_by_user() const { return bounds_changed_by_user_; }

  // True if the window should not adjust the window's bounds when
  // virtual keyboard bounds changes.
  // TODO(oshima): This is hack. Replace this with proper
  // implementation based on EnsureCaretNotInRect.
  bool ignore_keyboard_bounds_change() const {
    return ignore_keyboard_bounds_change_;
  }
  void set_ignore_keyboard_bounds_change(bool ignore_keyboard_bounds_change) {
    ignore_keyboard_bounds_change_ = ignore_keyboard_bounds_change;
  }

  // True if the window bounds can be updated directly using SET_BOUNDS event.
  void set_allow_set_bounds_direct(bool value) {
    allow_set_bounds_direct_ = value;
  }
  bool allow_set_bounds_direct() const { return allow_set_bounds_direct_; }

  // Returns a pointer to DragDetails during drag operations.
  const DragDetails* drag_details() const { return drag_details_.get(); }
  DragDetails* drag_details() { return drag_details_.get(); }

  const std::vector<chromeos::WindowStateType>& window_state_restore_history()
      const {
    return window_state_restore_history_;
  }

  bool HasDelegate() const;
  void SetDelegate(std::unique_ptr<WindowStateDelegate> delegate);

  // Creates PersistentWindowInfo on display removal or display rotation.
  // `for_display_removal` indicates to create
  // `persistent_window_info_of_display_removal_`, otherwise
  // `persistent_window_info_of_screen_rotation_`.
  void CreatePersistentWindowInfo(bool was_landscape_before_rotation,
                                  const gfx::Rect& restore_bounds_in_parent,
                                  bool for_display_removal);

  // Returns the window's current ash state type.
  // Refer to chromeos::WindowStateType definition in wm_types.h as for why Ash
  // has its own state type.
  chromeos::WindowStateType GetStateType() const;

  // Predicates to check window state.
  bool IsMinimized() const;
  bool IsMaximized() const;
  bool IsFullscreen() const;
  bool IsSnapped() const;
  bool IsPinned() const;
  bool IsTrustedPinned() const;
  bool IsPip() const;
  bool IsFloated() const;

  // Gets the id of the display to show fullscreen on.
  // Returns kInvalidDisplay if not set.
  int64_t GetFullscreenTargetDisplayId() const;

  // True if the window's state type is chromeos::WindowStateType::kMaximized,
  // chromeos::WindowStateType::kFullscreen or
  // chromeos::WindowStateType::kPinned.
  bool IsMaximizedOrFullscreenOrPinned() const;

  // True if the window's state type is chromeos::WindowStateType::kNormal or
  // chromeos::WindowStateType::kDefault.
  bool IsNormalStateType() const;

  bool IsNormalOrSnapped() const;

  // Returns true if the window is vertical or horizontal maximized. The window
  // is in normal state type with vertical or horizontal axis maximized.
  bool IsVerticalOrHorizontalMaximized() const;

  // Return true if the window is in normal state but not horizontal or vertical
  // maximized.
  bool IsNonVerticalOrHorizontalMaximizedNormalState() const;

  bool IsActive() const;

  // Returns true if the window's location can be controlled by the user.
  bool IsUserPositionable() const;

  // Checks if the window can change its state accordingly.
  bool CanFullscreen() const;
  bool CanMaximize() const;
  bool CanMinimize() const;
  bool CanResize() const;
  // CanSnap() checks if the window can be snapped on the display which
  // currently the window is on, whereas CanSnapOnDisplay() checks the
  // snappability on the given |display|.
  bool CanSnap();
  bool CanSnapOnDisplay(display::Display display) const;
  bool CanActivate() const;

  // Returns true if the window has restore bounds.
  bool HasRestoreBounds() const;

  // These methods use aura::WindowProperty to change the window's state
  // instead of using WMEvent directly. This is to use the same mechanism as
  // what views::Widget is using.
  void Maximize();
  void Minimize();
  void Unminimize();

  void Activate();
  void Deactivate();

  // Set the window state to its previous applicable window state.
  void Restore();

  // Determines whether transitioning from the `previous_state` to the current
  // state counts as restoring.
  bool IsRestoring(chromeos::WindowStateType previous_state) const;

  // Caches, then disables z-ordering state and then stacks |window_| below
  // |window_on_top| if |window_| currently has a special z-order.
  void DisableZOrdering(aura::Window* window_on_top);

  // Restores the z-ordering state that a window might have cached.
  void RestoreZOrdering();

  // Invoked when a WMevent occurs, which drives the internal
  // state machine.
  void OnWMEvent(const WMEvent* event);

  // Sets `bounds` as is and ensure the layer is aligned with pixel boundary.
  // For use in unit tests.
  void SetBoundsDirectForTesting(const gfx::Rect& bounds);

  // TODO(oshima): Try hiding these methods and making them accessible only to
  // state impl. State changes should happen through events (as much
  // as possible).

  // Gets the current bounds in screen DIP coordinates.
  gfx::Rect GetCurrentBoundsInScreen() const;

  // Saves the current bounds to be used as a restore bounds.
  void SaveCurrentBoundsForRestore();

  // Same as |GetRestoreBoundsInScreen| except that it returns the
  // bounds in the parent's coordinates.
  gfx::Rect GetRestoreBoundsInParent() const;

  // Returns the restore bounds property on the window in the virtual screen
  // coordinates. The bounds can be NULL if the bounds property does not
  // exist for the window. The window owns the bounds object.
  gfx::Rect GetRestoreBoundsInScreen() const;

  // Same as |SetRestoreBoundsInScreen| except that the bounds is in the
  // parent's coordinates.
  void SetRestoreBoundsInParent(const gfx::Rect& bounds_in_parent);

  // Sets the restore bounds property on the window in the virtual screen
  // coordinates.  Deletes existing bounds value if exists.
  void SetRestoreBoundsInScreen(const gfx::Rect& bounds_in_screen);

  // Deletes and clears the restore bounds property on the window.
  void ClearRestoreBounds();

  // Shrink window from work_area/vertical maximized state.
  // If window is not vertically shrinkable, return false.
  bool VerticallyShrinkWindow(const gfx::Rect& work_area);

  // Shrink window from work_area/horizontal maximized state.
  // If window is not horizontally shrinkable, return false.
  bool HorizontallyShrinkWindow(const gfx::Rect& work_area);

  // Replace the State object of a window with a state handler which can
  // implement a new window manager type. The passed object will be owned
  // by this object and the returned object will be owned by the caller.
  std::unique_ptr<State> SetStateObject(std::unique_ptr<State> new_state);

  // Updates |snap_ratio_| with the current snapped window to screen ratio.
  // Should be called by snap events and bound events, or when resizing a
  // snapped window.
  void UpdateSnapRatio();

  // Forcefully updates `snap_ratio` based on the given `target_bounds`. You
  // usually should use `UpdateSnapRatio()` instead. This method does not check
  // whether `window()` is in the snapped state, so the caller must be sure that
  // `window()` is to-be-snapped. Use with care.
  void ForceUpdateSnapRatio(const gfx::Rect& target_bounds);

  // Gets/sets whether the shelf should be hidden when this window is
  // fullscreen.
  bool GetHideShelfWhenFullscreen() const;
  void SetHideShelfWhenFullscreen(bool value);

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

  // Whether or not the window's position can be managed by the
  // auto management logic.
  bool GetWindowPositionManaged() const;
  void SetWindowPositionManaged(bool managed);

  // True if the window should be offered a chance to consume special system
  // keys such as brightness, volume, etc. that are usually handled by the
  // shell.
  bool CanConsumeSystemKeys() const;
  void SetCanConsumeSystemKeys(bool can_consume_system_keys);

  // True if the window is in "immersive full screen mode" which is slightly
  // different from the normal fullscreen mode by allowing the user to reveal
  // the top portion of the window through a touch / mouse gesture. It might
  // also allow the shelf to be shown in some situations.
  bool IsInImmersiveFullscreen() const;

  // Sets `bounds_changed_by_user_` to the given value and resets the
  // corresponding variables.
  void SetBoundsChangedByUser(bool bounds_changed_by_user);

  // Creates and takes ownership of a pointer to DragDetails when resizing is
  // active. This should be done before a resizer gets created.
  void CreateDragDetails(const gfx::PointF& point_in_parent,
                         int window_component,
                         ::wm::WindowMoveSource source);

  // Deletes and clears a pointer to DragDetails. This should be done when the
  // resizer gets destroyed.
  void DeleteDragDetails();

  // Sets the currently stored restore bounds and clears the restore bounds.
  void SetAndClearRestoreBounds();

  // Notifies that the drag operation has been started. Optionally returns a
  // presentation time recorder for the drag.
  std::unique_ptr<PresentationTimeRecorder> OnDragStarted(int window_component);

  // Notifies that the drag operation has been either completed or reverted.
  // |location| is the last position of the pointer device used to drag.
  void OnCompleteDrag(const gfx::PointF& location);
  void OnRevertDrag(const gfx::PointF& location);

  // Notifies that the window lost the activation.
  void OnActivationLost();

  // Returns the Display that this WindowState is on.
  display::Display GetDisplay() const;

  // Returns the WindowStateType to restore to from the current window state.
  // TODO(aluh): Rename to GetWindowStateTypeForRestore() for clarity.
  chromeos::WindowStateType GetRestoreWindowState() const;

  // Called when `window_` is dragged to maximized to track if it's a
  // mis-triggered drag to maximize behavior.
  void TrackDragToMaximizeBehavior();

  // Allows for caller to prevent property changes within scope.
  base::AutoReset<bool> GetScopedIgnorePropertyChange();

  // Returns true if `current_state_` is `ClientControlledState`.
  // A client-controlled window behaves in a manner distinct from other windows
  // (e.g., windows backed by `DefaultState`). So when making modifications to a
  // window management component, be careful about the following considerations.
  // 1. Client dominance
  //  All window state/bounds changes (excluding “direct methods”) made to a
  //  client-controlled window are considered as just a “request” to the client.
  //  The client has permission to accept or ignore the request so don’t expect
  //  the request is always fulfilled. Also, window state and bounds may be
  //  altered by the client-side without any prior request from the ash-side.
  // 2. Asynchronous changes
  //  All window state/bounds changes (excluding “direct methods”) are not
  //  immediately applied but applied asynchronously when the client accepts the
  //  change request. If you want to perform something sequentially after
  //  changes, use `aura::WindowObserver::OnWindowBoundsChanged` or
  //  `WindowStateObserver::OnPostWindowStateTypeChange`.
  // 3. Direct methods
  //  `SetBoundsDirect*` directly changes the window bounds without informing
  //  the client, bypassing the client-controlled model. These methods can be
  //  useful for implementing ash-decorated window animations that the client is
  //  not interested in. However because the client is unaware of the current
  //  bounds, it may overwrite the current bounds with its preferred bounds at
  //  any time.
  bool is_client_controlled() const { return is_client_controlled_; }

  class TestApi {
   public:
    static State* GetStateImpl(WindowState* window_state) {
      return window_state->current_state_.get();
    }
  };

 private:
  friend class BaseState;
  friend class ClientControlledState;
  friend class DefaultState;
  friend class LockWindowState;
  friend class ScopedBoundsChangeAnimation;
  friend class TabletModeWindowState;
  friend class WorkspaceWindowResizerTest;
  FRIEND_TEST_ALL_PREFIXES(WindowAnimationsTest, CrossFadeToBounds);
  FRIEND_TEST_ALL_PREFIXES(WindowAnimationsTest, CrossFadeHistograms);
  FRIEND_TEST_ALL_PREFIXES(WindowAnimationsTest,
                           CrossFadeToBoundsFromTransform);

  // A class can temporarily change the window bounds change animation type.
  class ScopedBoundsChangeAnimation : public aura::WindowObserver {
   public:
    ScopedBoundsChangeAnimation(aura::Window* window,
                                BoundsChangeAnimationType animation_type);

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

    ~ScopedBoundsChangeAnimation() override;

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

   private:
    raw_ptr<aura::Window> window_;
    BoundsChangeAnimationType previous_bounds_animation_type_;
  };

  explicit WindowState(aura::Window* window);

  WindowStateDelegate* delegate() { return delegate_.get(); }
  BoundsChangeAnimationType bounds_animation_type() {
    return bounds_animation_type_;
  }

  // Returns the window's current z-ordering state.
  ui::ZOrderLevel GetZOrdering() const;

  // Returns the window's current show state.
  ui::WindowShowState GetShowState() const;

  // Sets the window's bounds in screen coordinates.
  void SetBoundsInScreen(const gfx::Rect& bounds_in_screen);

  // Adjusts the |bounds| so that they are flush with the edge of the
  // workspace in clamshell mode if the window represented by |window_state|
  // is side snapped. It is called for workspace events.
  void AdjustSnappedBoundsForDisplayWorkspaceChange(gfx::Rect* bounds);

  // Updates the window properties(show state, pin type) according to the
  // current window state type.
  // Note that this does not update the window bounds.
  void UpdateWindowPropertiesFromStateType();

  void NotifyPreStateTypeChange(
      chromeos::WindowStateType old_window_state_type);
  void NotifyPostStateTypeChange(
      chromeos::WindowStateType old_window_state_type);

  // Sets `bounds_in_parent` as is and ensure the layer is aligned with pixel
  // boundary.
  void SetBoundsDirect(const gfx::Rect& bounds_in_parent);

  // Sets the window's `bounds_in_parent` with constraint where the size of the
  // new bounds will not exceeds the size of the work area.
  void SetBoundsConstrained(const gfx::Rect& bounds_in_parent);

  // Sets the window's `bounds_in_parent` and transitions to the new bounds with
  // a scale animation, with duration specified by `duration`.
  void SetBoundsDirectAnimated(
      const gfx::Rect& bounds_in_parent,
      base::TimeDelta duration = kBoundsChangeSlideDuration,
      gfx::Tween::Type animation_type = gfx::Tween::LINEAR);

  // Sets the window's `bounds_in_parent` and transition to the new bounds with
  // a cross fade animation. If `float_state` has a value, sets a custom
  // float/unfloat cross fade animation.
  void SetBoundsDirectCrossFade(const gfx::Rect& bounds_in_parent,
                                std::optional<bool> float_state = std::nullopt);

  // Called before the state change and update PIP related state, such as next
  // window animation type, upon state change.
  void OnPrePipStateChange(chromeos::WindowStateType old_window_state_type);

  // Called after the state change and update PIP related state, such as next
  // window animation type, upon state change.
  void OnPostPipStateChange(chromeos::WindowStateType old_window_state_type);

  // Collects PIP enter and exit metrics:
  void CollectPipEnterExitMetrics(bool enter);

  // Records the time since partial split was started. Does nothing if the
  // window was not partial.
  void MaybeRecordPartialDuration();

  // Called after the window state changes to update the window state restore
  // history stack.
  void UpdateWindowStateRestoreHistoryStack(
      chromeos::WindowStateType previous_state_type);

  // Used in tablet mode to get the window state type depends on whether the
  // window is maximizable. If not, the window will be put in
  // `WindowStateType::kNormal` state and be centered to the work area of the
  // current display.
  chromeos::WindowStateType GetWindowTypeOnMaximizable() const;

  // aura::WindowObserver:
  void OnWindowPropertyChanged(aura::Window* window,
                               const void* key,
                               intptr_t old) override;
  void OnWindowAddedToRootWindow(aura::Window* window) override;
  void OnWindowDestroying(aura::Window* window) override;
  void OnWindowBoundsChanged(aura::Window* window,
                             const gfx::Rect& old_bounds,
                             const gfx::Rect& new_bounds,
                             ui::PropertyChangeReason reason) override;
  void OnWindowParentChanged(aura::Window* window,
                             aura::Window* parent) override;
  void OnWindowVisibilityChanged(aura::Window* window, bool visible) override;

  bool CanUnresizableSnapOnDisplay(display::Display display) const;

  void RecordWindowSnapActionSource(WindowSnapActionSource snap_action_source);

  // Gets called by the `drag_to_maximize_mis_trigger_timer_` to check the drag
  // to maximize behavior's validity and record the number of mis-triggers.
  void CheckAndRecordDragMaximizedBehavior();

  // Read out the window cycle snap action through ChromeVox. It can be snap a
  // window to the left, right or unsnapped window. `message_id` provides the
  // text will be read out.
  void ReadOutWindowCycleSnapAction(int message_id);

  // Counter used to track the number of mis-triggers of drag to maximize
  // behavior for `window_`.
  int num_of_drag_to_maximize_mis_triggers_ = 0;

  // Started when `window_` is dragged to maximized. Runs
  // `CheckAndRecordDragToMaximizeMisTriggers` to record number of mis-triggers.
  base::OneShotTimer drag_to_maximize_mis_trigger_timer_;

  // Will be set to true if `window_` has been dragged to maximized. Only when
  // it's true, we will record the number of mis-triggers of drag to maximize
  // behavior for `window_` while `this` is being destroyed.
  bool has_ever_been_dragged_to_maximized_ = false;

  // The owner of this window settings.
  raw_ptr<aura::Window> window_;
  std::unique_ptr<WindowStateDelegate> delegate_;

  bool bounds_changed_by_user_ = false;
  std::unique_ptr<DragDetails> drag_details_;

  bool unminimize_to_restore_bounds_ = false;
  bool ignore_keyboard_bounds_change_ = false;
  bool autohide_shelf_when_maximized_or_fullscreen_ = false;
  ui::ZOrderLevel cached_z_order_ = ui::ZOrderLevel::kNormal;
  bool allow_set_bounds_direct_ = false;
  bool is_moving_to_another_display_ = false;

  bool is_handling_float_event_ = false;

  // True while a snap event is being handled. Needed because a snap event can
  // trigger other events, during which we don't want the nested events to
  // update the snap ratio.
  bool is_handling_snap_event_ = false;

  // Set to false while a window may about to be unsnapped. Needed because when
  // a drag to unsnap starts, the state type is still considered snapped, but we
  // don't want to update the snap ratio with the target unsnapped bounds.
  bool can_update_snap_ratio_ = true;

  // Contains the window's target snap ratio if it's going to be snapped by a
  // WMEvent, and the updated window snap ratio if the snapped window's bounds
  // are changed while it remains snapped. It will be used to calculate the
  // desired snapped window bounds for a WMEvent, or adjust the window's bounds
  // when display or workarea changes, or decide what the window bounds should
  // be if restoring the window back to a snapped window state, etc.
  std::optional<float> snap_ratio_;

  // Contains the snap action source for the most recent snap event.
  std::optional<WindowSnapActionSource> snap_action_source_;

  // A property to remember the window position which was set before the
  // auto window position manager changed the window bounds, so that it can
  // get restored when only this one window gets shown.
  std::optional<gfx::Rect> pre_auto_manage_window_bounds_;

  // A property which resets when bounds is changed by user and sets when it
  // is nullptr, and window is removing from a workspace.
  std::optional<gfx::Rect> pre_added_to_workspace_window_bounds_;

  // A property to remember the persistent window info used in multi-displays
  // scenario to attempt to restore windows to their original bounds when
  // displays are restored to their previous states.
  std::unique_ptr<PersistentWindowInfo>
      persistent_window_info_of_display_removal_;

  // A property to remember the persistent window info when screen rotation
  // happens. It will be used to restore windows' bounds when rotating back to
  // the previous screen orientation. Note, `kLandscapePrimary` and
  // `kLandscapeSecondary` will be treated as the same screen orientation, since
  // the window's bounds should be the same in each landscape orientation. Same
  // for portrait screen orientation.
  std::unique_ptr<PersistentWindowInfo>
      persistent_window_info_of_screen_rotation_;

  base::ObserverList<WindowStateObserver>::Unchecked observer_list_;

  // True to ignore a property change event to avoid reentrance in
  // UpdateWindowStateType()
  bool ignore_property_change_ = false;

  std::unique_ptr<State> current_state_;

  // The animation type for the bounds change.
  BoundsChangeAnimationType bounds_animation_type_ =
      BoundsChangeAnimationType::kAnimate;

  // When the current (or last) PIP session started.
  base::TimeTicks pip_start_time_;

  // When the window was partial split. Not null during partial split.
  base::TimeTicks partial_start_time_;

  // Maintains the window state restore history that the current window state
  // can restore back to, with relevant restore states.
  // See `kWindowStateRestoreHistoryLayerMap` in the cc file for what window
  // state types can be put in the restore history stack.
  std::vector<chromeos::WindowStateType> window_state_restore_history_;

  // True if `current_state_` is `ClientControlledState`.
  bool is_client_controlled_{false};
};

}  // namespace ash

#endif  // ASH_WM_WINDOW_STATE_H_