use std::{
ops::Deref,
sync::{Mutex, Weak},
};
use core_graphics::base::CGFloat;
use dispatch2::{DispatchQueue, DispatchQueueAttr};
use objc2::{rc::autoreleasepool, Message};
use objc2_app_kit::{NSScreen, NSView, NSWindow, NSWindowStyleMask};
use objc2_foundation::{MainThreadMarker, NSPoint, NSSize, NSString};
use crate::{
dpi::LogicalSize,
platform_impl::platform::{
ffi::{self, id, NO, YES},
window::SharedState,
},
};
pub fn is_main_thread() -> bool {
unsafe { msg_send!(class!(NSThread), isMainThread) }
}
struct MainThreadSafe<T>(T);
unsafe impl<T> Send for MainThreadSafe<T> {}
impl<T> Deref for MainThreadSafe<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
fn run_on_main<F: Send + FnOnce()>(f: F) {
if is_main_thread() {
f();
} else {
DispatchQueue::main().exec_sync(f);
}
}
unsafe fn set_style_mask(ns_window: &NSWindow, ns_view: &NSView, mask: NSWindowStyleMask) {
ns_window.setStyleMask(mask);
ns_window.makeFirstResponder(Some(ns_view));
}
pub unsafe fn set_style_mask_async(
ns_window: &NSWindow,
ns_view: &NSView,
mask: NSWindowStyleMask,
) {
let ns_window = MainThreadSafe(ns_window.retain());
let ns_view = MainThreadSafe(ns_view.retain());
DispatchQueue::main().exec_async(move || {
set_style_mask(&ns_window, &ns_view, mask);
});
}
pub unsafe fn set_style_mask_sync(ns_window: &NSWindow, ns_view: &NSView, mask: NSWindowStyleMask) {
if is_main_thread() {
set_style_mask(ns_window, ns_view, mask);
} else {
let ns_window = MainThreadSafe(ns_window.retain());
let ns_view = MainThreadSafe(ns_view.retain());
DispatchQueue::main().exec_sync(move || {
set_style_mask(&ns_window, &ns_view, mask);
})
}
}
pub unsafe fn set_content_size_async(ns_window: &NSWindow, size: LogicalSize<f64>) {
let ns_window = MainThreadSafe(ns_window.retain());
DispatchQueue::main().exec_async(move || {
ns_window.setContentSize(NSSize::new(size.width as CGFloat, size.height as CGFloat));
});
}
pub unsafe fn set_frame_top_left_point_async(ns_window: &NSWindow, point: NSPoint) {
let ns_window = MainThreadSafe(ns_window.retain());
DispatchQueue::main().exec_async(move || {
ns_window.setFrameTopLeftPoint(point);
});
}
pub unsafe fn set_level_async(ns_window: &NSWindow, level: ffi::NSWindowLevel) {
let ns_window = MainThreadSafe(ns_window.retain());
DispatchQueue::main().exec_async(move || {
ns_window.setLevel(level as _);
});
}
pub unsafe fn toggle_full_screen_async(
ns_window: &NSWindow,
ns_view: &NSView,
not_fullscreen: bool,
shared_state: Weak<Mutex<SharedState>>,
) {
let ns_window = MainThreadSafe(ns_window.retain());
let ns_view = MainThreadSafe(ns_view.retain());
let shared_state = MainThreadSafe(shared_state);
DispatchQueue::main().exec_async(move || {
if not_fullscreen {
let curr_mask = ns_window.styleMask();
let required = NSWindowStyleMask::Titled | NSWindowStyleMask::Resizable;
if !curr_mask.contains(required) {
set_style_mask(&ns_window, &ns_view, required);
if let Some(shared_state) = shared_state.upgrade() {
trace!("Locked shared state in `toggle_full_screen_callback`");
let mut shared_state_lock = shared_state.lock().unwrap();
shared_state_lock.saved_style = Some(curr_mask);
trace!("Unlocked shared state in `toggle_full_screen_callback`");
}
}
}
ns_window.setLevel(0);
ns_window.toggleFullScreen(None);
});
}
pub unsafe fn restore_display_mode_async(ns_screen: u32) {
DispatchQueue::main().exec_async(move || {
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(ffi::CGDisplayRelease(ns_screen), ffi::kCGErrorSuccess);
});
}
pub unsafe fn set_maximized_async(
ns_window: &NSWindow,
is_zoomed: bool,
maximized: bool,
shared_state: Weak<Mutex<SharedState>>,
) {
let ns_window = MainThreadSafe(ns_window.retain());
let shared_state = MainThreadSafe(shared_state);
DispatchQueue::main().exec_async(move || {
if let Some(shared_state) = shared_state.upgrade() {
trace!("Locked shared state in `set_maximized`");
let mut shared_state_lock = shared_state.lock().unwrap();
if !is_zoomed {
shared_state_lock.standard_frame = Some(NSWindow::frame(&ns_window));
}
shared_state_lock.maximized = maximized;
let curr_mask = ns_window.styleMask();
if shared_state_lock.fullscreen.is_some() {
return;
} else if curr_mask.contains(NSWindowStyleMask::Resizable)
&& curr_mask.contains(NSWindowStyleMask::Titled)
{
ns_window.zoom(None);
} else {
let new_rect = if maximized {
let mtm = MainThreadMarker::new_unchecked();
let screen = NSScreen::mainScreen(mtm).unwrap();
NSScreen::visibleFrame(&screen)
} else {
shared_state_lock.saved_standard_frame()
};
let _: () = msg_send![&*ns_window, setFrame:new_rect, display:NO, animate: YES];
}
trace!("Unlocked shared state in `set_maximized`");
}
});
}
pub unsafe fn order_out_sync(ns_window: &NSWindow) {
let ns_window = MainThreadSafe(ns_window.retain());
run_on_main(move || {
ns_window.orderOut(None);
});
}
pub unsafe fn make_key_and_order_front_sync(ns_window: &NSWindow) {
let ns_window = MainThreadSafe(ns_window.retain());
run_on_main(move || {
ns_window.makeKeyAndOrderFront(None);
});
}
pub unsafe fn set_title_async(ns_window: &NSWindow, title: String) {
let ns_window = MainThreadSafe(ns_window.retain());
DispatchQueue::main().exec_async(move || {
let title = NSString::from_str(&title);
ns_window.setTitle(&title);
});
}
pub unsafe fn set_focus(ns_window: &NSWindow) {
let ns_window = MainThreadSafe(ns_window.retain());
run_on_main(move || {
ns_window.makeKeyAndOrderFront(None);
let app: id = msg_send![class!(NSApplication), sharedApplication];
let () = msg_send![app, activateIgnoringOtherApps: YES];
});
}
pub unsafe fn close_async(ns_window: &NSWindow) {
let ns_window = MainThreadSafe(ns_window.retain());
run_on_main(move || {
autoreleasepool(move |_| {
ns_window.close();
});
});
}
pub unsafe fn set_ignore_mouse_events(ns_window: &NSWindow, ignore: bool) {
let ns_window = MainThreadSafe(ns_window.retain());
DispatchQueue::main().exec_async(move || {
ns_window.setIgnoresMouseEvents(ignore);
});
}