chromium/components/remote_cocoa/app_shim/native_widget_ns_window_fullscreen_controller.h

// Copyright 2022 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_REMOTE_COCOA_APP_SHIM_NATIVE_WIDGET_NS_WINDOW_FULLSCREEN_CONTROLLER_H_
#define COMPONENTS_REMOTE_COCOA_APP_SHIM_NATIVE_WIDGET_NS_WINDOW_FULLSCREEN_CONTROLLER_H_

#include <optional>

#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "components/remote_cocoa/app_shim/remote_cocoa_app_shim_export.h"
#include "ui/display/types/display_constants.h"
#include "ui/gfx/geometry/rect.h"

namespace remote_cocoa {

class NativeWidgetNSWindowBridge;

class REMOTE_COCOA_APP_SHIM_EXPORT NativeWidgetNSWindowFullscreenController {
 public:
  class Client {
   public:
    // Called when a transition between fullscreen and windowed (or vice-versa).
    // If `is_target_fullscreen` is true, then the target of the transition is
    // fullscreen.
    virtual void FullscreenControllerTransitionStart(
        bool is_target_fullscreen) = 0;

    // Called when a transition between fullscreen and windowed is complete.
    // If `is_fullscreen` is true, then the window is now fullscreen.
    virtual void FullscreenControllerTransitionComplete(bool is_fullscreen) = 0;

    // Set the window's frame to the specified rectangle. If `animate` is true,
    // then animate the transition. Runs the `completion_callback` callback once
    // the animation is complete, or immediately when `animate` is false.
    virtual void FullscreenControllerSetFrame(
        const gfx::Rect& frame,
        bool animate,
        base::OnceCallback<void()> completion_callback) = 0;

    // Call -[NSWindow toggleFullscreen:].
    virtual void FullscreenControllerToggleFullscreen() = 0;

    // Call -[NSWindow close]. Note that this call may result in the caller
    // being destroyed.
    virtual void FullscreenControllerCloseWindow() = 0;

    // Return the display id for the display that the window is currently on.
    virtual int64_t FullscreenControllerGetDisplayId() const = 0;

    // Return the frame that should be set prior to transitioning to fullscreen
    // on the display specified by `display_id`. If `display_id` is invalid,
    // then return an empty rectangle.
    virtual gfx::Rect FullscreenControllerGetFrameForDisplay(
        int64_t display_id) const = 0;

    // Get the window's current frame.
    virtual gfx::Rect FullscreenControllerGetFrame() const = 0;
  };

  explicit NativeWidgetNSWindowFullscreenController(Client* client);
  NativeWidgetNSWindowFullscreenController(
      const NativeWidgetNSWindowFullscreenController&) = delete;
  NativeWidgetNSWindowFullscreenController& operator=(
      const NativeWidgetNSWindowFullscreenController&) = delete;
  ~NativeWidgetNSWindowFullscreenController();

  // Called by NativeWidget::SetFullscreen.
  void EnterFullscreen(int64_t target_display_id);
  void ExitFullscreen();

  // Called from NativeWidgetNSWindowBridge:CloseWindow, indicating that the
  // window has been requested to be closed. If a transition is in progress,
  // then the close will be deferred until after the transition completes.
  void OnWindowWantsToClose();

  // Return true if an active transition has caused closing of the window to be
  // deferred.
  bool HasDeferredWindowClose() const { return has_deferred_window_close_; }

  // Called by NativeWidgetNSWindowBridge::OnWindowWillClose.
  void OnWindowWillClose();

  // Called by -[NSWindowDelegate windowWill/DidEnter/ExitFullScreen:].
  void OnWindowWillEnterFullscreen();
  void OnWindowDidEnterFullscreen();
  void OnWindowWillExitFullscreen();
  void OnWindowDidExitFullscreen();

  // Return false unless the state is kWindowed or kFullscreen.
  bool IsInFullscreenTransition() const;

