chromium/components/exo/client_controlled_shell_surface.h

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_EXO_CLIENT_CONTROLLED_SHELL_SURFACE_H_
#define COMPONENTS_EXO_CLIENT_CONTROLLED_SHELL_SURFACE_H_

#include <memory>
#include <string>

#include "ash/display/screen_orientation_controller.h"
#include "ash/public/cpp/arc_resize_lock_type.h"
#include "ash/wm/client_controlled_state.h"
#include "base/functional/callback.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "components/exo/client_controlled_accelerators.h"
#include "components/exo/shell_surface_base.h"
#include "ui/base/hit_test.h"
#include "ui/compositor/compositor_lock.h"

namespace ash {
class NonClientFrameViewAsh;
class WideFrameView;
}  // namespace ash

namespace chromeos {
class ImmersiveFullscreenController;
}  // namespace chromeos

namespace exo {
class Surface;
class ClientControlledAcceleratorTarget;

enum class Orientation { PORTRAIT, LANDSCAPE };
enum class ZoomChange { IN, OUT, RESET };

// This class implements a ShellSurface whose window state and bounds are
// controlled by a remote shell client rather than the window manager. The
// position specified as part of the geometry is relative to the origin of
// the screen coordinate system.
class ClientControlledShellSurface : public ShellSurfaceBase,
                                     public ui::CompositorLockClient {
 public:
  // TODO(mukai): integrate this with ShellSurfaceBase's callback.
  class Delegate {
   public:
    virtual ~Delegate() = default;
    virtual void OnGeometryChanged(const gfx::Rect& geometry) = 0;
    virtual void OnStateChanged(chromeos::WindowStateType old_state_type,
                                chromeos::WindowStateType new_state_type) = 0;
    virtual void OnBoundsChanged(chromeos::WindowStateType current_state,
                                 chromeos::WindowStateType requested_state,
                                 int64_t display_id,
                                 const gfx::Rect& bounds_in_display,
                                 bool is_resize,
                                 int bounds_change,
                                 bool is_adjusted_bounds) = 0;
    virtual void OnDragStarted(int component) = 0;
    virtual void OnDragFinished(int x, int y, bool canceled) = 0;
    virtual void OnZoomLevelChanged(ZoomChange zoom_change) = 0;
  };

  ClientControlledShellSurface(Surface* surface,
                               bool can_minimize,
                               int container,
                               bool default_scale_cancellation,
                               bool supports_floated_state);

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

  ~ClientControlledShellSurface() override;

  Delegate* set_delegate(std::unique_ptr<Delegate> delegate) {
    delegate_ = std::move(delegate);
    return delegate_.get();
  }

  void set_server_reparent_window(bool reparent) {
    server_reparent_window_ = reparent;
  }

  // Set bounds in root window coordinates relative to the given display.
  void SetBounds(int64_t display_id, const gfx::Rect& bounds);

  // Set origin of bounds for surface while preserving the size.
  void SetBoundsOrigin(int64_t display_id, const gfx::Point& origin);

  // Set size of bounds for surface while preserving the origin.
  void SetBoundsSize(const gfx::Size& size);

  // Called when the client was maximized.
  void SetMaximized();

  // Called when the client was minimized.
  void SetMinimized();

  // Called when the client was restored.
  void SetRestored();

  // Called when the client changed the fullscreen state. When `fullscreen` is
  // true, `display_id` indicates the id of the display where the surface should
  // be shown, otherwise it is ignored. When `display::kInvalidDisplayId` is
  // specified, the current display may be used.
  void SetFullscreen(bool fullscreen, int64_t display_id);

  // Returns true if this shell surface is currently being dragged.
  bool IsDragging();

  // Pin/unpin the surface. Pinned surface cannot be switched to
  // other windows unless its explicitly unpinned.
  void SetPinned(chromeos::WindowPinType type);

  // Sets the surface to be on top of all other windows.
  void SetAlwaysOnTop(bool always_on_top);

  // Controls the visibility of the system UI when this surface is active.
  void SetSystemUiVisibility(bool autohide);

  // Set orientation for surface.
  void SetOrientation(Orientation orientation);

  // Set shadow bounds in surface coordinates. Empty bounds disable the shadow.
  void SetShadowBounds(const gfx::Rect& bounds);

  // Set the pending scale.
  void SetScale(double scale);

  // Sends the request to change the zoom level to the client.
  void ChangeZoomLevel(ZoomChange change);

  // Sends the window state change event to client.
  void OnWindowStateChangeEvent(chromeos::WindowStateType old_state,
                                chromeos::WindowStateType next_state);

  // Sends the window bounds change event to client. |display_id| specifies in
  // which display the surface should live in. |drag_bounds_change| is
  // a masked value of ash::WindowResizer::kBoundsChange_Xxx, and specifies
  // how the bounds was changed. The bounds change event may also come from a
  // snapped window state change |requested_state|.
  void OnBoundsChangeEvent(chromeos::WindowStateType current_state,
                           chromeos::WindowStateType requested_state,
                           int64_t display_id,
                           const gfx::Rect& bounds,
                           int drag_bounds_change,
                           bool is_adjusted_bounds);

  // Sends the window drag events to client.
  void OnDragStarted(int component);
  void OnDragFinished(bool cancel, const gfx::PointF& location);

  // Starts the drag operation.
  void StartDrag(int component, const gfx::PointF& location);

  // Set if the surface can be maximzied.
  void SetCanMaximize(bool can_maximize);

  // Update the auto hide frame state.
  void UpdateAutoHideFrame();

  // Set the frame button state. The |visible_button_mask| and
  // |enabled_button_mask| is a bit mask whose position is defined
  // in views::CaptionButtonIcon enum.
  void SetFrameButtons(uint32_t frame_visible_button_mask,
                       uint32_t frame_enabled_button_mask);

  // Set the extra title for the surface.
  void SetExtraTitle(const std::u16string& extra_title);

  // Rebind a surface as the root surface of the shell surface.
  void RebindRootSurface(Surface* root_surface,
                         bool can_minimize,
                         int container,
                         bool default_scale_cancellation,
                         bool supports_floated_state);

  // SurfaceTreeHost:
  void DidReceiveCompositorFrameAck() override;

  // ShellSurfaceBase:
  bool IsInputEnabled(Surface* surface) const override;
  void OnSetFrame(SurfaceFrameType type) override;
  void OnSetFrameColors(SkColor active_color, SkColor inactive_color) override;
  void SetSnapPrimary(float snap_ratio) override;
  void SetSnapSecondary(float snap_ratio) override;
  void SetPip() override;
  void UnsetPip() override;
  void SetFloatToLocation(
      chromeos::FloatStartLocation float_start_location) override;
  void OnDidProcessDisplayChanges(
      const DisplayConfigurationChange& configuration_change) override;

  // views::WidgetDelegate:
  void WindowClosing() override;
  bool CanMaximize() const override;
  std::unique_ptr<views::NonClientFrameView> CreateNonClientFrameView(
      views::Widget* widget) override;
  bool ShouldSaveWindowPlacement() const override;
  void SaveWindowPlacement(const gfx::Rect& bounds,
                           ui::WindowShowState show_state) override;
  bool GetSavedWindowPlacement(const views::Widget* widget,
                               gfx::Rect* bounds,
                               ui::WindowShowState* show_state) const override;

  // views::View:
  gfx::Size GetMaximumSize() const override;
  void OnDeviceScaleFactorChanged(float old_dsf, float new_dsf) override;

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

  // ui::CompositorLockClient:
  void CompositorLockTimedOut() override;

  // A factory callback to create ClientControlledState::Delegate.
  using DelegateFactoryCallback = base::RepeatingCallback<
      std::unique_ptr<ash::ClientControlledState::Delegate>(void)>;

  // Set the factory callback for unit test.
  static void SetClientControlledStateDelegateFactoryForTest(
      const DelegateFactoryCallback& callback);

  ash::WideFrameView* wide_frame_for_test() { return wide_frame_.get(); }

  // Used to scale incoming coordinates from the client to DP.
  float GetClientToDpScale() const;

  // Used to scale incoming coordinates from the client to DP before the pending
  // scale is committed.
  float GetClientToDpPendingScale() const;

  // Sets the resize lock type to the surface.
  void SetResizeLockType(ash::ArcResizeLockType resize_lock_type);

  // Update the resizability based on the resize lock type.
  void UpdateResizability() override;

  // exo::ShellSurfaceBase
  void SetSystemModal(bool system_modal) override;

 protected:
  // ShellSurfaceBase:
  float GetScale() const override;

  // SurfaceTreeHost:
  float GetScaleFactor() const override;

 private:
  FRIEND_TEST_ALL_PREFIXES(ClientControlledShellSurfaceTest,
                           OverlayShadowBounds);
  class ScopedSetBoundsLocally;
  class ScopedLockedToRoot;
  class ScopedDeferWindowStateUpdate;

  // ShellSurfaceBase:
  void SetWidgetBounds(const gfx::Rect& bounds,
                       bool adjusted_by_server) override;
  gfx::Rect GetVisibleBounds() const override;
  gfx::Rect GetShadowBounds() const override;
  void InitializeWindowState(ash::WindowState* window_state) override;
  std::optional<gfx::Rect> GetWidgetBounds() const override;
  gfx::Point GetSurfaceOrigin() const override;
  bool OnPreWidgetCommit() override;
  void ShowWidget(bool activate) override;
  void OnPostWidgetCommit() override;
  void OnSurfaceDestroying(Surface* surface) override;

  // Update frame status. This may create (or destroy) a wide frame
  // that spans the full work area width if the surface didn't cover
  // the work area.
  void UpdateFrame();

  void UpdateCaptionButtonModel();

  void UpdateBackdrop();

  void UpdateFrameWidth();

  void UpdateFrameType() override;

  bool GetCanResizeFromSizeConstraints() const override;

  void AttemptToStartDrag(int component, const gfx::PointF& location);

  // Lock the compositor if it's not already locked, or extends the
  // lock timeout if it's already locked.
  // TODO(reveman): Remove this when using configure callbacks for orientation.
  // crbug.com/765954
  void EnsureCompositorIsLockedForOrientationChange();

  ash::WindowState* GetWindowState();
  ash::NonClientFrameViewAsh* GetFrameView();
  const ash::NonClientFrameViewAsh* GetFrameView() const;

  void EnsurePendingScale(bool commit_immediately);

  gfx::Rect GetClientBoundsForWindowBoundsAndWindowState(
      const gfx::Rect& window_bounds,
      chromeos::WindowStateType window_state) const;

  uint32_t frame_visible_button_mask_ = 0;
  uint32_t frame_enabled_button_mask_ = 0;

  std::unique_ptr<Delegate> delegate_;

  // TODO(reveman): Use configure callbacks for orientation. crbug.com/765954
  Orientation pending_orientation_ = Orientation::LANDSCAPE;
  Orientation orientation_ = Orientation::LANDSCAPE;
  Orientation expected_orientation_ = Orientation::LANDSCAPE;

  raw_ptr<ash::ClientControlledState> client_controlled_state_ = nullptr;

  chromeos::WindowStateType pending_window_state_ =
      chromeos::WindowStateType::kNormal;

  bool pending_always_on_top_ = false;

  SurfaceFrameType pending_frame_type_ = SurfaceFrameType::NONE;

  bool can_maximize_ = true;

  std::unique_ptr<chromeos::ImmersiveFullscreenController>
      immersive_fullscreen_controller_;

  std::unique_ptr<ash::WideFrameView> wide_frame_;

  std::unique_ptr<ui::CompositorLock> orientation_compositor_lock_;

  // The extra title to be applied when widget is being created.
  std::u16string initial_extra_title_ = std::u16string();

  bool preserve_widget_bounds_ = false;

  // Checking DragDetails is not sufficient to determine if a bounds
  // request happened during a drag move or resize. If the window resizer
  // requests a bounds update after completing the drag but before the
  // drag details are cleaned up, we want to consider that as a regular
  // bounds update, not a drag move/resize update.
  bool in_drag_ = false;

  // N uses older protocol which expects that server will reparent the window.
  // TODO(oshima): Remove this once all boards are migrated to P or above.
  bool server_reparent_window_ = false;

  bool display_rotating_with_pip_ = false;

  // True if the window state has changed during the commit.
  bool state_changed_ = false;

  // When false, the client handles all display scale changes, so the
  // buffer should be re-scaled to undo any scaling added by exo so that the
  // 1:1 correspondence between the pixels is maintained.
  bool use_default_scale_cancellation_ = false;

  // Client controlled specific accelerator target.
  std::unique_ptr<ClientControlledAcceleratorTarget> accelerator_target_;

  ash::ArcResizeLockType pending_resize_lock_type_ =
      ash::ArcResizeLockType::NONE;

  std::unique_ptr<ScopedDeferWindowStateUpdate>
      scoped_defer_window_state_update_;

  // True if the window supports the floated state.
  bool supports_floated_state_;
};

}  // namespace exo

#endif  // COMPONENTS_EXO_CLIENT_CONTROLLED_SHELL_SURFACE_H_