use ash::vk;
use concinnity_core::components::WindowMode;
use concinnity_core::input::keymap::KeyMap;
use concinnity_core::input::snapshot::InputSnapshot;
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::window::clipboard::Clipboard;
use concinnity_core::window::display_mode::DisplayMode;
use objc2::MainThreadOnly;
use objc2::rc::Retained;
use objc2_app_kit::{NSApplication, NSView};
use objc2_foundation::NSSize;
use objc2_quartz_core::CAMetalLayer;
use crate::appkit::{AppKitWindow, AppKitWindowParts, chrome, window_delegate};
pub(crate) struct AppKitVkWindow {
win: AppKitWindow,
layer: Retained<CAMetalLayer>,
}
unsafe impl Send for AppKitVkWindow {}
impl AppKitVkWindow {
pub(crate) fn new(
title: &str,
width: u32,
height: u32,
mode: &WindowMode,
_resizable: bool,
title_bar: bool,
) -> RenderResult<Self> {
let mtm = objc2::MainThreadMarker::new().ok_or_else(|| {
RenderError::Other("the Vulkan window must be created on the main thread".to_string())
})?;
let window = chrome::create_window(mtm, title, width, height, title_bar)
.map_err(RenderError::Other)?;
let content_rect = window.contentRectForFrameRect(window.frame());
let view = NSView::initWithFrame(NSView::alloc(mtm), content_rect);
let layer = CAMetalLayer::new();
view.setLayer(Some(&layer));
view.setWantsLayer(true);
window.setContentView(Some(&view));
let (delegate, signals) = window_delegate::attach_window_delegate(mtm, &window);
NSApplication::sharedApplication(mtm).activate();
window.makeKeyAndOrderFront(None);
let mut this = Self {
win: AppKitWindow::new(AppKitWindowParts {
window: Some(window),
view,
title_bar,
pump_events: true,
signals,
window_delegate: Some(delegate),
}),
layer,
};
this.sync_drawable_size();
this.set_window_mode(*mode);
Ok(this)
}
fn sync_drawable_size(&self) -> (i32, i32) {
let view = self.win.view();
let bounds = view.bounds().size;
let scale = match self.win.window().map(|w| w.backingScaleFactor()) {
Some(s) if s > 0.0 => s,
_ => self.layer.contentsScale(),
};
let (w, h) = (bounds.width * scale, bounds.height * scale);
self.layer.setContentsScale(scale);
self.layer
.setDrawableSize(NSSize::new(w.max(1.0), h.max(1.0)));
(w as i32, h as i32)
}
pub(crate) fn poll(&mut self) -> bool {
if let Some(mtm) = objc2::MainThreadMarker::new() {
self.win.pump_ns_events(mtm);
}
self.win.reconcile_display_mode();
self.win.closed()
}
pub(crate) fn take_input(&mut self) -> InputSnapshot {
self.win.take_input()
}
pub(crate) fn clipboard(&mut self) -> Option<&mut dyn Clipboard> {
self.win.clipboard()
}
pub(crate) fn request_cursor_capture(&mut self) {
self.win.capture_cursor();
}
pub(crate) fn set_ui_cursor_hidden(&mut self, hidden: bool) {
self.win.set_ui_cursor_hidden(hidden);
}
pub(crate) fn set_menu_mode(&mut self, on: bool) {
self.win.set_menu_mode(on);
}
pub(crate) fn set_camera_capture(&mut self, capture: bool) {
self.win.set_camera_capture(capture);
}
pub(crate) fn cursor_outside_window(&self) -> bool {
self.win.cursor_outside_window()
}
pub(crate) fn set_keymap(&mut self, keymap: &KeyMap) {
self.win.set_keymap(keymap);
}
pub(crate) fn set_window_mode(&mut self, mode: WindowMode) {
self.win.set_window_mode(mode);
}
pub(crate) fn set_window_size(&mut self, width: u32, height: u32) {
self.win.set_window_size(width, height);
}
pub(crate) fn display_modes(&self) -> Vec<DisplayMode> {
self.win.display_modes()
}
pub(crate) fn current_display_mode(&self) -> Option<DisplayMode> {
self.win.current_display_mode()
}
pub(crate) fn set_display_mode(&mut self, mode: DisplayMode) {
self.win.set_display_mode(mode);
}
pub(crate) fn framebuffer_size(&self) -> (i32, i32) {
self.sync_drawable_size()
}
pub(crate) fn logical_size(&self) -> (f32, f32) {
self.win.logical_size()
}
pub(crate) fn top_content_inset(&self) -> f32 {
self.win.top_content_inset()
}
pub(crate) fn create_surface(
&mut self,
entry: &ash::Entry,
instance: &ash::Instance,
) -> RenderResult<vk::SurfaceKHR> {
let info =
vk::MetalSurfaceCreateInfoEXT::default().layer(Retained::as_ptr(&self.layer).cast());
let loader = ash::ext::metal_surface::Instance::new(entry, instance);
unsafe { loader.create_metal_surface(&info, None) }
.map_err(|e| crate::vulkan::error::map_vk_result(e, "vkCreateMetalSurfaceEXT"))
}
pub(crate) fn required_instance_extensions(&self) -> Vec<String> {
[ash::khr::surface::NAME, ash::ext::metal_surface::NAME]
.into_iter()
.map(|n| n.to_str().unwrap_or_default().to_string())
.collect()
}
}