use std::cell::Cell;
use objc2::{
DefinedClass, MainThreadOnly, define_class,
rc::Retained,
runtime::{AnyObject, NSObject, NSObjectProtocol},
sel,
};
use objc2_app_kit::{
NSWindow, NSWindowDidEnterFullScreenNotification, NSWindowDidExitFullScreenNotification,
NSWindowWillEnterFullScreenNotification, NSWindowWillExitFullScreenNotification,
};
use objc2_foundation::{
MainThreadMarker, NSNotification, NSNotificationCenter, NSObjectNSDelayedPerforming,
};
use crate::window::FullscreenTarget;
const TRANSITION_TIMEOUT: f64 = 3.0;
pub(crate) struct FullscreenTransitionIvars {
in_progress: Cell<bool>,
pending: Cell<Option<Option<FullscreenTarget>>>,
on_transition_end: Box<dyn Fn()>,
}
define_class!(
#[unsafe(super(NSObject))]
#[name = "TauriCefFullscreenTransition"]
#[ivars = FullscreenTransitionIvars]
#[thread_kind = MainThreadOnly]
pub(crate) struct FullscreenTransition;
unsafe impl NSObjectProtocol for FullscreenTransition {}
impl FullscreenTransition {
#[unsafe(method(windowWillChangeFullScreen:))]
fn window_will_change_fullscreen(&self, _: &NSNotification) {
self.ivars().in_progress.set(true);
self.cancel_timeout();
unsafe {
self.performSelector_withObject_afterDelay(
sel!(transitionTimedOut:),
None,
TRANSITION_TIMEOUT,
);
}
}
#[unsafe(method(windowDidChangeFullScreen:))]
fn window_did_change_fullscreen(&self, _: &NSNotification) {
self.cancel_timeout();
self.transition_ended();
}
#[unsafe(method(transitionTimedOut:))]
fn transition_timed_out(&self, _: Option<&AnyObject>) {
self.transition_ended();
}
}
);
impl FullscreenTransition {
pub(crate) fn observe(
nswindow: &NSWindow,
on_transition_end: impl Fn() + 'static,
) -> Retained<Self> {
let mtm = MainThreadMarker::from(nswindow);
let observer = Self::alloc(mtm).set_ivars(FullscreenTransitionIvars {
in_progress: Cell::new(false),
pending: Cell::new(None),
on_transition_end: Box::new(on_transition_end),
});
let observer: Retained<Self> = unsafe { objc2::msg_send![super(observer), init] };
let center = NSNotificationCenter::defaultCenter();
let names = unsafe {
[
(
NSWindowWillEnterFullScreenNotification,
sel!(windowWillChangeFullScreen:),
),
(
NSWindowWillExitFullScreenNotification,
sel!(windowWillChangeFullScreen:),
),
(
NSWindowDidEnterFullScreenNotification,
sel!(windowDidChangeFullScreen:),
),
(
NSWindowDidExitFullScreenNotification,
sel!(windowDidChangeFullScreen:),
),
]
};
for (name, selector) in names {
unsafe {
center.addObserver_selector_name_object(&observer, selector, Some(name), Some(nswindow));
}
}
observer
}
fn cancel_timeout(&self) {
unsafe {
NSObject::cancelPreviousPerformRequestsWithTarget_selector_object(
self,
sel!(transitionTimedOut:),
None,
);
}
}
fn transition_ended(&self) {
self.ivars().in_progress.set(false);
if self.ivars().pending.get().is_some() {
(self.ivars().on_transition_end)();
}
}
pub(crate) fn in_progress(&self) -> bool {
self.ivars().in_progress.get()
}
pub(crate) fn hold(&self, target: Option<FullscreenTarget>) {
self.ivars().pending.set(Some(target));
}
pub(crate) fn take_pending(&self) -> Option<Option<FullscreenTarget>> {
self.ivars().pending.take()
}
}