use core::ptr;
use crate::{
LogicalSize, Margins, MonitorId, PanelAnchor, PanelDesc, PanelKeyboard, PanelLayer, Reserve,
WindowId,
};
use super::ffi::{self, Id, NSPoint, NSRect, NSSize, Sel};
use super::geometry;
use super::monitors::Screen;
use super::shell::AppKitShell;
#[derive(Clone, Copy, Debug, PartialEq)]
pub(super) struct Panel {
pub monitor: Option<MonitorId>,
pub layer: PanelLayer,
pub anchor: PanelAnchor,
pub reserve: Reserve,
pub margins: Margins,
pub keyboard: PanelKeyboard,
pub size: LogicalSize,
}
impl Panel {
pub(super) fn new(desc: &PanelDesc<'_>, size: LogicalSize) -> Self {
Self {
monitor: desc.monitor,
layer: desc.layer,
anchor: desc.anchor,
reserve: desc.reserve,
margins: desc.margins,
keyboard: desc.keyboard,
size,
}
}
pub(super) fn frame_on(&self, screen: &Screen) -> NSRect {
let area = match self.reserve {
Reserve::IgnoreOthers => screen.frame,
Reserve::None | Reserve::OwnExtent => screen.visible,
};
geometry::panel_frame(
area,
self.anchor,
NSSize::new(self.size.width, self.size.height),
self.margins,
)
}
}
fn contains(rect: NSRect, point: NSPoint) -> bool {
point.x >= rect.origin.x
&& point.x < rect.origin.x + rect.size.width
&& point.y >= rect.origin.y
&& point.y < rect.origin.y + rect.size.height
}
impl AppKitShell {
pub(super) fn panel_screen(&self, panel: &Panel) -> Option<Screen> {
if let Some(id) = panel.monitor {
return self.screens.iter().find(|screen| screen.id == id).copied();
}
let under = ffi::class(c"NSEvent").map(|class| {
unsafe {
let location: unsafe extern "C" fn(Id, Sel) -> NSPoint = ffi::msg_send();
location(class.cast(), ffi::sel(c"mouseLocation"))
}
});
under
.and_then(|point| {
self.screens
.iter()
.find(|screen| contains(screen.frame, point))
})
.or_else(|| self.screens.first())
.copied()
}
pub(super) unsafe fn configure_panel(window: Id, panel: &Panel) {
let (key, above) = geometry::panel_level(panel.layer);
let level = unsafe { ffi::CGWindowLevelForKey(key) } as ffi::NSInteger + above;
unsafe {
ffi::msg_set_isize(window, ffi::sel(c"setLevel:"), level);
ffi::msg_set_usize(
window,
ffi::sel(c"setCollectionBehavior:"),
geometry::panel_collection(panel.layer),
);
ffi::msg_set_bool(window, ffi::sel(c"setHidesOnDeactivate:"), false);
ffi::msg_set_bool(
window,
ffi::sel(c"setBecomesKeyOnlyIfNeeded:"),
panel.keyboard == PanelKeyboard::OnDemand,
);
}
}
pub(super) unsafe fn show_panel(window: Id, keyboard: PanelKeyboard) {
unsafe {
match keyboard {
PanelKeyboard::Exclusive => {
ffi::msg1_void(window, ffi::sel(c"makeKeyAndOrderFront:"), ptr::null_mut());
}
PanelKeyboard::None | PanelKeyboard::OnDemand => {
ffi::msg_void(window, ffi::sel(c"orderFrontRegardless"));
}
}
}
}
pub(super) fn place_panels(&mut self, removed: &[MonitorId]) {
let panels: Vec<(WindowId, Panel)> = self
.windows_iter()
.filter_map(|(handle, window)| Some((handle, window.panel?)))
.collect();
for (window, panel) in panels {
if panel
.monitor
.is_some_and(|monitor| removed.contains(&monitor))
{
self.close_requested(window);
continue;
}
self.place_panel(window, &panel);
}
}
fn place_panel(&self, handle: WindowId, panel: &Panel) {
let Ok(state) = self.window(handle) else {
return;
};
let window = state.window;
let screen = match panel.monitor {
Some(_) => self.panel_screen(panel),
None => self
.screen_of(window)
.and_then(|id| self.screens.iter().find(|screen| screen.id == id).copied()),
};
let Some(screen) = screen else { return };
let frame = panel.frame_on(&screen);
unsafe {
let set: unsafe extern "C" fn(Id, Sel, NSRect, ffi::ObjcBool) = ffi::msg_send();
set(window, ffi::sel(c"setFrame:display:"), frame, ffi::YES);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_point_is_inside_a_rect_by_its_left_and_bottom_edges() {
let rect = NSRect::new(-1440.0, 0.0, 1440.0, 900.0);
assert!(contains(rect, NSPoint { x: -1440.0, y: 0.0 }));
assert!(contains(rect, NSPoint { x: -0.5, y: 899.5 }));
assert!(
!contains(rect, NSPoint { x: 0.0, y: 10.0 }),
"the right edge is out"
);
assert!(
!contains(rect, NSPoint { x: -10.0, y: 900.0 }),
"the top edge is out"
);
}
}