use crate::{TitlebarStyle, WindowOptions};
use objc2::runtime::AnyObject;
use objc2::{define_class, msg_send, sel, MainThreadOnly};
use objc2_app_kit::{
NSWindow, NSWindowButton, NSWindowDelegate, NSWindowStyleMask, NSWindowTitleVisibility,
};
use objc2_foundation::{NSObjectProtocol, NSPoint, NSRect, NSSize};
use super::options::native_window_style;
define_class!(
#[unsafe(super = NSWindow)]
#[thread_kind = MainThreadOnly]
pub(super) struct DesktopWindow;
impl DesktopWindow {
#[unsafe(method(canBecomeKeyWindow))]
fn can_become_key_window(&self) -> bool {
true
}
#[unsafe(method(canBecomeMainWindow))]
fn can_become_main_window(&self) -> bool {
true
}
#[unsafe(method(performClose:))]
fn perform_close(&self, sender: Option<&AnyObject>) {
if self
.styleMask()
.contains(objc2_app_kit::NSWindowStyleMask::Titled)
{
unsafe {
let _: () = msg_send![super(self), performClose: sender];
}
return;
}
if let Some(delegate) = self.delegate() {
if delegate.respondsToSelector(sel!(windowShouldClose:))
&& !delegate.windowShouldClose(self)
{
return;
}
}
self.close();
}
}
);
pub(super) fn apply_window_options(window: &NSWindow, options: &WindowOptions) {
let style = native_window_style(options);
if style.transparent_titlebar {
window.setTitlebarAppearsTransparent(true);
window.setTitleVisibility(NSWindowTitleVisibility::Hidden);
}
if let (Some(width), Some(height)) = (options.min_width, options.min_height) {
window.setContentMinSize(NSSize::new(f64::from(width), f64::from(height)));
}
if let (Some(width), Some(height)) = (options.max_width, options.max_height) {
window.setContentMaxSize(NSSize::new(f64::from(width), f64::from(height)));
}
if options.always_on_top {
unsafe {
let _: () = objc2::msg_send![window, setLevel: 3_i64];
};
}
}
fn caption_center_y(bounds: NSRect, flipped: bool, height: u32) -> Option<f64> {
if !bounds.size.height.is_finite() || bounds.size.height <= 0.0 {
return None;
}
let half_band = f64::from(height).min(bounds.size.height) / 2.0;
Some(
bounds.origin.y
+ if flipped {
half_band
} else {
bounds.size.height - half_band
},
)
}
pub(super) fn align_overlay_controls(window: &NSWindow, options: &WindowOptions) {
let TitlebarStyle::Overlay { height } = options.titlebar else {
return;
};
if window.styleMask().contains(NSWindowStyleMask::FullScreen) {
return;
}
let Some(content) = window.contentView() else {
return;
};
let Some(center_y) = caption_center_y(content.bounds(), content.isFlipped(), height) else {
return;
};
for kind in [
NSWindowButton::CloseButton,
NSWindowButton::MiniaturizeButton,
NSWindowButton::ZoomButton,
] {
let Some(button) = window.standardWindowButton(kind) else {
continue;
};
let Some(parent) = (unsafe { button.superview() }) else {
continue;
};
let frame = button.frame();
let target = content.convertPoint_toView(NSPoint::new(0.0, center_y), Some(&parent));
let next_y = target.y - frame.size.height / 2.0;
if next_y.is_finite() && (frame.origin.y - next_y).abs() > 0.5 {
button.setFrameOrigin(NSPoint::new(frame.origin.x, next_y));
}
}
}
#[cfg(test)]
mod overlay_tests {
use super::*;
#[test]
fn header_center_uses_view_coordinates_and_clamps_to_visible_height() {
let bounds = NSRect::new(NSPoint::new(0.0, 5.0), NSSize::new(1140.0, 1124.0));
assert_eq!(caption_center_y(bounds, false, 64), Some(1097.0));
assert_eq!(caption_center_y(bounds, true, 64), Some(37.0));
assert_eq!(caption_center_y(bounds, false, 4000), Some(567.0));
assert_eq!(caption_center_y(NSRect::ZERO, false, 64), None);
}
}