chromium/components/remote_cocoa/app_shim/immersive_mode_controller_cocoa.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_IMMERSIVE_MODE_CONTROLLER_COCOA_H_
#define COMPONENTS_REMOTE_COCOA_APP_SHIM_IMMERSIVE_MODE_CONTROLLER_COCOA_H_

#import <AppKit/AppKit.h>

#include <optional>

#import "components/remote_cocoa/app_shim/bridged_content_view.h"
#import "components/remote_cocoa/app_shim/native_widget_mac_nswindow.h"
#include "components/remote_cocoa/app_shim/remote_cocoa_app_shim_export.h"
#include "components/remote_cocoa/common/native_widget_ns_window.mojom-shared.h"

@class ImmersiveModeMapper;

namespace remote_cocoa {
class ImmersiveModeControllerCocoa;
}  // namespace remote_cocoa

// Host of the overlay view.
@interface ImmersiveModeTitlebarViewController
    : NSTitlebarAccessoryViewController {
  NSView* __strong _blank_separator_view;
  base::WeakPtr<remote_cocoa::ImmersiveModeControllerCocoa>
      _immersive_mode_controller;
}
@end

namespace gfx {
class Rect;
}

namespace remote_cocoa {

// TODO(mek): This should not be exported and used outside of remote_cocoa. So
// figure out how to restructure code so callers outside of remote_cocoa can
// stop existing.
REMOTE_COCOA_APP_SHIM_EXPORT bool IsNSToolbarFullScreenWindow(NSWindow* window);

// Manages a single fullscreen session.
class REMOTE_COCOA_APP_SHIM_EXPORT ImmersiveModeControllerCocoa {
 public:
  explicit ImmersiveModeControllerCocoa(
      NativeWidgetMacNSWindow* browser_window,
      NativeWidgetMacNSWindow* overlay_window);
  virtual ~ImmersiveModeControllerCocoa();

  // Must be called once and only once after construction. Prevents the side-
  // effects of adding a toolbar accessory from accessing partially constructed
  // objects.
  virtual void Init();
  bool is_initialized() { return initialized_; }

  virtual void FullscreenTransitionCompleted();
  virtual void OnTopViewBoundsChanged(const gfx::Rect& bounds);
  virtual void UpdateToolbarVisibility(
      std::optional<mojom::ToolbarVisibilityStyle> style);

  // Reveal top chrome leaving it visible until all outstanding calls to
  // RevealLock() are balanced with RevealUnlock(). Reveal locks will persist
  // through calls to UpdateToolbarVisibility(). For example, the current
  // ToolbarVisibilityStyle is set to kAlways and RevealLock() has been called.
  // If ToolbarVisibilityStyle is then changed to kAutohide, top
  // chrome will stay on screen until RevealUnlock() is called. At that point
  // top chrome will autohide.
  void RevealLock();
  void RevealUnlock();
  int reveal_lock_count() { return reveal_lock_count_; }

  // When set to true, calls to RevealLock() and RevealUnlock() will have no
  // visual effect. The lock/unlock calls are still counted and will go into
  // effect when this function is called with false.
  void SetIgnoreRevealLocks(bool ignore);

  // Called when a reveal lock/unlock is ready to take effect.
  virtual void RevealLocked();
  virtual void RevealUnlocked();

  // Returns true if the toolbar is visible, either because there are
  // outstanding reveal locks, or the user hovers over the upper border of the
  // screen.
  bool IsToolbarRevealed();

  // Called when the reveal status changes.
  void OnToolbarRevealMaybeChanged();

  // Called when the menu bar reveal status changes.
  void OnMenuBarRevealChanged();

  // Called when a child window is added to the observed windows.
  // `ObserveChildWindows` controls which windows are being observed.
  virtual void OnChildWindowAdded(NSWindow* child);

  // Called when a child window is removed from the observed windows.
  virtual void OnChildWindowRemoved(NSWindow* child);

  // Start observing child windows of `window`.
  void ObserveChildWindows(NSWindow* window);

  // Stop observing child windows of `window`.
  void StopObservingChildWindows(NSWindow* window);

  // Return true if the child window should trigger OnChildWindowAdded and
  // OnChildWindowRemoved events, otherwise return false.
  // Called for browser window children that survive the transition to
  // fullscreen.
  virtual bool ShouldObserveChildWindow(NSWindow* child);

