diff --git a/output/src/lib.rs b/output/src/lib.rs index 0a61edb..df18d69 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -13,17 +13,23 @@ pub(crate) use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}}; pub(crate) use tengri_core::*; #[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; -mod output; pub use self::output::*; -mod output_render; pub use self::output_render::*; -mod output_content; pub use self::output_content::*; -mod output_thunk; pub use self::output_thunk::*; -mod space; pub use self::space::*; -mod layout_align; pub use self::layout_align::*; -mod layout_bsp; pub use self::layout_bsp::*; -mod layout_cond; pub use self::layout_cond::*; -mod layout_map; pub use self::layout_map::*; -mod layout_stack; pub use self::layout_stack::*; -mod layout_xy; pub use self::layout_xy::*; +mod output; pub use self::output::*; +mod output_render; pub use self::output_render::*; +mod output_content; pub use self::output_content::*; +mod output_thunk; pub use self::output_thunk::*; +mod space_area; pub use self::space_area::*; +mod space_coordinate; pub use self::space_coordinate::*; +mod space_direction; pub use self::space_direction::*; +mod space_measure; pub use self::space_measure::*; +mod space_size; pub use self::space_size::*; +mod layout_align; pub use self::layout_align::*; +mod layout_bsp; pub use self::layout_bsp::*; +mod layout_cond; pub use self::layout_cond::*; +mod layout_map; pub use self::layout_map::*; +mod layout_stack; pub use self::layout_stack::*; +mod layout_xy; pub use self::layout_xy::*; + +pub(crate) use self::Direction::*; #[cfg(test)] mod test; #[cfg(test)] pub(crate) use proptest_derive::Arbitrary; diff --git a/output/src/space_area.rs b/output/src/space_area.rs new file mode 100644 index 0000000..e958857 --- /dev/null +++ b/output/src/space_area.rs @@ -0,0 +1,97 @@ +use crate::*; + +pub trait Area: From<[N;4]> + Debug + Copy { + fn x (&self) -> N; + fn y (&self) -> N; + fn w (&self) -> N; + fn h (&self) -> N; + fn zero () -> [N;4] { + [N::zero(), N::zero(), N::zero(), N::zero()] + } + fn from_position (pos: impl Size) -> [N;4] { + let [x, y] = pos.wh(); + [x, y, 0.into(), 0.into()] + } + fn from_size (size: impl Size) -> [N;4] { + let [w, h] = size.wh(); + [0.into(), 0.into(), w, h] + } + fn expect_min (&self, w: N, h: N) -> Usually<&Self> { + if self.w() < w || self.h() < h { + Err(format!("min {w}x{h}").into()) + } else { + Ok(self) + } + } + fn xy (&self) -> [N;2] { + [self.x(), self.y()] + } + fn wh (&self) -> [N;2] { + [self.w(), self.h()] + } + fn xywh (&self) -> [N;4] { + [self.x(), self.y(), self.w(), self.h()] + } + fn clip_h (&self, h: N) -> [N;4] { + [self.x(), self.y(), self.w(), self.h().min(h)] + } + fn clip_w (&self, w: N) -> [N;4] { + [self.x(), self.y(), self.w().min(w), self.h()] + } + fn clip (&self, wh: impl Size) -> [N;4] { + [self.x(), self.y(), wh.w(), wh.h()] + } + fn set_w (&self, w: N) -> [N;4] { + [self.x(), self.y(), w, self.h()] + } + fn set_h (&self, h: N) -> [N;4] { + [self.x(), self.y(), self.w(), h] + } + fn x2 (&self) -> N { + self.x().plus(self.w()) + } + fn y2 (&self) -> N { + self.y().plus(self.h()) + } + fn lrtb (&self) -> [N;4] { + [self.x(), self.x2(), self.y(), self.y2()] + } + fn center (&self) -> [N;2] { + [self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into())] + } + fn center_x (&self, n: N) -> [N;4] { + let [x, y, w, h] = self.xywh(); + [(x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into()] + } + fn center_y (&self, n: N) -> [N;4] { + let [x, y, w, h] = self.xywh(); + [x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n] + } + fn center_xy (&self, [n, m]: [N;2]) -> [N;4] { + let [x, y, w, h] = self.xywh(); + [(x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m] + } + fn centered (&self) -> [N;2] { + [self.x().minus(self.w()/2.into()), self.y().minus(self.h()/2.into())] + } + fn iter_x (&self) -> impl Iterator where N: std::iter::Step { + self.x()..(self.x()+self.w()) + } + fn iter_y (&self) -> impl Iterator where N: std::iter::Step { + self.y()..(self.y()+self.h()) + } +} + +impl Area for (N, N, N, N) { + fn x (&self) -> N { self.0 } + fn y (&self) -> N { self.1 } + fn w (&self) -> N { self.2 } + fn h (&self) -> N { self.3 } +} + +impl Area for [N;4] { + fn x (&self) -> N { self[0] } + fn y (&self) -> N { self[1] } + fn w (&self) -> N { self[2] } + fn h (&self) -> N { self[3] } +} diff --git a/output/src/space_coordinate.rs b/output/src/space_coordinate.rs new file mode 100644 index 0000000..d977318 --- /dev/null +++ b/output/src/space_coordinate.rs @@ -0,0 +1,30 @@ +use crate::*; + +/// A linear coordinate. +pub trait Coordinate: Send + Sync + Copy + + Add + + Sub + + Mul + + Div + + Ord + PartialEq + Eq + + Debug + Display + Default + + From + Into + + Into + + Into +{ + fn zero () -> Self { 0.into() } + fn plus (self, other: Self) -> Self; + fn minus (self, other: Self) -> Self { + if self >= other { + self - other + } else { + 0.into() + } + } +} + +impl Coordinate for u16 { + fn plus (self, other: Self) -> Self { + self.saturating_add(other) + } +} diff --git a/output/src/space_direction.rs b/output/src/space_direction.rs new file mode 100644 index 0000000..90dc95e --- /dev/null +++ b/output/src/space_direction.rs @@ -0,0 +1,21 @@ +use crate::*; + +/// A cardinal direction. +#[derive(Copy, Clone, PartialEq, Debug)] +#[cfg_attr(test, derive(Arbitrary))] +pub enum Direction { + North, South, East, West, Above, Below +} + +impl Direction { + pub fn split_fixed (self, area: impl Area, a: N) -> ([N;4],[N;4]) { + let [x, y, w, h] = area.xywh(); + match self { + North => ([x, y.plus(h).minus(a), w, a], [x, y, w, h.minus(a)]), + South => ([x, y, w, a], [x, y.plus(a), w, h.minus(a)]), + East => ([x, y, a, h], [x.plus(a), y, w.minus(a), h]), + West => ([x.plus(w).minus(a), y, a, h], [x, y, w.minus(a), h]), + Above | Below => (area.xywh(), area.xywh()) + } + } +} diff --git a/output/src/space_measure.rs b/output/src/space_measure.rs new file mode 100644 index 0000000..8279c9d --- /dev/null +++ b/output/src/space_measure.rs @@ -0,0 +1,81 @@ +use crate::*; + +/// A widget that tracks its render width and height +#[derive(Default)] +pub struct Measure { + _engine: PhantomData, + pub x: Arc, + pub y: Arc, +} + +impl PartialEq for Measure { + fn eq (&self, other: &Self) -> bool { + self.x.load(Relaxed) == other.x.load(Relaxed) && + self.y.load(Relaxed) == other.y.load(Relaxed) + } +} + +// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small +impl Content for Measure { + fn render (&self, to: &mut E) { + self.x.store(to.area().w().into(), Relaxed); + self.y.store(to.area().h().into(), Relaxed); + } +} + +impl Clone for Measure { + fn clone (&self) -> Self { + Self { + _engine: Default::default(), + x: self.x.clone(), + y: self.y.clone(), + } + } +} + +impl std::fmt::Debug for Measure { + fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("Measure") + .field("width", &self.x) + .field("height", &self.y) + .finish() + } +} + +impl Measure { + pub fn new () -> Self { + Self { + _engine: PhantomData::default(), + x: Arc::new(0.into()), + y: Arc::new(0.into()), + } + } + pub fn set_w (&self, w: impl Into) -> &Self { + self.x.store(w.into(), Relaxed); + self + } + pub fn set_h (&self, h: impl Into) -> &Self { + self.y.store(h.into(), Relaxed); + self + } + pub fn set_wh (&self, w: impl Into, h: impl Into) -> &Self { + self.set_w(w); + self.set_h(h); + self + } + pub fn w (&self) -> usize { + self.x.load(Relaxed) + } + pub fn h (&self) -> usize { + self.y.load(Relaxed) + } + pub fn wh (&self) -> [usize;2] { + [self.w(), self.h()] + } + pub fn format (&self) -> Arc { + format!("{}x{}", self.w(), self.h()).into() + } + pub fn of > (&self, item: T) -> Bsp, T> { + Bsp::b(Fill::xy(self), item) + } +} diff --git a/output/src/space_size.rs b/output/src/space_size.rs new file mode 100644 index 0000000..ff53c25 --- /dev/null +++ b/output/src/space_size.rs @@ -0,0 +1,55 @@ +use crate::*; + +pub trait Size: From<[N;2]> + Debug + Copy { + fn x (&self) -> N; + fn y (&self) -> N; + fn w (&self) -> N { self.x() } + fn h (&self) -> N { self.y() } + fn wh (&self) -> [N;2] { [self.x(), self.y()] } + fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] } + fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] } + fn expect_min (&self, w: N, h: N) -> Usually<&Self> { + if self.w() < w || self.h() < h { + Err(format!("min {w}x{h}").into()) + } else { + Ok(self) + } + } + fn zero () -> [N;2] { + [N::zero(), N::zero()] + } + fn to_area_pos (&self) -> [N;4] { + let [x, y] = self.wh(); + [x, y, 0.into(), 0.into()] + } + fn to_area_size (&self) -> [N;4] { + let [w, h] = self.wh(); + [0.into(), 0.into(), w, h] + } +} + +impl Size for (N, N) { + fn x (&self) -> N { self.0 } + fn y (&self) -> N { self.1 } +} + +impl Size for [N;2] { + fn x (&self) -> N { self[0] } + fn y (&self) -> N { self[1] } +} + +pub trait HasSize { + fn size (&self) -> &Measure; + fn width (&self) -> usize { + self.size().w() + } + fn height (&self) -> usize { + self.size().h() + } +} + +impl>> HasSize for T { + fn size (&self) -> &Measure { + self.get() + } +}