#![deny(unsafe_op_in_unsafe_fn)]
use objc2::rc::Retained;
use objc2::runtime::{NSObject, NSObjectProtocol, ProtocolObject};
use objc2::{DefinedClass, MainThreadOnly, define_class, msg_send};
use objc2_app_kit::{NSWindow, NSWindowDelegate, NSWindowStyleMask};
use objc2_foundation::NSNotification;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Debug, Default)]
pub(crate) struct WindowSignals {
fullscreen: AtomicBool,
closed: AtomicBool,
}
impl WindowSignals {
fn new(fullscreen: bool) -> Self {
Self {
fullscreen: AtomicBool::new(fullscreen),
closed: AtomicBool::new(false),
}
}
pub(crate) fn is_fullscreen(&self) -> bool {
self.fullscreen.load(Ordering::Relaxed)
}
pub(super) fn set_fullscreen(&self, fullscreen: bool) {
self.fullscreen.store(fullscreen, Ordering::Relaxed);
}
pub(crate) fn closed(&self) -> bool {
self.closed.load(Ordering::Relaxed)
}
fn mark_closed(&self) {
self.closed.store(true, Ordering::Relaxed);
}
}
pub(crate) struct DelegateIvars {
signals: Arc<WindowSignals>,
}
define_class!(
#[unsafe(super(NSObject))]
#[thread_kind = MainThreadOnly]
#[name = "ConcinnityWindowDelegate"]
#[ivars = DelegateIvars]
pub(crate) struct WindowDelegate;
unsafe impl NSObjectProtocol for WindowDelegate {}
unsafe impl NSWindowDelegate for WindowDelegate {
#[unsafe(method(windowWillEnterFullScreen:))]
fn window_will_enter_full_screen(&self, _notification: &NSNotification) {
self.ivars().signals.set_fullscreen(true);
}
#[unsafe(method(windowWillExitFullScreen:))]
fn window_will_exit_full_screen(&self, _notification: &NSNotification) {
self.ivars().signals.set_fullscreen(false);
}
#[unsafe(method(windowDidEnterFullScreen:))]
fn window_did_enter_full_screen(&self, _notification: &NSNotification) {
self.ivars().signals.set_fullscreen(true);
}
#[unsafe(method(windowDidExitFullScreen:))]
fn window_did_exit_full_screen(&self, _notification: &NSNotification) {
self.ivars().signals.set_fullscreen(false);
}
#[unsafe(method(windowWillClose:))]
fn window_will_close(&self, _notification: &NSNotification) {
self.ivars().signals.mark_closed();
}
}
);
impl WindowDelegate {
fn new(mtm: objc2::MainThreadMarker, signals: Arc<WindowSignals>) -> Retained<Self> {
let this = Self::alloc(mtm).set_ivars(DelegateIvars { signals });
unsafe { msg_send![super(this), init] }
}
}
pub(crate) fn attach_window_delegate(
mtm: objc2::MainThreadMarker,
window: &NSWindow,
) -> (Retained<WindowDelegate>, Arc<WindowSignals>) {
let signals = Arc::new(WindowSignals::new(
window.styleMask().contains(NSWindowStyleMask::FullScreen),
));
let delegate = WindowDelegate::new(mtm, Arc::clone(&signals));
window.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
(delegate, signals)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_new_window_is_open() {
assert!(!WindowSignals::new(false).closed());
assert!(!WindowSignals::default().closed());
}
#[test]
fn close_latches() {
let signals = WindowSignals::new(false);
signals.mark_closed();
signals.mark_closed();
assert!(signals.closed());
}
#[test]
fn fullscreen_transitions_never_read_as_a_close() {
let signals = WindowSignals::new(true);
assert!(signals.is_fullscreen());
signals.set_fullscreen(false);
signals.set_fullscreen(true);
assert!(signals.is_fullscreen());
assert!(!signals.closed());
}
#[test]
fn a_close_leaves_the_fullscreen_flag_alone() {
let signals = WindowSignals::new(true);
signals.mark_closed();
assert!(signals.is_fullscreen());
}
}