  // Return true if the window can be resized. The window cannot be resized
  // while fullscreen or during a transition.
  bool CanResize() const;

  // Return the fullscreen state that will be arrived at when all transition
  // is done.
  bool GetTargetFullscreenState() const;

 private:
  enum class State {
    // In windowed mode.
    kWindowed,
    // Moving the window to the target display on which it will go fullscreen.
    kWindowedMovingToFullscreenTarget,
    // In transition to enter fullscreen mode. This encompasses the following
    // states:
    // - From the kWindowed state, a task for ToggleFullscreen has been
    //   posted.
    // - OnWindowWillEnterFullscreen has been called (either as a result of
    //   ToggleFullscreen, or as a result of user interaction), but neither
    //   OnWindowDidEnterFullscreen nor OnWindowDidExitFullscreen have been
    //   called yet.
    kEnterFullscreenTransition,
    // In fullscreen mode.
    kFullscreen,
    // In transition to exit fullscreen mode. This encompasses the following
    // states:
    // - From the kFullscreen state, a task for ToggleFullscreen has been
    //   posted.
    // - OnWindowWillExitFullscreen has been called (either as a result of
    //   ToggleFullscreen, or as a result of user interaction), but neither
    //   OnWindowDidExitFullscreen nor OnWindowDidEnterFullscreen have been
    //   called yet.
    kExitFullscreenTransition,
    // Moving the window back to its original position from before it entered
    // fullscreen.
    kWindowedRestoringOriginalFrame,
    // The window has been closed.
    kClosed,
  };
  struct PendingState {
    bool is_fullscreen = false;
    int64_t display_id = display::kInvalidDisplayId;
  };

  // Move the window to `target_display_id`, and then post a task to go
  // fullscreen.
  void MoveToTargetDisplayThenToggleFullscreen(int64_t target_display_id);

  // Set the window's frame back to `windowed_frame_`, and then return to
  // the kWindowed state.
  void RestoreWindowedFrame();
  // Notifies the client that the fullscreen exit transition has completed after
  // the frame has been restored to its original position.
  void OnWindowedFrameRestored();

  // Helper function wrapping -[NSWindow toggleFullscreen:].
  void ToggleFullscreen();

  // If not currently in transition, consume `pending_state_` and start a
  // transition to the state it specifies.
  void HandlePendingState();

  // If there exists a deferred close, then close the window, set the
  // current state to kClosed, and return true.
  bool HandleDeferredClose();

  // Set `state` to `new_state`, and invalidate any posted tasks. Posted tasks
  // exist to transition from the current state to a new state, and so if the
  // current state changes, then those tasks are no longer applicable.
  void SetStateAndCancelPostedTasks(State new_state);

  State state_ = State::kWindowed;

  // If a call to EnterFullscreen or ExitFullscreen happens during a
  // transition, then that final requested state is stored in `pending_state_`.
  std::optional<PendingState> pending_state_;

  // If we call setFrame while in fullscreen transitions, then we will need to
  // restore the original window frame when we return to windowed mode. We save
  // that original frame in `windowed_frame_`, and set set
  // `restore_windowed_frame_` to true if we call setFrame.
  bool restore_windowed_frame_ = false;
  std::optional<gfx::Rect> windowed_frame_;

  // Trying to close an NSWindow during a fullscreen transition will cause the
  // window to lock up. Use this to track if CloseWindow was called during a
  // fullscreen transition, to defer the -[NSWindow close] call until the
  // transition is complete.
  // https://crbug.com/945237
  bool has_deferred_window_close_ = false;

  // Weak, owns `this`.
  const raw_ptr<Client> client_;
  base::WeakPtrFactory<NativeWidgetNSWindowFullscreenController> weak_factory_{
      this};
};

}  // namespace remote_cocoa

#endif  // COMPONENTS_REMOTE_COCOA_APP_SHIM_NATIVE_WIDGET_NS_WINDOW_FULLSCREEN_CONTROLLER_H_