use std::sync::Arc;
use objc2::MainThreadMarker;
use objc2::rc::Retained;
use objc2_app_kit::{NSView, NSWindow, NSWindowButton};
use teksilo_canvas::{Point, Size};
use teksilo_core::{
HitRegions, PlatformError, PlatformTitleBarHost, ResizeEdge, TitleBarHostCallbacks,
};
use winit::raw_window_handle::{HasWindowHandle, RawWindowHandle};
use winit::window::Window;
pub struct MacOsHost {
window: Arc<Window>,
leading_inset: Size,
}
impl MacOsHost {
pub fn new(
window: Arc<Window>,
_callbacks: TitleBarHostCallbacks,
) -> Result<Self, PlatformError> {
let _mtm = MainThreadMarker::new()
.ok_or_else(|| PlatformError::Os("MacOsHost::new must run on main thread".into()))?;
let ns_window = unsafe { ns_window_from_winit(&window)? };
let leading_inset = measure_traffic_light_inset(&ns_window);
Ok(Self {
window,
leading_inset,
})
}
}
unsafe fn ns_window_from_winit(window: &Arc<Window>) -> Result<Retained<NSWindow>, PlatformError> {
let handle = window
.window_handle()
.map_err(|e| PlatformError::Os(format!("window_handle: {e}")))?;
let RawWindowHandle::AppKit(raw) = handle.as_raw() else {
return Err(PlatformError::Os("expected AppKit window handle".into()));
};
let ns_view: Retained<NSView> = unsafe { Retained::retain(raw.ns_view.as_ptr().cast()) }
.ok_or_else(|| PlatformError::Os("failed to retain NSView".into()))?;
ns_view
.window()
.ok_or_else(|| PlatformError::Os("NSView has no attached NSWindow".into()))
}
fn measure_traffic_light_inset(ns_window: &NSWindow) -> Size {
let close = ns_window.standardWindowButton(NSWindowButton::CloseButton);
let zoom = ns_window.standardWindowButton(NSWindowButton::ZoomButton);
if let (Some(close), Some(zoom)) = (close, zoom) {
let cf = close.frame();
let zf = zoom.frame();
let leading_edge = cf.origin.x as f32;
let cluster_width = (zf.origin.x + zf.size.width - cf.origin.x) as f32;
let trailing_padding = 12.0_f32;
let width = leading_edge + cluster_width + trailing_padding;
let height = cf.size.height as f32;
Size::new(width, height)
} else {
Size::new(78.0, 22.0)
}
}
impl PlatformTitleBarHost for MacOsHost {
fn reserved_leading_inset(&self) -> Size {
self.leading_inset
}
fn reserved_trailing_inset(&self) -> Size {
Size::ZERO
}
fn renders_custom_controls(&self) -> bool {
false
}
fn needs_custom_resize_handles(&self) -> bool {
false
}
fn begin_drag(&self) -> Result<(), PlatformError> {
self.window
.drag_window()
.map_err(|e| PlatformError::Os(e.to_string()))
}
fn begin_resize(&self, _edge: ResizeEdge) -> Result<(), PlatformError> {
Err(PlatformError::Unsupported)
}
fn show_window_menu(&self, _at: Point) -> Result<(), PlatformError> {
Ok(())
}
fn update_hit_regions(&self, _regions: &HitRegions) {
}
}