  NSWindow* browser_window();
  NSWindow* overlay_window();
  BridgedContentView* overlay_content_view();

  // Called when `immersive_mode_titlebar_view_controller_`'s view is moved to
  // a different window.
  void ImmersiveModeViewWillMoveToWindow(NSWindow* window);

  // Returns true if kImmersiveFullscreenTabs is being used.
  virtual bool IsTabbed();
  bool IsContentFullscreen();

  ImmersiveModeTitlebarViewController*
  immersive_mode_titlebar_view_controller_for_testing() {
    return immersive_mode_titlebar_view_controller_;
  }

 protected:
  // Used by derived classes to manually set last_used_style_. Typically this is
  // used while a RevealLock is active, allowing for a style change after the
  // last RevealLock has been released.
  void set_last_used_style(std::optional<mojom::ToolbarVisibilityStyle> style) {
    last_used_style_ = style;
  }
  std::optional<mojom::ToolbarVisibilityStyle> last_used_style() {
    return last_used_style_;
  }

  // Layout the `window` on top of the `anchor_view`. The `window` will occupy
  // the same place on screen as the `anchor_view`, completely occluding the
  // `anchor_view`. The `window` is clear but needs to overlay the `anchor_view`
  // to handle drag events.
  // If the `anchor_view` is offscreen, the `window` will be moved offscreen.
  void LayoutWindowWithAnchorView(NSWindow* window, NSView* anchor_view);

  // Reanchor the overlay window with its anchor view.
  virtual void Reanchor();

 private:
  // Get offscreen y origin. Used for moving overlay windows offscreen.
  double GetOffscreenYOrigin();

  // Notify the browser window that the reveal status changes.
  void NotifyBrowserWindowAboutToolbarRevealChanged();

  // Updates the visibility of the thin controller. The thin controller will
  // only become visible when the toolbar is hidden.
  // TODO(crbug.com/40240734): Remove when fixed by Apple.
  void UpdateThinControllerVisibility();

  // Calls either RevealLocked() or RevealUnlocked() based on the current
  // reveal_lock_count_.
  void ApplyRevealLockState();

  // `UpdateToolbarVisibility(style)` will skip updating if `last_used_style_`
  // and `style` are the same. `ForceToolbarVisibilityUpdate()` forces a toolbar
  // visibility update of `last_used_style_`.
  void ForceToolbarVisibilityUpdate();

  bool initialized_ = false;

  int reveal_lock_count_ = 0;
  bool ignore_reveal_locks_ = false;

  bool is_toolbar_revealed_ = false;

  int menu_bar_height_ = 0;

  std::optional<mojom::ToolbarVisibilityStyle> last_used_style_;
  // Keeps the view controllers hidden until the fullscreen transition is
  // complete.
  bool fullscreen_transition_complete_ = false;

  NativeWidgetMacNSWindow* __weak browser_window_;
  NativeWidgetMacNSWindow* __weak overlay_window_;
  BridgedContentView* __weak overlay_content_view_;

  // A controller for top chrome.
  ImmersiveModeTitlebarViewController* __strong
      immersive_mode_titlebar_view_controller_;

  // A controller that keeps a small portion (0.5px) of the fullscreen AppKit
  // NSWindow on screen.
  // This controller is used as a workaround for an AppKit bug that displays a
  // black bar when changing a NSTitlebarAccessoryViewController's
  // fullScreenMinHeight from zero to non-zero.
  // TODO(crbug.com/40240734): Remove when fixed by Apple.
  NSTitlebarAccessoryViewController* __strong thin_titlebar_view_controller_;

  ImmersiveModeMapper* __strong immersive_mode_mapper_;

  // Keeps track of which windows have received titlebar and reveal locks.
  std::set<NSWindow*> window_lock_received_;

  base::WeakPtrFactory<ImmersiveModeControllerCocoa> weak_ptr_factory_;
};

}  // namespace remote_cocoa

// An empty NSView that is also opaque.
@interface OpaqueView : NSView
@end

#endif  // COMPONENTS_REMOTE_COCOA_APP_SHIM_IMMERSIVE_MODE_CONTROLLER_COCOA_H_