use teksilo_canvas::EdgeInsets;
#[derive(Copy, Clone, PartialEq, Debug, Default)]
pub struct SafeAreaSides {
pub top: f32,
pub bottom: f32,
pub left: f32,
pub right: f32,
}
impl SafeAreaSides {
pub const ZERO: Self = Self {
top: 0.0,
bottom: 0.0,
left: 0.0,
right: 0.0,
};
pub fn is_zero(&self) -> bool {
*self == Self::ZERO
}
pub fn to_insets(self, direction: teksilo_core::environment::LayoutDirection) -> EdgeInsets {
let rtl = matches!(
direction,
teksilo_core::environment::LayoutDirection::RightToLeft
);
let (leading, trailing) = if rtl {
(self.right, self.left)
} else {
(self.left, self.right)
};
EdgeInsets {
top: self.top,
bottom: self.bottom,
leading,
trailing,
}
}
}
pub fn window_safe_area(window: &winit::window::Window) -> SafeAreaSides {
#[cfg(target_os = "macos")]
{
macos::safe_area(window)
}
#[cfg(not(target_os = "macos"))]
{
let _ = window;
SafeAreaSides::ZERO
}
}
#[cfg(target_os = "macos")]
mod macos {
use super::SafeAreaSides;
use objc2::rc::Retained;
use objc2_app_kit::NSView;
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
pub(super) fn safe_area(window: &winit::window::Window) -> SafeAreaSides {
let Ok(handle) = window.window_handle() else {
return SafeAreaSides::ZERO;
};
let RawWindowHandle::AppKit(raw) = handle.as_raw() else {
return SafeAreaSides::ZERO;
};
let Some(view): Option<Retained<NSView>> =
(unsafe { Retained::retain(raw.ns_view.as_ptr().cast()) })
else {
return SafeAreaSides::ZERO;
};
let insets = view.safeAreaInsets();
SafeAreaSides {
top: insets.top as f32,
bottom: insets.bottom as f32,
left: insets.left as f32,
right: insets.right as f32,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use teksilo_core::environment::LayoutDirection;
#[test]
fn zero_is_zero_on_every_side() {
assert!(SafeAreaSides::ZERO.is_zero());
assert!(SafeAreaSides::default().is_zero());
assert!(
!SafeAreaSides {
top: 1.0,
..SafeAreaSides::ZERO
}
.is_zero()
);
}
#[test]
fn a_left_inset_leads_in_ltr_and_trails_in_rtl() {
let sides = SafeAreaSides {
top: 32.0,
bottom: 8.0,
left: 5.0,
right: 3.0,
};
let ltr = sides.to_insets(LayoutDirection::LeftToRight);
assert_eq!(ltr.leading, 5.0);
assert_eq!(ltr.trailing, 3.0);
let rtl = sides.to_insets(LayoutDirection::RightToLeft);
assert_eq!(rtl.leading, 3.0);
assert_eq!(rtl.trailing, 5.0);
assert_eq!((ltr.top, ltr.bottom), (32.0, 8.0));
assert_eq!((rtl.top, rtl.bottom), (32.0, 8.0));
}
}