use std::rc::Rc;
use std::sync::Arc;
use teksilo_core::{
HitRegions, PlatformError, PlatformTitleBarHost, ResizeBorders, ResizeEdge,
TitleBarHostCallbacks,
};
use winit::window::{ResizeDirection, Window};
#[cfg_attr(target_os = "macos", allow(dead_code))]
pub(crate) fn edge_to_direction(edge: ResizeEdge) -> ResizeDirection {
match edge {
ResizeEdge::Top => ResizeDirection::North,
ResizeEdge::TopRight => ResizeDirection::NorthEast,
ResizeEdge::Right => ResizeDirection::East,
ResizeEdge::BottomRight => ResizeDirection::SouthEast,
ResizeEdge::Bottom => ResizeDirection::South,
ResizeEdge::BottomLeft => ResizeDirection::SouthWest,
ResizeEdge::Left => ResizeDirection::West,
ResizeEdge::TopLeft => ResizeDirection::NorthWest,
}
}
pub fn is_resize_band_update(regions: &HitRegions) -> bool {
let b = regions.resize_borders;
let has_band = b.top > 0.0 || b.right > 0.0 || b.bottom > 0.0 || b.left > 0.0;
has_band
&& regions.minimize.is_none()
&& regions.maximize.is_none()
&& regions.close.is_none()
&& regions.drag.is_empty()
&& regions.no_drag.is_empty()
}
pub fn merge_hit_regions(stored: &mut HitRegions, incoming: HitRegions) {
if is_resize_band_update(&incoming) {
stored.resize_borders = incoming.resize_borders;
} else {
*stored = incoming;
}
}
pub fn resize_band(os_metric: f32, published: ResizeBorders, coarse: bool) -> ResizeBorders {
if !coarse {
return ResizeBorders::uniform(os_metric);
}
ResizeBorders {
top: published.top.max(os_metric),
right: published.right.max(os_metric),
bottom: published.bottom.max(os_metric),
left: published.left.max(os_metric),
}
}
#[cfg(target_os = "macos")]
mod macos;
#[cfg(all(unix, not(target_os = "macos")))]
mod wayland;
#[cfg(target_os = "windows")]
mod windows;
#[cfg(all(unix, not(target_os = "macos")))]
mod x11;
#[cfg(target_os = "macos")]
pub use macos::MacOsHost;
#[cfg(all(unix, not(target_os = "macos")))]
pub use wayland::WaylandHost;
#[cfg(target_os = "windows")]
pub use windows::WindowsHost;
#[cfg(all(unix, not(target_os = "macos")))]
pub use x11::X11Host;
pub fn create_title_bar_host(
window: Arc<Window>,
callbacks: TitleBarHostCallbacks,
) -> Result<Rc<dyn PlatformTitleBarHost>, PlatformError> {
#[cfg(target_os = "windows")]
{
WindowsHost::new(window, callbacks).map(|h| Rc::new(h) as Rc<dyn PlatformTitleBarHost>)
}
#[cfg(target_os = "macos")]
{
MacOsHost::new(window, callbacks).map(|h| Rc::new(h) as Rc<dyn PlatformTitleBarHost>)
}
#[cfg(all(unix, not(target_os = "macos")))]
{
use winit::raw_window_handle::HasDisplayHandle;
use crate::window_system::{WindowSystem, window_system_for_display_handle};
let display = window
.display_handle()
.map_err(|e| PlatformError::Os(e.to_string()))?;
match window_system_for_display_handle(&display.as_raw()) {
WindowSystem::Wayland => WaylandHost::new(window, callbacks)
.map(|h| Rc::new(h) as Rc<dyn PlatformTitleBarHost>),
WindowSystem::X11 => {
X11Host::new(window, callbacks).map(|h| Rc::new(h) as Rc<dyn PlatformTitleBarHost>)
}
WindowSystem::Unknown => {
eprintln!(
"teksilo-platform: window reports neither an X11 nor a Wayland \
display handle; custom TitleBar disabled"
);
Err(PlatformError::Unsupported)
}
}
}
#[cfg(not(any(target_os = "windows", target_os = "macos", unix)))]
{
let _ = (window, callbacks);
Err(PlatformError::Unsupported)
}
}
#[cfg(test)]
mod resize_band_tests {
use super::*;
use teksilo_canvas::Rect;
fn band(v: f32) -> ResizeBorders {
ResizeBorders::uniform(v)
}
#[test]
fn a_mouse_gets_the_os_metric_untouched() {
for published in [0.0_f32, 6.0, 24.0, 44.0, 200.0] {
let out = resize_band(8.0, band(published), false);
assert_eq!(out.top, 8.0);
assert_eq!(out.right, 8.0);
assert_eq!(out.bottom, 8.0);
assert_eq!(out.left, 8.0);
}
}
#[test]
fn a_coarse_pointer_takes_the_published_band() {
let out = resize_band(8.0, band(24.0), true);
assert_eq!(out.top, 24.0);
assert_eq!(out.left, 24.0);
}
#[test]
fn the_published_band_is_a_floor_never_a_ceiling() {
let out = resize_band(8.0, band(2.0), true);
assert_eq!(out.top, 8.0);
assert_eq!(out.bottom, 8.0);
}
#[test]
fn an_unpublished_band_falls_back_to_the_os_metric() {
let out = resize_band(8.0, ResizeBorders::default(), true);
assert_eq!(out.top, 8.0);
assert_eq!(out.right, 8.0);
}
#[test]
fn each_edge_is_resolved_on_its_own() {
let published = ResizeBorders {
top: 44.0,
right: 4.0,
bottom: 24.0,
left: 0.0,
};
let out = resize_band(8.0, published, true);
assert_eq!(out.top, 44.0);
assert_eq!(out.right, 8.0, "below the metric → the metric");
assert_eq!(out.bottom, 24.0);
assert_eq!(out.left, 8.0, "unpublished → the metric");
}
#[test]
fn a_band_only_payload_is_a_band_update() {
let regions = HitRegions {
resize_borders: band(24.0),
..HitRegions::default()
};
assert!(is_resize_band_update(®ions));
}
#[test]
fn a_chrome_snapshot_is_not_a_band_update() {
let regions = HitRegions {
drag: vec![Rect::new(0.0, 0.0, 400.0, 32.0)],
..HitRegions::default()
};
assert!(!is_resize_band_update(®ions));
}
#[test]
fn an_empty_clearing_snapshot_is_not_a_band_update() {
assert!(!is_resize_band_update(&HitRegions::default()));
}
#[test]
fn merging_a_band_update_preserves_the_chrome_snapshot() {
let mut stored = HitRegions {
drag: vec![Rect::new(0.0, 0.0, 400.0, 32.0)],
close: Some(Rect::new(370.0, 0.0, 30.0, 32.0)),
..HitRegions::default()
};
merge_hit_regions(
&mut stored,
HitRegions {
resize_borders: band(24.0),
..HitRegions::default()
},
);
assert_eq!(stored.drag.len(), 1, "the drag rect survived");
assert!(stored.close.is_some(), "the close button survived");
assert_eq!(stored.resize_borders.top, 24.0);
}
#[test]
fn merging_a_chrome_snapshot_replaces_everything() {
let mut stored = HitRegions {
resize_borders: band(24.0),
close: Some(Rect::new(370.0, 0.0, 30.0, 32.0)),
..HitRegions::default()
};
merge_hit_regions(&mut stored, HitRegions::default());
assert_eq!(stored.resize_borders.top, 0.0);
assert!(stored.close.is_none());
}
}