chromium/ash/wm/scoped_window_tucker.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_SCOPED_WINDOW_TUCKER_H_
#define ASH_WM_SCOPED_WINDOW_TUCKER_H_

#include <memory>

#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_observer.h"
#include "ash/wm/window_state.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "ui/aura/scoped_window_event_targeting_blocker.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/wm/public/activation_change_observer.h"

namespace ash {

constexpr char kTuckUserAction[] = "FloatWindowTucked";
constexpr char kUntuckUserAction[] = "FloatWindowUntucked";

// Scoped class which makes modifications while a window is tucked. It owns a
// tuck handle widget that will bring the hidden window back onscreen. Users of
// the class need to ensure that window outlives instance of this class.
class ScopedWindowTucker : public wm::ActivationChangeObserver,
                           public OverviewObserver,
                           public aura::WindowObserver {
 public:
  static constexpr int kTuckHandleWidth = 20;
  static constexpr int kTuckHandleHeight = 92;

  class Delegate {
   public:
    Delegate();
    virtual ~Delegate();

    // Paint the tuck handle.
    virtual void PaintTuckHandle(gfx::Canvas* canvas, int width, bool left) = 0;

    // Returns `kContainerIdsToMove` for the parent of the handle widget.
    virtual int ParentContainerId() const = 0;

    // Updates the position of the window.
    virtual void UpdateWindowPosition(aura::Window* window, bool left) = 0;

    // Destroys `this_`, which will untuck `window_` and set the window bounds
    // back onscreen.
    virtual void UntuckWindow(aura::Window* window) = 0;

    // Hides the window after the tuck animation is finished. This is so it will
    // behave similarly to a minimized window in overview.
    virtual void OnAnimateTuckEnded(aura::Window* window) = 0;

    // Returns proper bounds for tuck handle.
    virtual gfx::Rect GetTuckHandleBounds(
        bool left,
        const gfx::Rect& window_bounds) const = 0;

    base::WeakPtr<Delegate> GetWeakPtr() {
      return weak_ptr_factory_.GetWeakPtr();
    }

   private:
    base::WeakPtrFactory<Delegate> weak_ptr_factory_{this};
  };

  // Represents a tuck handle that untucks floated / PiP windows from offscreen.
  class TuckHandleView : public views::Button,
                         public views::ViewTargeterDelegate {
   public:
    TuckHandleView(base::WeakPtr<Delegate> delegate,
                   base::RepeatingClosure callback,
                   bool left);
    TuckHandleView(const TuckHandleView&) = delete;
    TuckHandleView& operator=(const TuckHandleView&) = delete;
    ~TuckHandleView() override;

    // views::Button:
    void OnThemeChanged() override;
    void PaintButtonContents(gfx::Canvas* canvas) override;
    void OnGestureEvent(ui::GestureEvent* event) override;

    // views::ViewTargeterDelegate:
    bool DoesIntersectRect(const views::View* target,
                           const gfx::Rect& rect) const override;

   private:
    // The delegate held by `ScopedWindowTucker`.
    base::WeakPtr<Delegate> scoped_window_tucker_delegate_;

    // Whether the tuck handle is on the left or right edge of the screen. A
    // left tuck handle will have the chevron arrow pointing right and vice
    // versa.
    const bool left_;
  };

  // Creates an instance for `window` where `left` is the side of the screen
  // that the tuck handle is on.
  explicit ScopedWindowTucker(std::unique_ptr<Delegate> delegate,
                              aura::Window* window,
                              bool left);
  ScopedWindowTucker(const ScopedWindowTucker&) = delete;
  ScopedWindowTucker& operator=(const ScopedWindowTucker&) = delete;
  ~ScopedWindowTucker() override;

  // Returns the target window the resizer was created for.
  aura::Window* window() { return window_; }

  // Returns the tuck handle widget that this tucker manages.
  views::Widget* tuck_handle_widget() { return tuck_handle_widget_.get(); }

  // Returns true if the window is tucked to the left of the screen edge.
  bool left() const { return left_; }

  // Starts the tucking animation.
  void AnimateTuck();

  // Starts the untucking animation. Runs `callback` when the animation
  // is completed.
  void AnimateUntuck(base::OnceClosure callback);

  // Runs `delegate`'s `UntuckWindow()`.
  void UntuckWindow();

  // Runs `delegate`'s `OnAnimateTuckEnded()`.
  void OnAnimateTuckEnded();

  // wm::ActivationChangeObserver:
  void OnWindowActivated(ActivationReason reason,
                         aura::Window* gained_active,
                         aura::Window* lost_active) override;

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

  // aura::WindowObserver:
  void OnWindowBoundsChanged(aura::Window* window,
                             const gfx::Rect& old_bounds,
                             const gfx::Rect& new_bounds,
                             ui::PropertyChangeReason reason) override;

 private:
  // Initializes the tuck handle widget.
  void InitializeTuckHandleWidget();

  // Slides the tuck handle offscreen and onscreen when entering and exiting
  // overview mode respectively.
  void OnOverviewModeChanged(bool in_overview);

  std::unique_ptr<Delegate> delegate_;

  // The window that is being tucked. Will be tucked and untucked by the tuck
  // handle.
  raw_ptr<aura::Window> window_;

  // True if the window is tucked to the left screen edge, false otherwise.
  bool left_ = false;

  // Blocks events from hitting the window while `this` is alive.
  aura::ScopedWindowEventTargetingBlocker event_blocker_;

  views::UniqueWidgetPtr tuck_handle_widget_ =
      std::make_unique<views::Widget>();

  base::ScopedObservation<OverviewController, OverviewObserver>
      overview_observer_{this};

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

  base::WeakPtrFactory<ScopedWindowTucker> weak_factory_{this};
};

}  // namespace ash

#endif  // ASH_WM_SCOPED_WINDOW_TUCKER_H_