use std::{
ops::Deref,
sync::{Mutex, Weak},
};
use cocoa::{
appkit::{CGFloat, NSScreen, NSWindow, NSWindowStyleMask},
base::{id, nil},
foundation::{NSPoint, NSSize, NSString},
};
use dispatch::Queue;
use objc::{
rc::autoreleasepool,
runtime::{BOOL, NO, YES},
};
use crate::{
dpi::LogicalSize,
event::{Event, WindowEvent},
platform_impl::platform::{
app_state::AppState,
event::EventWrapper,
ffi,
util::IdRef,
window::{get_window_id, SharedState},
},
window::WindowId,
};
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
}
}
unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
ns_window.setStyleMask_(mask);
ns_window.makeFirstResponder_(ns_view);
}
pub unsafe fn set_style_mask_async(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
let ns_window = MainThreadSafe(ns_window);
let ns_view = MainThreadSafe(ns_view);
Queue::main().exec_async(move || {
set_style_mask(*ns_window, *ns_view, mask);
});
}
pub unsafe fn set_style_mask_sync(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
let is_main_thread: BOOL = msg_send!(class!(NSThread), isMainThread);
if is_main_thread != NO {
set_style_mask(ns_window, ns_view, mask);
} else {
let ns_window = MainThreadSafe(ns_window);
let ns_view = MainThreadSafe(ns_view);
Queue::main().exec_sync(move || {
set_style_mask(*ns_window, *ns_view, mask);
})
}
}
pub unsafe fn set_content_size_async(ns_window: id, size: LogicalSize<f64>) {
let ns_window = MainThreadSafe(ns_window);
Queue::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: id, point: NSPoint) {
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.setFrameTopLeftPoint_(point);
});
}
pub unsafe fn set_level_async(ns_window: id, level: ffi::NSWindowLevel) {
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.setLevel_(level as _);
});
}
pub unsafe fn toggle_full_screen_async(
ns_window: id,
ns_view: id,
not_fullscreen: bool,
shared_state: Weak<Mutex<SharedState>>,
) {
let ns_window = MainThreadSafe(ns_window);
let ns_view = MainThreadSafe(ns_view);
let shared_state = MainThreadSafe(shared_state);
Queue::main().exec_async(move || {
if not_fullscreen {
let curr_mask = ns_window.styleMask();
let required =
NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask;
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_(nil);
});
}
pub unsafe fn restore_display_mode_async(ns_screen: u32) {
Queue::main().exec_async(move || {
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(ffi::CGDisplayRelease(ns_screen), ffi::kCGErrorSuccess);
});
}
pub unsafe fn set_maximized_async(
ns_window: id,
is_zoomed: bool,
maximized: bool,
shared_state: Weak<Mutex<SharedState>>,
) {
let ns_window = MainThreadSafe(ns_window);
let shared_state = MainThreadSafe(shared_state);
Queue::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::NSResizableWindowMask)
&& curr_mask.contains(NSWindowStyleMask::NSTitledWindowMask)
{
ns_window.zoom_(nil);
} else {
let new_rect = if maximized {
let screen = NSScreen::mainScreen(nil);
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_async(ns_window: id) {
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.orderOut_(nil);
});
}
pub unsafe fn make_key_and_order_front_async(ns_window: id) {
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.makeKeyAndOrderFront_(nil);
});
}
pub unsafe fn set_title_async(ns_window: id, title: String) {
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
let title = IdRef::new(NSString::alloc(nil).init_str(&title));
ns_window.setTitle_(*title);
});
}
pub unsafe fn set_focus(ns_window: id) {
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.makeKeyAndOrderFront_(nil);
let app: id = msg_send![class!(NSApplication), sharedApplication];
let () = msg_send![app, activateIgnoringOtherApps: YES];
});
}
pub unsafe fn close_async(ns_window: IdRef) {
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
autoreleasepool(move || {
ns_window.close();
let event = Event::WindowEvent {
window_id: WindowId(get_window_id(*ns_window.0)),
event: WindowEvent::Destroyed,
};
AppState::queue_event(EventWrapper::StaticEvent(event));
});
});
}
pub unsafe fn set_ignore_mouse_events(ns_window: id, ignore: bool) {
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.setIgnoresMouseEvents_(if ignore { YES } else { NO });
});
}