use crate::*; use Alignment::*; use Direction::*; use unicode_width::{UnicodeWidthStr, UnicodeWidthChar}; use rand::{thread_rng, distributions::uniform::UniformSampler}; macro_rules! layout_op_xy ( // Variant for layout ops that take no coordinates (0: $T: ident) => { impl $T { #[inline] pub const fn inner (&self) -> &A { match self { Self::X(c) | Self::Y(c) | Self::XY(c) => c } } } impl> Draw for $T { fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) } } }; // Variant for layout ops that take one coordinate (1: $T: ident) => { impl $T { #[inline] pub const fn inner (&self) -> &A { match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c, } } } impl> Draw for $T { fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) } } impl $T { #[inline] pub fn dx (&self) -> U { match self { Self::X(x, _) | Self::XY(x, ..) => *x, _ => 0.into() } } #[inline] pub fn dy (&self) -> U { match self { Self::Y(y, _) | Self::XY(y, ..) => *y, _ => 0.into() } } } }; (1 opt: $T: ident) => { impl $T { #[inline] pub const fn inner (&self) -> &A { match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c, } } } impl> Draw for $T { fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) } } impl $T { #[inline] pub const fn dx (&self) -> Option { match self { Self::X(x, _) | Self::XY(x, ..) => Some(*x), _ => None } } #[inline] pub const fn dy (&self) -> Option { match self { Self::Y(y, _) | Self::XY(y, ..) => Some(*y), _ => None } } } }; ); // Implement layout op that increments X and/or Y by fixed amount. macro_rules! push_pull(($T:ident: $method: ident)=>{ layout_op_xy!(1: $T); impl> Layout for $T { fn layout_x (&self, area: XYWH) -> O::Unit { area.x().$method(self.dx()) } fn layout_y (&self, area: XYWH) -> O::Unit { area.y().$method(self.dy()) } } }); macro_rules! impl_map_direction (($name:ident, $axis:ident, $align:ident)=>{ impl<'a, O, A, B, I, F> Map< O, A, Push>>>, I, F, fn(A, usize)->B > where O: Out, B: Draw, I: Iterator + Send + Sync + 'a, F: Fn() -> I + Send + Sync + 'a { pub const fn $name ( size: O::Unit, get_iter: F, get_item: impl Fn(A, usize)->B + Send + Sync ) -> Map< O, A, Push>>, I, F, impl Fn(A, usize)->Push>> + Send + Sync > { Map { __: PhantomData, get_iter, get_item: move |item: A, index: usize|{ // FIXME: multiply let mut push: O::Unit = O::Unit::from(0u16); for _ in 0..index { push = push + size; } Push::$axis(push, Align::$align(Fixed::$axis(size, get_item(item, index)))) } } } } }); impl> Command for Option { fn execute (&self, _: &mut S) -> Perhaps { Ok(None) } fn delegate (&self, _: &mut S, _: impl Fn(Self)->U) -> Perhaps where Self: Sized { Ok(None) } } impl Coord for u16 { fn plus (self, other: Self) -> Self { self.saturating_add(other) } } impl HasXY for XY { fn x (&self) -> N { self.0 } fn y (&self) -> N { self.1 } } impl HasXY for XYWH { fn x (&self) -> N { self.0 } fn y (&self) -> N { self.1 } } impl HasXY for O { // X coordinate of output area #[inline] fn x (&self) -> O::Unit { self.area().x() } // Y coordinate of output area #[inline] fn y (&self) -> O::Unit { self.area().y() } } impl HasWH for WH { fn w (&self) -> N { self.0 } fn h (&self) -> N { self.1 } } impl HasWH for XYWH { fn w (&self) -> N { self.2 } fn h (&self) -> N { self.3 } } impl HasWH for O { // Width of output area #[inline] fn w (&self) -> O::Unit { self.area().w() } // Height of output area #[inline] fn h (&self) -> O::Unit { self.area().h() } } impl WH { pub fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] } pub fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] } pub fn expect_min (&self, w: N, h: N) -> Usually<&Self> { if self.w() < w || self.h() < h { return Err(format!("min {w}x{h}").into()) } Ok(self) } } impl XYWH { pub fn zero () -> Self { Self(0.into(), 0.into(), 0.into(), 0.into()) } pub fn x2 (&self) -> N { self.x().plus(self.w()) } pub fn y2 (&self) -> N { self.y().plus(self.h()) } pub fn with_w (&self, w: N) -> XYWH { Self(self.x(), self.y(), w, self.h()) } pub fn with_h (&self, h: N) -> XYWH { Self(self.x(), self.y(), self.w(), h) } pub fn lrtb (&self) -> [N;4] { [self.x(), self.x2(), self.y(), self.y2()] } pub fn clipped_w (&self, w: N) -> XYWH { Self(self.x(), self.y(), self.w().min(w), self.h()) } pub fn clipped_h (&self, h: N) -> XYWH { Self(self.x(), self.y(), self.w(), self.h().min(h)) } pub fn clipped (&self, wh: WH) -> XYWH { Self(self.x(), self.y(), wh.w(), wh.h()) } /// Iterate over every covered X coordinate. pub fn iter_x (&self) -> impl Iterator where N: std::iter::Step { let Self(x, _, w, _) = *self; x..(x+w) } /// Iterate over every covered Y coordinate. pub fn iter_y (&self) -> impl Iterator where N: std::iter::Step { let Self(_, y, _, h) = *self; y..(y+h) } pub fn center (&self) -> XY { let Self(x, y, w, h) = self; XY(self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into())) } pub fn centered (&self) -> XY { let Self(x, y, w, h) = *self; XY(x.minus(w/2.into()), y.minus(h/2.into())) } pub fn centered_x (&self, n: N) -> XYWH { let Self(x, y, w, h) = *self; XYWH((x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into()) } pub fn centered_y (&self, n: N) -> XYWH { let Self(x, y, w, h) = *self; XYWH(x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n) } pub fn centered_xy (&self, [n, m]: [N;2]) -> XYWH { let Self(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) } } impl + Layout> Content for T {} impl<'a, O: Out> AsRef + 'a> for dyn Content + 'a { fn as_ref (&self) -> &(dyn Draw + 'a) { self } } impl<'a, O: Out> AsRef + 'a> for dyn Content + 'a { fn as_ref (&self) -> &(dyn Layout + 'a) { self } } impl Draw for () { fn draw (&self, _: &mut O) {} } impl Draw for fn(&mut O) { fn draw (&self, to: &mut O) { (*self)(to) } } impl Draw for Box> { fn draw (&self, to: &mut O) { (**self).draw(to) } } impl> Draw for &D { fn draw (&self, to: &mut O) { (*self).draw(to) } } impl> Draw for &mut D { fn draw (&self, to: &mut O) { (**self).draw(to) } } impl> Draw for Arc { fn draw (&self, to: &mut O) { (**self).draw(to) } } impl> Draw for RwLock { fn draw (&self, to: &mut O) { self.read().unwrap().draw(to) } } impl> Draw for Option { fn draw (&self, to: &mut O) { if let Some(draw) = self { draw.draw(to) } } } //impl> Draw for T { //fn draw (&self, to: &mut O) { //let area = to.area(); //*to.area_mut() = self.0; //self.content().draw(to); //*to.area_mut() = area; //} //} impl, F: Fn()->T> Lazy { pub const fn new (thunk: F) -> Self { Self(thunk, PhantomData) } } impl Thunk { pub const fn new (draw: F) -> Self { Self(PhantomData, draw) } } impl Layout for Thunk {} impl Draw for Thunk { fn draw (&self, to: &mut O) { (self.1)(to) } } impl Memo { pub fn new (value: T, view: U) -> Self { Self { value, view: Arc::new(view.into()) } } pub fn update (&mut self, newval: T, draw: impl Fn(&mut U, &T, &T)->R) -> Option { if newval != self.value { let result = draw(&mut*self.view.write().unwrap(), &newval, &self.value); self.value = newval; return Some(result); } None } } impl Direction { pub fn split_fixed (self, area: XYWH, a: N) -> (XYWH, XYWH) { let XYWH(x, y, w, h) = area; match self { North => (XYWH(x, y.plus(h).minus(a), w, a), XYWH(x, y, w, h.minus(a))), South => (XYWH(x, y, w, a), XYWH(x, y.plus(a), w, h.minus(a))), East => (XYWH(x, y, a, h), XYWH(x.plus(a), y, w.minus(a), h)), West => (XYWH(x.plus(w).minus(a), y, a, h), XYWH(x, y, w.minus(a), h)), Above | Below => (area, area) } } } impl>> Measured for T { fn measure (&self) -> &Measure { self.get() } } impl Clone for Measure { fn clone (&self) -> Self { Self { __: Default::default(), x: self.x.clone(), y: self.y.clone(), } } } impl Layout for Measure {} 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 Draw for Measure { fn draw (&self, to: &mut O) { self.x.store(to.area().w().into(), Relaxed); self.y.store(to.area().h().into(), Relaxed); } } impl 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 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 format (&self) -> Arc { format!("{}x{}", self.w(), self.h()).into() } pub fn of > (&self, item: T) -> Bsp, T> { Bsp::b(Fill::XY(self), item) } pub fn new (x: O::Unit, y: O::Unit) -> Self { Self { __: PhantomData::default(), x: Arc::new(x.atomic()), y: Arc::new(y.atomic()), } } } /// FIXME don't convert to u16 specifically impl HasWH for Measure { fn w (&self) -> O::Unit { (self.x.load(Relaxed) as u16).into() } fn h (&self) -> O::Unit { (self.y.load(Relaxed) as u16).into() } } impl From> for Measure { fn from (WH(x, y): WH) -> Self { Self::new(x, y) } } impl Layout for () { fn layout_x (&self, a: XYWH) -> O::Unit { a.x() } fn layout_y (&self, a: XYWH) -> O::Unit { a.y() } fn layout_w (&self, _: XYWH) -> O::Unit { 0.into() } fn layout_w_min (&self, _: XYWH) -> O::Unit { 0.into() } fn layout_w_max (&self, _: XYWH) -> O::Unit { 0.into() } fn layout_h (&self, _: XYWH) -> O::Unit { 0.into() } fn layout_h_min (&self, _: XYWH) -> O::Unit { 0.into() } fn layout_h_max (&self, _: XYWH) -> O::Unit { 0.into() } fn layout (&self, a: XYWH) -> XYWH { XYWH(a.x(), a.y(), 0.into(), 0.into()) } } impl> Layout for &L { fn layout_x (&self, a: XYWH) -> O::Unit { (*self).layout_x(a) } fn layout_y (&self, a: XYWH) -> O::Unit { (*self).layout_y(a) } fn layout_w (&self, a: XYWH) -> O::Unit { (*self).layout_w(a) } fn layout_w_min (&self, a: XYWH) -> O::Unit { (*self).layout_w_min(a) } fn layout_w_max (&self, a: XYWH) -> O::Unit { (*self).layout_w_max(a) } fn layout_h (&self, a: XYWH) -> O::Unit { (*self).layout_h(a) } fn layout_h_min (&self, a: XYWH) -> O::Unit { (*self).layout_h_min(a) } fn layout_h_max (&self, a: XYWH) -> O::Unit { (*self).layout_h_max(a) } fn layout (&self, a: XYWH) -> XYWH { (*self).layout(a) } } impl> Layout for &mut L { fn layout_x (&self, a: XYWH) -> O::Unit { (**self).layout_x(a) } fn layout_y (&self, a: XYWH) -> O::Unit { (**self).layout_y(a) } fn layout_w (&self, a: XYWH) -> O::Unit { (**self).layout_w(a) } fn layout_w_min (&self, a: XYWH) -> O::Unit { (**self).layout_w_min(a) } fn layout_w_max (&self, a: XYWH) -> O::Unit { (**self).layout_w_max(a) } fn layout_h (&self, a: XYWH) -> O::Unit { (**self).layout_h(a) } fn layout_h_min (&self, a: XYWH) -> O::Unit { (**self).layout_h_min(a) } fn layout_h_max (&self, a: XYWH) -> O::Unit { (**self).layout_h_max(a) } fn layout (&self, a: XYWH) -> XYWH { (**self).layout(a) } } impl> Layout for Arc { fn layout_x (&self, a: XYWH) -> O::Unit { (**self).layout_x(a) } fn layout_y (&self, a: XYWH) -> O::Unit { (**self).layout_y(a) } fn layout_w (&self, a: XYWH) -> O::Unit { (**self).layout_w(a) } fn layout_w_min (&self, a: XYWH) -> O::Unit { (**self).layout_w_min(a) } fn layout_w_max (&self, a: XYWH) -> O::Unit { (**self).layout_w_max(a) } fn layout_h (&self, a: XYWH) -> O::Unit { (**self).layout_h(a) } fn layout_h_min (&self, a: XYWH) -> O::Unit { (**self).layout_h_min(a) } fn layout_h_max (&self, a: XYWH) -> O::Unit { (**self).layout_h_max(a) } fn layout (&self, a: XYWH) -> XYWH { (**self).layout(a) } } impl Layout for Box> { fn layout_x (&self, a: XYWH) -> O::Unit { (**self).layout_x(a) } fn layout_y (&self, a: XYWH) -> O::Unit { (**self).layout_y(a) } fn layout_w (&self, a: XYWH) -> O::Unit { (**self).layout_w(a) } fn layout_w_min (&self, a: XYWH) -> O::Unit { (**self).layout_w_min(a) } fn layout_w_max (&self, a: XYWH) -> O::Unit { (**self).layout_w_max(a) } fn layout_h (&self, a: XYWH) -> O::Unit { (**self).layout_h(a) } fn layout_h_min (&self, a: XYWH) -> O::Unit { (**self).layout_h_min(a) } fn layout_h_max (&self, a: XYWH) -> O::Unit { (**self).layout_h_max(a) } fn layout (&self, a: XYWH) -> XYWH { (**self).layout(a) } } impl> Layout for RwLock { fn layout_x (&self, a: XYWH) -> O::Unit { self.read().unwrap().layout_x(a) } fn layout_y (&self, a: XYWH) -> O::Unit { self.read().unwrap().layout_y(a) } fn layout_w (&self, a: XYWH) -> O::Unit { self.read().unwrap().layout_w(a) } fn layout_w_min (&self, a: XYWH) -> O::Unit { self.read().unwrap().layout_w_min(a) } fn layout_w_max (&self, a: XYWH) -> O::Unit { self.read().unwrap().layout_w_max(a) } fn layout_h (&self, a: XYWH) -> O::Unit { self.read().unwrap().layout_h(a) } fn layout_h_min (&self, a: XYWH) -> O::Unit { self.read().unwrap().layout_h_min(a) } fn layout_h_max (&self, a: XYWH) -> O::Unit { self.read().unwrap().layout_h_max(a) } fn layout (&self, a: XYWH) -> XYWH { self.read().unwrap().layout(a) } } impl> Layout for Option { fn layout_x (&self, to: XYWH) -> O::Unit { self.as_ref().map(|c|c.layout_x(to)).unwrap_or(to.x()) } fn layout_y (&self, to: XYWH) -> O::Unit { self.as_ref().map(|c|c.layout_y(to)).unwrap_or(to.y()) } fn layout_w_min (&self, to: XYWH) -> O::Unit { self.as_ref().map(|c|c.layout_w_min(to)).unwrap_or(0.into()) } fn layout_w_max (&self, to: XYWH) -> O::Unit { self.as_ref().map(|c|c.layout_w_max(to)).unwrap_or(0.into()) } fn layout_w (&self, to: XYWH) -> O::Unit { self.as_ref().map(|c|c.layout_w(to)).unwrap_or(0.into()) } fn layout_h_min (&self, to: XYWH) -> O::Unit { self.as_ref().map(|c|c.layout_h_min(to)).unwrap_or(0.into()) } fn layout_h_max (&self, to: XYWH) -> O::Unit { self.as_ref().map(|c|c.layout_h_max(to)).unwrap_or(0.into()) } fn layout_h (&self, to: XYWH) -> O::Unit { self.as_ref().map(|c|c.layout_h(to)).unwrap_or(0.into()) } fn layout (&self, to: XYWH) -> XYWH { let xywh = XYWH(self.layout_x(to), self.layout_y(to), self.layout_w(to), self.layout_h(to)); self.as_ref().map(|c|c.layout(xywh)).unwrap_or(XYWH(to.x(), to.y(), 0.into(), 0.into())) } } impl> HasContent for Bounded { fn content (&self) -> impl Content { &self.1 } } impl> Draw for Bounded { fn draw (&self, to: &mut O) { let area = to.area(); *to.area_mut() = self.0; self.1.draw(to); *to.area_mut() = area; } } impl> When { /// Create a binary condition. pub const fn new (c: bool, a: T) -> Self { Self(c, a, PhantomData) } } impl> Layout for When { fn layout (&self, to: XYWH) -> XYWH { let Self(cond, item, ..) = self; if *cond { item.layout(to) } else { XYWH::::zero().into() } } } impl> Draw for When { fn draw (&self, to: &mut O) { let Self(cond, item, ..) = self; if *cond { Bounded(self.layout(to.area()), item).draw(to) } } } impl, B: Content> Either { /// Create a ternary view condition. pub const fn new (c: bool, a: A, b: B) -> Self { Self(c, a, b, PhantomData) } } impl, B: Layout> Layout for Either { fn layout (&self, to: XYWH) -> XYWH { let Self(cond, a, b, ..) = self; if *cond { a.layout(to) } else { b.layout(to) } } } impl, B: Content> Draw for Either { fn draw (&self, to: &mut E) { let Self(cond, a, b, ..) = self; let area = self.layout(to.area()); if *cond { Bounded(area, a).draw(to) } else { Bounded(area, b).draw(to) } } } push_pull!(Push: plus); push_pull!(Pull: minus); layout_op_xy!(0: Fill); impl> Layout for Fill { fn layout_x (&self, area: XYWH) -> O::Unit { if self.dx() { area.x() } else { self.inner().layout_x(area) } } fn layout_y (&self, area: XYWH) -> O::Unit { if self.dy() { area.y() } else { self.inner().layout_y(area) } } fn layout_w (&self, area: XYWH) -> O::Unit { if self.dx() { area.w() } else { self.inner().layout_w(area) } } fn layout_w_min (&self, area: XYWH) -> O::Unit { if self.dx() { area.w() } else { self.inner().layout_w_min(area) } } fn layout_w_max (&self, area: XYWH) -> O::Unit { if self.dx() { area.w() } else { self.inner().layout_w_max(area) } } fn layout_h (&self, area: XYWH) -> O::Unit { if self.dy() { area.h() } else { self.inner().layout_h(area) } } fn layout_h_min (&self, area: XYWH) -> O::Unit { if self.dy() { area.h() } else { self.inner().layout_h_min(area) } } fn layout_h_max (&self, area: XYWH) -> O::Unit { if self.dy() { area.h() } else { self.inner().layout_h_max(area) } } } impl Fill { #[inline] pub const fn dx (&self) -> bool { matches!(self, Self::X(_) | Self::XY(_)) } #[inline] pub const fn dy (&self) -> bool { matches!(self, Self::Y(_) | Self::XY(_)) } } layout_op_xy!(1 opt: Fixed); impl> Layout for Fixed { fn layout_w (&self, area: XYWH) -> O::Unit { self.dx().unwrap_or(self.inner().layout_w(area)) } fn layout_w_min (&self, area: XYWH) -> O::Unit { self.dx().unwrap_or(self.inner().layout_w_min(area)) } fn layout_w_max (&self, area: XYWH) -> O::Unit { self.dx().unwrap_or(self.inner().layout_w_max(area)) } fn layout_h (&self, area: XYWH) -> O::Unit { self.dy().unwrap_or(self.inner().layout_h(area)) } fn layout_h_min (&self, area: XYWH) -> O::Unit { self.dy().unwrap_or(self.inner().layout_h_min(area)) } fn layout_h_max (&self, area: XYWH) -> O::Unit { self.dy().unwrap_or(self.inner().layout_h_max(area)) } } layout_op_xy!(1 opt: Max); impl> Layout for Max { fn layout (&self, area: XYWH) -> XYWH { let XYWH(x, y, w, h) = self.inner().layout(area); match self { Self::X(mw, _) => XYWH(x, y, w.min(*mw), h ), Self::Y(mh, _) => XYWH(x, y, w, h.min(*mh)), Self::XY(mw, mh, _) => XYWH(x, y, w.min(*mw), h.min(*mh)), } } } layout_op_xy!(1 opt: Min); impl> Layout for Min { fn layout (&self, area: XYWH) -> XYWH { let XYWH(x, y, w, h) = self.inner().layout(area); match self { Self::X(mw, _) => XYWH(x, y, w.max(*mw), h), Self::Y(mh, _) => XYWH(x, y, w, h.max(*mh)), Self::XY(mw, mh, _) => XYWH(x, y, w.max(*mw), h.max(*mh)), } } } layout_op_xy!(1 opt: Expand); impl> Layout for Expand { fn layout_w (&self, to: XYWH) -> O::Unit { self.inner().layout_w(to).plus(self.dx().unwrap_or_default()) } fn layout_h (&self, to: XYWH) -> O::Unit { self.inner().layout_w(to).plus(self.dy().unwrap_or_default()) } } // FIXME: why they differ? layout_op_xy!(1 opt: Shrink); impl> Layout for Shrink { fn layout (&self, to: XYWH) -> XYWH { let area = self.inner().layout(to); let dx = self.dx().unwrap_or_default(); let dy = self.dy().unwrap_or_default(); XYWH(area.x(), area.y(), area.w().minus(dx), area.h().minus(dy)) } } impl Align { #[inline] pub const fn c (a: T) -> Self { Self(Alignment::Center, a) } #[inline] pub const fn x (a: T) -> Self { Self(Alignment::X, a) } #[inline] pub const fn y (a: T) -> Self { Self(Alignment::Y, a) } #[inline] pub const fn n (a: T) -> Self { Self(Alignment::N, a) } #[inline] pub const fn s (a: T) -> Self { Self(Alignment::S, a) } #[inline] pub const fn e (a: T) -> Self { Self(Alignment::E, a) } #[inline] pub const fn w (a: T) -> Self { Self(Alignment::W, a) } #[inline] pub const fn nw (a: T) -> Self { Self(Alignment::NW, a) } #[inline] pub const fn sw (a: T) -> Self { Self(Alignment::SW, a) } #[inline] pub const fn ne (a: T) -> Self { Self(Alignment::NE, a) } #[inline] pub const fn se (a: T) -> Self { Self(Alignment::SE, a) } } impl> Draw for Align { fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), &self.1).draw(to) } } impl> Layout for Align { fn layout_x (&self, to: XYWH) -> O::Unit { match self.0 { NW | W | SW => to.x(), N | Center | S => to.x().plus(to.w() / 2.into()).minus(self.1.layout_w(to) / 2.into()), NE | E | SE => to.x().plus(to.w()).minus(self.1.layout_w(to)), _ => todo!(), } } fn layout_y (&self, to: XYWH) -> O::Unit { match self.0 { NW | N | NE => to.y(), W | Center | E => to.y().plus(to.h() / 2.into()).minus(self.1.layout_h(to) / 2.into()), SW | S | SE => to.y().plus(to.h()).minus(self.1.layout_h(to)), _ => todo!(), } } } impl Pad { #[inline] pub const fn inner (&self) -> &A { use Pad::*; match self { X(_, c) | Y(_, c) | XY(_, _, c) => c, } } } impl Pad { #[inline] pub fn dx (&self) -> U { use Pad::*; match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, } } #[inline] pub fn dy (&self) -> U { use Pad::*; match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, } } } impl> Draw for Pad { fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) } } impl> Layout for Pad { fn layout_x (&self, area: XYWH) -> O::Unit { area.x().plus(self.dx()) } fn layout_y (&self, area: XYWH) -> O::Unit { area.x().plus(self.dx()) } fn layout_w (&self, area: XYWH) -> O::Unit { area.w().minus(self.dx() * 2.into()) } fn layout_h (&self, area: XYWH) -> O::Unit { area.h().minus(self.dy() * 2.into()) } } impl Bsp { #[inline] pub const fn n (a: Head, b: Tail) -> Self { Self(North, a, b) } #[inline] pub const fn s (a: Head, b: Tail) -> Self { Self(South, a, b) } #[inline] pub const fn e (a: Head, b: Tail) -> Self { Self(East, a, b) } #[inline] pub const fn w (a: Head, b: Tail) -> Self { Self(West, a, b) } #[inline] pub const fn a (a: Head, b: Tail) -> Self { Self(Above, a, b) } #[inline] pub const fn b (a: Head, b: Tail) -> Self { Self(Below, a, b) } } impl, Tail: Content> Draw for Bsp { fn draw (&self, to: &mut O) { match self.0 { South => { //panic!("{}", self.1.h(to.area())); let area_1 = self.1.layout(to.area()); let area_2 = self.2.layout(XYWH( to.area().x(), to.area().y().plus(area_1.h()), to.area().w(), to.area().h().minus(area_1.h()) )); //panic!("{area_1:?} {area_2:?}"); to.place_at(area_1, &self.1); to.place_at(area_2, &self.2); }, _ => todo!("{:?}", self.0) } //let [a, b, _] = bsp_areas(to.area(), self.0, &self.1, &self.2); //panic!("{a:?} {b:?}"); //if self.0 == Below { //to.place_at(a, &self.1); //to.place_at(b, &self.2); //} else { //to.place_at(b, &self.2); //to.place_at(a, &self.1); //} } } impl, Tail: Layout> Layout for Bsp { fn layout_w (&self, area: XYWH) -> O::Unit { match self.0 { Above | Below | North | South => self.1.layout_w(area).max(self.2.layout_w(area)), East | West => self.1.layout_w_min(area).plus(self.2.layout_w(area)), } } fn layout_w_min (&self, area: XYWH) -> O::Unit { match self.0 { Above | Below | North | South => self.1.layout_w_min(area).max(self.2.layout_w_min(area)), East | West => self.1.layout_w_min(area).plus(self.2.layout_w_min(area)), } } fn layout_w_max (&self, area: XYWH) -> O::Unit { match self.0 { Above | Below | North | South => self.1.layout_w_max(area).max(self.2.layout_w_max(area)), East | West => self.1.layout_w_max(area).plus(self.2.layout_w_max(area)), } } fn layout_h (&self, area: XYWH) -> O::Unit { match self.0 { Above | Below | East | West => self.1.layout_h(area).max(self.2.layout_h(area)), North | South => self.1.layout_h(area).plus(self.2.layout_h(area)), } } fn layout_h_min (&self, area: XYWH) -> O::Unit { match self.0 { Above | Below | East | West => self.1.layout_h_min(area).max(self.2.layout_h_min(area)), North | South => self.1.layout_h_min(area).plus(self.2.layout_h_min(area)), } } fn layout_h_max (&self, area: XYWH) -> O::Unit { match self.0 { Above | Below | North | South => self.1.layout_h_max(area).max(self.2.layout_h_max(area)), East | West => self.1.layout_h_max(area).plus(self.2.layout_h_max(area)), } } fn layout (&self, area: XYWH) -> XYWH { bsp_areas(area, self.0, &self.1, &self.2)[2] } } fn bsp_areas , B: Layout> ( area: XYWH, direction: Direction, a: &A, b: &B, ) -> [XYWH;3] { let XYWH(x, y, w, h) = area; let WH(aw, ah) = a.layout(area).wh(); let WH(bw, bh) = b.layout(match direction { South => XYWH(x, y + ah, w, h.minus(ah)), North => XYWH(x, y, w, h.minus(ah)), East => XYWH(x + aw, y, w.minus(aw), h), West => XYWH(x, y, w.minus(aw), h), Above => area, Below => area, }).wh(); match direction { Above | Below => { let XYWH(x, y, w, h) = area.centered_xy([aw.max(bw), ah.max(bh)]); let a = XYWH((x + w/2.into()).minus(aw/2.into()), (y + h/2.into()).minus(ah/2.into()), aw, ah); let b = XYWH((x + w/2.into()).minus(bw/2.into()), (y + h/2.into()).minus(bh/2.into()), bw, bh); [a.into(), b.into(), XYWH(x, y, w, h)] }, South => { let XYWH(x, y, w, h) = area.centered_xy([aw.max(bw), ah + bh]); let a = XYWH((x + w/2.into()).minus(aw/2.into()), y, aw, ah); let b = XYWH((x + w/2.into()).minus(bw/2.into()), y + ah, bw, bh); [a.into(), b.into(), XYWH(x, y, w, h)] }, North => { let XYWH(x, y, w, h) = area.centered_xy([aw.max(bw), ah + bh]); let a = XYWH((x + (w/2.into())).minus(aw/2.into()), y + bh, aw, ah); let b = XYWH((x + (w/2.into())).minus(bw/2.into()), y, bw, bh); [a.into(), b.into(), XYWH(x, y, w, h)] }, East => { let XYWH(x, y, w, h) = area.centered_xy([aw + bw, ah.max(bh)]); let a = XYWH(x, (y + h/2.into()).minus(ah/2.into()), aw, ah); let b = XYWH(x + aw, (y + h/2.into()).minus(bh/2.into()), bw, bh); [a.into(), b.into(), XYWH(x, y, w, h)] }, West => { let XYWH(x, y, w, h) = area.centered_xy([aw + bw, ah.max(bh)]); let a = XYWH(x + bw, (y + h/2.into()).minus(ah/2.into()), aw, ah); let b = XYWH(x, (y + h/2.into()).minus(bh/2.into()), bw, bh); [a.into(), b.into(), XYWH(x, y, w, h)] }, } } impl<'a, O, A, B, I, F, G> Map where I: Iterator + Send + Sync + 'a, F: Fn() -> I + Send + Sync + 'a, { pub const fn new (get_iter: F, get_item: G) -> Self { Self { __: PhantomData, get_iter, get_item } } } impl_map_direction!(east, X, w); impl_map_direction!(south, Y, n); impl_map_direction!(west, X, e); impl_map_direction!(north, Y, s); impl<'a, O, A, B, I, F, G> Layout for Map where O: Out, B: Layout, I: Iterator + Send + Sync + 'a, F: Fn() -> I + Send + Sync + 'a, G: Fn(A, usize)->B + Send + Sync { fn layout (&self, area: XYWH) -> XYWH { let Self { get_iter, get_item, .. } = self; let mut index = 0; let XY(mut min_x, mut min_y) = area.centered(); let XY(mut max_x, mut max_y) = area.center(); for item in get_iter() { let XYWH(x, y, w, h) = get_item(item, index).layout(area); min_x = min_x.min(x); min_y = min_y.min(y); max_x = max_x.max(x + w); max_y = max_y.max(y + h); index += 1; } let w = max_x - min_x; let h = max_y - min_y; //[min_x.into(), min_y.into(), w.into(), h.into()].into() area.centered_xy([w.into(), h.into()]) } } impl<'a, O, A, B, I, F, G> Draw for Map where O: Out, B: Content, I: Iterator + Send + Sync + 'a, F: Fn() -> I + Send + Sync + 'a, G: Fn(A, usize)->B + Send + Sync { fn draw (&self, to: &mut O) { let Self { get_iter, get_item, .. } = self; let mut index = 0; let area = self.layout(to.area()); for item in get_iter() { let item = get_item(item, index); //to.place_at(area.into(), &item); to.place_at(item.layout(area), &item); index += 1; } } } impl Tryptich<(), (), ()> { pub fn center (h: u16) -> Self { Self { h, top: false, left: (0, ()), middle: (0, ()), right: (0, ()) } } pub fn top (h: u16) -> Self { Self { h, top: true, left: (0, ()), middle: (0, ()), right: (0, ()) } } } impl Tryptich { pub fn left (self, w: u16, content: D) -> Tryptich { Tryptich { left: (w, content), ..self } } pub fn middle (self, w: u16, content: D) -> Tryptich { Tryptich { middle: (w, content), ..self } } pub fn right (self, w: u16, content: D) -> Tryptich { Tryptich { right: (w, content), ..self } } } impl> Layout for Foreground { fn layout (&self, to: XYWH) -> XYWH { self.1.layout(to) } } impl> Layout for Background { fn layout (&self, to: XYWH) -> XYWH { self.1.layout(to) } } impl, V: Content> HasContent for FieldH { fn content (&self) -> impl Content { Bsp::e(&self.1, &self.2) } } impl, V: Content> Layout for FieldH { fn layout (&self, to: XYWH) -> XYWH { self.content().layout(to) } } impl, V: Content> Draw for FieldH { fn draw (&self, to: &mut O) { self.content().draw(to) } } impl, V: Content> HasContent for FieldV { fn content (&self) -> impl Content { Bsp::s(&self.1, &self.2) } } impl, V: Content> Layout for FieldV { fn layout (&self, to: XYWH) -> XYWH { self.content().layout(to) } } impl, V: Content> Draw for FieldV { fn draw (&self, to: &mut O) { self.content().draw(to) } } impl> Layout for Border { fn layout (&self, area: XYWH) -> XYWH { self.1.layout(area) } } impl Field { pub fn new (direction: Direction) -> Field { Field:: { direction, label: None, label_fg: None, label_bg: None, label_align: None, value: None, value_fg: None, value_bg: None, value_align: None, } } pub fn label ( self, label: Option, align: Option, fg: Option, bg: Option ) -> Field { Field:: { label, label_fg: fg, label_bg: bg, label_align: align, ..self } } pub fn value ( self, value: Option, align: Option, fg: Option, bg: Option ) -> Field { Field:: { value, value_fg: fg, value_bg: bg, value_align: align, ..self } } } impl Default for PerfModel { fn default () -> Self { Self { enabled: true, clock: quanta::Clock::new(), used: Default::default(), window: Default::default(), } } } impl PerfModel { pub fn get_t0 (&self) -> Option { if self.enabled { Some(self.clock.raw()) } else { None } } pub fn get_t1 (&self, t0: Option) -> Option { if let Some(t0) = t0 { if self.enabled { Some(self.clock.delta(t0, self.clock.raw())) } else { None } } else { None } } pub fn update (&self, t0: Option, microseconds: f64) { if let Some(t0) = t0 { let t1 = self.clock.raw(); self.used.store(self.clock.delta_as_nanos(t0, t1) as f64, Relaxed); self.window.store(microseconds, Relaxed,); } } pub fn percentage (&self) -> Option { let window = self.window.load(Relaxed) * 1000.0; if window > 0.0 { let used = self.used.load(Relaxed); Some(100.0 * used / window) } else { None } } } impl Tui { pub fn new (output: Stdout) -> Usually { let backend = CrosstermBackend::new(output); let Size { width, height } = backend.size()?; Ok(Self { exited: Arc::new(AtomicBool::new(false)), buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }), area: [0, 0, width, height], perf: Default::default(), backend, }) } /// Create and launch a terminal user interface. pub fn run (self, join: bool, state: &Arc>) -> Usually>> where T: Handle + Draw + Send + Sync + 'static { let tui = Arc::new(RwLock::new(self)); let _input_thread = tui_input(tui.clone(), state, Duration::from_millis(100)); tui.write().unwrap().setup()?; let render_thread = tui_output(tui.clone(), state, Duration::from_millis(10))?; if join { match render_thread.join() { Ok(result) => { tui.write().unwrap().teardown()?; println!("\n\rRan successfully: {result:?}\n\r"); }, Err(error) => { tui.write().unwrap().teardown()?; panic!("\n\rDraw thread failed: error={error:?}.\n\r") }, } } Ok(tui) } /// True if done pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) } /// Prepare before run pub fn setup (&mut self) -> Usually<()> { tui_setup(&mut self.backend) } /// Clean up after run pub fn teardown (&mut self) -> Usually<()> { tui_teardown(&mut self.backend) } /// Apply changes to the display buffer. pub fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer { tui_resized(&mut self.backend, &mut self.buffer, size); tui_redrawn(&mut self.backend, &mut self.buffer, &mut buffer); buffer } } impl Input for TuiIn { type Event = TuiEvent; type Handled = bool; fn event (&self) -> &TuiEvent { &self.event } fn done (&self) { self.exited.store(true, Relaxed); } fn is_done (&self) -> bool { self.exited.fetch_and(true, Relaxed) } } impl Ord for TuiEvent { fn cmp (&self, other: &Self) -> std::cmp::Ordering { self.partial_cmp(other) .unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf } } impl TuiEvent { pub fn from_crossterm (event: Event) -> Self { Self(event) } #[cfg(feature = "dsl")] pub fn from_dsl (dsl: impl Language) -> Perhaps { Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self)) } } impl From for TuiEvent { fn from (c: char) -> Self { Self(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE))) } } impl TuiKey { const SPLIT: char = '/'; #[cfg(feature = "dsl")] pub fn from_dsl (dsl: impl Language) -> Usually { if let Some(word) = dsl.word()? { let word = word.trim(); Ok(if word == ":char" { Self(None, KeyModifiers::NONE) } else if word.chars().nth(0) == Some('@') { let mut key = None; let mut modifiers = KeyModifiers::NONE; let mut tokens = word[1..].split(Self::SPLIT).peekable(); while let Some(token) = tokens.next() { if tokens.peek().is_some() { match token { "ctrl" | "Ctrl" | "c" | "C" => modifiers |= KeyModifiers::CONTROL, "alt" | "Alt" | "m" | "M" => modifiers |= KeyModifiers::ALT, "shift" | "Shift" | "s" | "S" => { modifiers |= KeyModifiers::SHIFT; // + TODO normalize character case, BackTab, etc. }, _ => panic!("unknown modifier {token}"), } } else { key = if token.len() == 1 { Some(KeyCode::Char(token.chars().next().unwrap())) } else { Some(named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) } } } Self(key, modifiers) } else { return Err(format!("TuiKey: unexpected: {word}").into()) }) } else { return Err(format!("TuiKey: unspecified").into()) } } pub fn to_crossterm (&self) -> Option { self.0.map(|code|Event::Key(KeyEvent { code, modifiers: self.1, kind: KeyEventKind::Press, state: KeyEventState::NONE, })) } } impl Out for TuiOut { type Unit = u16; #[inline] fn area (&self) -> XYWH { self.area } #[inline] fn area_mut (&mut self) -> &mut XYWH { &mut self.area } #[inline] fn place_at <'t, T: Draw + ?Sized> (&mut self, area: XYWH, content: &'t T) { let last = self.area(); *self.area_mut() = area; content.draw(self); *self.area_mut() = last; } } impl TuiOut { #[inline] pub fn with_rect (&mut self, area: XYWH) -> &mut Self { self.area = area; self } pub fn update (&mut self, area: XYWH, callback: &impl Fn(&mut Cell, u16, u16)) { tui_update(&mut self.buffer, area, callback); } pub fn fill_char (&mut self, area: XYWH, c: char) { self.update(area, &|cell,_,_|{cell.set_char(c);}) } pub fn fill_bg (&mut self, area: XYWH, color: Color) { self.update(area, &|cell,_,_|{cell.set_bg(color);}) } pub fn fill_fg (&mut self, area: XYWH, color: Color) { self.update(area, &|cell,_,_|{cell.set_fg(color);}) } pub fn fill_mod (&mut self, area: XYWH, on: bool, modifier: Modifier) { if on { self.update(area, &|cell,_,_|cell.modifier.insert(modifier)) } else { self.update(area, &|cell,_,_|cell.modifier.remove(modifier)) } } pub fn fill_bold (&mut self, area: XYWH, on: bool) { self.fill_mod(area, on, Modifier::BOLD) } pub fn fill_reversed (&mut self, area: XYWH, on: bool) { self.fill_mod(area, on, Modifier::REVERSED) } pub fn fill_crossed_out (&mut self, area: XYWH, on: bool) { self.fill_mod(area, on, Modifier::CROSSED_OUT) } pub fn fill_ul (&mut self, area: XYWH, color: Option) { if let Some(color) = color { self.update(area, &|cell,_,_|{ cell.modifier.insert(ratatui::prelude::Modifier::UNDERLINED); cell.underline_color = color; }) } else { self.update(area, &|cell,_,_|{ cell.modifier.remove(ratatui::prelude::Modifier::UNDERLINED); }) } } pub fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) { for cell in self.buffer.content.iter_mut() { cell.fg = fg; cell.bg = bg; cell.modifier = modifier; } } pub fn blit (&mut self, text: &impl AsRef, x: u16, y: u16, style: Option