use bevy::{ecs::component::Component, math::IRect};
use core::ptr::NonNull;
use objc2_core_foundation::{CFRetained, CFString, CFUUID};
use objc2_core_graphics::CGDirectDisplayID;
use stdext::function_name;
use tracing::{Level, instrument};
use super::skylight::{CGDisplayCreateUUIDFromDisplayID, CGDisplayGetDisplayIDFromUUID};
use crate::{
config::Config,
ecs::DockPosition,
errors::{Error, Result},
};
#[derive(Component, Debug)]
pub struct Display {
id: CGDirectDisplayID,
bounds: IRect,
menubar_height: i32,
menubar_height_override: Option<i32>,
notch_height: i32,
}
impl Display {
pub fn new(id: CGDirectDisplayID, bounds: IRect, menubar_height: i32) -> Self {
Self {
id,
bounds,
menubar_height,
menubar_height_override: None,
notch_height: 0,
}
}
pub fn uuid_from_id(id: CGDirectDisplayID) -> Result<CFRetained<CFString>> {
unsafe {
let uuid = NonNull::new(CGDisplayCreateUUIDFromDisplayID(id))
.map(|ptr| CFRetained::from_raw(ptr))
.ok_or(Error::InvalidInput(format!(
"{}: can not create uuid from {id}.",
function_name!()
)))?;
CFUUID::new_string(None, Some(&uuid)).ok_or(Error::InvalidInput(format!(
"{}: can not create string from {uuid:?}.",
function_name!()
)))
}
}
pub fn id_from_uuid(uuid: &CFRetained<CFString>) -> Result<u32> {
unsafe {
let id = CFUUID::from_string(None, Some(uuid)).ok_or(Error::NotFound(format!(
"{}: can not convert from {uuid}.",
function_name!()
)))?;
Ok(CGDisplayGetDisplayIDFromUUID(&id))
}
}
pub fn id(&self) -> CGDirectDisplayID {
self.id
}
pub fn locate_dock(&self, visible_frame: &IRect) -> DockPosition {
if self.bounds.min.x < visible_frame.min.x {
DockPosition::Left(visible_frame.min.x - self.bounds.min.x)
} else if visible_frame.width() < self.bounds.width() {
DockPosition::Right(self.bounds.max.x - visible_frame.max.x)
} else if visible_frame.height() < self.bounds.height() - self.menubar_height() {
DockPosition::Bottom(
self.bounds.height() - visible_frame.height() - self.menubar_height(),
)
} else {
DockPosition::Hidden
}
}
pub fn bounds(&self) -> IRect {
let mut bounds = self.bounds;
bounds.min.y += self.menubar_height();
bounds
}
pub fn width(&self) -> i32 {
self.bounds().width()
}
pub fn menubar_height(&self) -> i32 {
self.menubar_height_override
.unwrap_or(self.menubar_height)
.max(self.notch_height)
}
pub fn set_menubar_height_override(&mut self, height: Option<i32>) {
self.menubar_height_override = height;
}
pub fn set_notch_height(&mut self, height: i32) {
self.notch_height = height;
}
#[instrument(level = Level::TRACE, skip_all, ret)]
pub fn actual_display_bounds(&self, dock: Option<&DockPosition>, config: &Config) -> IRect {
let (pad_top, pad_right, pad_bottom, pad_left) = config.edge_padding();
let mut viewport = self.bounds();
viewport.min.x += pad_left;
viewport.min.y += pad_top;
viewport.max.x -= pad_right;
viewport.max.y -= pad_bottom;
match dock {
Some(DockPosition::Bottom(size)) => viewport.max.y -= size,
Some(DockPosition::Left(size)) => {
viewport.min.x += size;
}
Some(DockPosition::Right(size)) => viewport.max.x -= size,
_ => (),
}
viewport
}
}