diff --git a/output/src/layout_align.rs b/output/src/layout_align.rs index 76cc8fd..59eda80 100644 --- a/output/src/layout_align.rs +++ b/output/src/layout_align.rs @@ -1,7 +1,7 @@ //! ``` //! use ::tengri::{output::*, tui::*}; //! let area: [u16;4] = [10, 10, 20, 20]; -//! fn test (area: [u16;4], item: &impl Render, expected: [u16;4]) { +//! fn test (area: [u16;4], item: &impl Content, expected: [u16;4]) { //! assert_eq!(Content::layout(item, area), expected); //! assert_eq!(Render::layout(item, area), expected); //! }; @@ -48,36 +48,33 @@ impl Align { #[inline] pub const fn se (a: A) -> Self { Self(Alignment::SE, a) } } -impl> Render for Align { +impl> Content for Align { + fn content (&self) -> impl Render + '_ { + &self.1 + } fn layout (&self, on: E::Area) -> E::Area { - self.0.align(on, &self.1) - } - fn render (&self, to: &mut E) { - to.place(self.layout(to.area()), &self.1) - } -} - -impl Alignment { - fn align (&self, on: E::Area, content: impl Render) -> E::Area { use Alignment::*; - let it = content.layout(on).xywh(); + let it = Render::layout(&self.content(), on).xywh(); let cx = on.x()+(on.w().minus(it.w())/2.into()); let cy = on.y()+(on.h().minus(it.h())/2.into()); let fx = (on.x()+on.w()).minus(it.w()); let fy = (on.y()+on.h()).minus(it.h()); - let [x, y] = match self { - Center => [cx, cy], - X => [cx, it.y()], - Y => [it.x(), cy], - NW => [on.x(), on.y()], - N => [cx, on.y()], - NE => [fx, on.y()], - W => [on.x(), cy], - E => [fx, cy], - SW => [on.x(), fy], - S => [cx, fy], - SE => [fx, fy], - }; + let [x, y] = match self.0 { + Center => [cx, cy], + X => [cx, it.y()], + Y => [it.x(), cy], + NW => [on.x(), on.y()], + N => [cx, on.y()], + NE => [fx, on.y()], + W => [on.x(), cy], + E => [fx, cy], + SW => [on.x(), fy], + S => [cx, fy], + SE => [fx, fy], + }.into(); [x, y, it.w(), it.h()].into() } + fn render (&self, to: &mut E) { + to.place(Content::layout(self, to.area()), &self.content()) + } } diff --git a/output/src/layout_bsp.rs b/output/src/layout_bsp.rs index 73d6882..0272d17 100644 --- a/output/src/layout_bsp.rs +++ b/output/src/layout_bsp.rs @@ -15,7 +15,7 @@ impl Bsp { #[inline] pub const fn a (a: A, b: B) -> Self { Self(Above, a, b) } #[inline] pub const fn b (a: A, b: B) -> Self { Self(Below, a, b) } } -impl, B: Render> Render for Bsp { +impl, B: Content> Content for Bsp { fn layout (&self, outer: E::Area) -> E::Area { let [_, _, c] = self.areas(outer); c } fn render (&self, to: &mut E) { let [area_a, area_b, _] = self.areas(to.area()); @@ -26,11 +26,11 @@ impl, B: Render> Render for Bsp { } } } -impl, B: Render> BspAreas for Bsp { +impl, B: Content> BspAreas for Bsp { fn direction (&self) -> Direction { self.0 } fn contents (&self) -> (&A, &B) { (&self.1, &self.2) } } -pub trait BspAreas, B: Render> { +pub trait BspAreas, B: Content> { fn direction (&self) -> Direction; fn contents (&self) -> (&A, &B); fn areas (&self, outer: E::Area) -> [E::Area;3] { diff --git a/output/src/layout_cond.rs b/output/src/layout_cond.rs index 7a7bdc0..1da4925 100644 --- a/output/src/layout_cond.rs +++ b/output/src/layout_cond.rs @@ -6,7 +6,7 @@ impl When { /// Create a binary condition. pub const fn new (c: bool, a: A) -> Self { Self(c, a) } } -impl> Render for When { +impl> Content for When { fn layout (&self, to: E::Area) -> E::Area { let Self(cond, item) = self; let mut area = E::Area::zero(); @@ -31,7 +31,7 @@ impl Either { /// Create a ternary view condition. pub const fn new (c: bool, a: A, b: B) -> Self { Self(c, a, b) } } -impl, B: Render> Render for Either { +impl, B: Render> Content for Either { fn layout (&self, to: E::Area) -> E::Area { let Self(cond, a, b) = self; if *cond { a.layout(to) } else { b.layout(to) } diff --git a/output/src/layout_map.rs b/output/src/layout_map.rs index 4c8494c..a194c06 100644 --- a/output/src/layout_map.rs +++ b/output/src/layout_map.rs @@ -66,7 +66,7 @@ impl_map_direction!(south, y, n); impl_map_direction!(west, x, e); impl_map_direction!(north, y, s); -impl<'a, E, A, B, I, F, G> Render for Map where +impl<'a, E, A, B, I, F, G> Content for Map where E: Output, B: Render, I: Iterator + Send + Sync + 'a, @@ -94,7 +94,7 @@ impl<'a, E, A, B, I, F, G> Render for Map where fn render (&self, to: &mut E) { let Self { get_iter, get_item, .. } = self; let mut index = 0; - let area = Render::layout(self, to.area()); + let area = Content::layout(self, to.area()); for item in get_iter() { let item = get_item(item, index); //to.place(area.into(), &item); @@ -107,24 +107,24 @@ impl<'a, E, A, B, I, F, G> Render for Map where #[inline] pub fn map_south( item_offset: O::Unit, item_height: O::Unit, - item: impl Render -) -> impl Render { + item: impl Content +) -> impl Content { Push::y(item_offset, Fixed::y(item_height, Fill::x(item))) } #[inline] pub fn map_south_west( item_offset: O::Unit, item_height: O::Unit, - item: impl Render -) -> impl Render { + item: impl Content +) -> impl Content { Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item)))) } #[inline] pub fn map_east( item_offset: O::Unit, item_width: O::Unit, - item: impl Render -) -> impl Render { + item: impl Content +) -> impl Content { Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item)))) } diff --git a/output/src/layout_stack.rs b/output/src/layout_stack.rs index c316c39..95f8c82 100644 --- a/output/src/layout_stack.rs +++ b/output/src/layout_stack.rs @@ -1,44 +1,43 @@ use crate::*; use Direction::*; -pub struct Stack<'x, E, F1> { - __: PhantomData<&'x (E, F1)>, +pub struct Stack<'x, E, F> { + __: PhantomData<&'x E>, direction: Direction, - callback: F1 + callback: F } - -impl<'x, E, F1> Stack<'x, E, F1> { - pub fn new (direction: Direction, callback: F1) -> Self { +impl<'x, E, F: Fn(&mut dyn FnMut(&dyn Render)) + 'x> Stack<'x, E, F> { + pub fn new (direction: Direction, callback: F) -> Self { Self { direction, callback, __: Default::default(), } } - pub fn above (callback: F1) -> Self { + pub fn above (callback: F) -> Self { Self::new(Above, callback) } - pub fn below (callback: F1) -> Self { + pub fn below (callback: F) -> Self { Self::new(Below, callback) } - pub fn north (callback: F1) -> Self { + pub fn north (callback: F) -> Self { Self::new(North, callback) } - pub fn south (callback: F1) -> Self { + pub fn south (callback: F) -> Self { Self::new(South, callback) } - pub fn east (callback: F1) -> Self { + pub fn east (callback: F) -> Self { Self::new(East, callback) } - pub fn west (callback: F1) -> Self { + pub fn west (callback: F) -> Self { Self::new(West, callback) } } - -impl<'x, E: Output, F1: Fn(&mut dyn FnMut(&dyn Render))> Render for Stack<'x, E, F1> { +impl<'x, E: Output, F: Fn(&mut dyn FnMut(&dyn Render)) + 'x> Content for Stack<'x, E, F> { fn layout (&self, to: E::Area) -> E::Area { let state = StackLayoutState::::new(self.direction, to); - (self.callback)(&mut |component: &dyn Render|{ + let mut adder = |component: &dyn Render|{ let StackLayoutState { x, y, w_remaining, h_remaining, .. } = *state.borrow(); let [_, _, w, h] = component.layout([x, y, w_remaining, h_remaining].into()).xywh(); state.borrow_mut().grow(w, h); - }); + }; + (self.callback)(&mut adder); let StackLayoutState { w_used, h_used, .. } = *state.borrow(); match self.direction { North | West => { todo!() }, @@ -49,12 +48,13 @@ impl<'x, E: Output, F1: Fn(&mut dyn FnMut(&dyn Render))> Render for Stack< fn render (&self, to: &mut E) { let state = StackLayoutState::::new(self.direction, to.area()); let to = Rc::new(RefCell::new(to)); - (self.callback)(&mut |component: &dyn Render|{ + let mut adder = |component: &dyn Render|{ let StackLayoutState { x, y, w_remaining, h_remaining, .. } = *state.borrow(); let layout = component.layout([x, y, w_remaining, h_remaining].into()); state.borrow_mut().grow(layout.w(), layout.h()); to.borrow_mut().place(layout, component); - }); + }; + (self.callback)(&mut adder); } } @@ -115,7 +115,7 @@ impl StackLayoutState { //Self { direction, callback, __: Default::default(), } //} //} -//impl<'a, E, F1> Render for Stack<'a, E, F1> where +//impl<'a, E, F1> Content for Stack<'a, E, F1> where //E: Output, F1: Fn(&mut dyn FnMut(&'a dyn Render)) + Send + Sync, //{ //fn layout (&self, to: E::Area) -> E::Area { diff --git a/output/src/layout_xy.rs b/output/src/layout_xy.rs index 52d99a6..93d2eea 100644 --- a/output/src/layout_xy.rs +++ b/output/src/layout_xy.rs @@ -2,8 +2,9 @@ //! ``` //! use ::tengri::{output::*, tui::*}; //! let area: [u16;4] = [10, 10, 20, 20]; -//! fn test (area: [u16;4], item: &impl Render, expected: [u16;4]) { -//! assert_eq!(item.layout(area), expected); +//! fn test (area: [u16;4], item: &impl Content, expected: [u16;4]) { +//! assert_eq!(Content::layout(item, area), expected); +//! assert_eq!(Render::layout(item, area), expected); //! }; //! test(area, &(), [20, 20, 0, 0]); //! @@ -27,20 +28,18 @@ macro_rules! transform_xy { #[inline] pub const fn y (item: A) -> Self { Self::Y(item) } #[inline] pub const fn xy (item: A) -> Self { Self::XY(item) } } - impl> Content for $Enum { - fn content (&self) -> Option + '_> { - use $Enum::*; - Some(match self { X(item) | Y(item) | XY(item) => item, }) + impl> Content for $Enum { + fn content (&self) -> impl Render + '_ { + match self { + Self::X(item) => item, + Self::Y(item) => item, + Self::XY(item) => item, + } } - } - impl> Render for $Enum { fn layout (&$self, $to: ::Area) -> ::Area { use $Enum::*; $area } - fn render (&self, output: &mut E) { - output.place(self.layout(output.area()), &self.content()) - } } } } @@ -55,18 +54,13 @@ macro_rules! transform_xy_unit { #[inline] pub const fn y (y: U, item: A) -> Self { Self::Y(y, item) } #[inline] pub const fn xy (x: U, y: U, item: A) -> Self { Self::XY(x, y, item) } } - impl> Content for $Enum { - fn content (&self) -> Option + '_> { - use $Enum::*; - Some(match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, }) - } - } - impl> Render for $Enum { + impl> Content for $Enum { fn layout (&$self, $to: E::Area) -> E::Area { $layout.into() } - fn render (&self, output: &mut E) { - output.place(self.layout(output.area()), &self.content()) + fn content (&self) -> impl Render + '_ { + use $Enum::*; + Some(match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, }) } } impl $Enum { @@ -94,11 +88,12 @@ transform_xy!("fill/x" "fill/y" "fill/xy" |self: Fill, to|{ transform_xy_unit!("fixed/x" "fixed/y" "fixed/xy"|self: Fixed, area|{ let [x, y, w, h] = area.xywh(); - let [x, y, w, h] = self.content().layout(match self { + let fixed_area = match self { Self::X(fw, _) => [x, y, *fw, h], Self::Y(fh, _) => [x, y, w, *fh], Self::XY(fw, fh, _) => [x, y, *fw, *fh], - }.into()).xywh(); + }; + let [x, y, w, h] = Render::layout(&self.content(), fixed_area.into()).xywh(); let fixed_area = match self { Self::X(fw, _) => [x, y, *fw, h], Self::Y(fh, _) => [x, y, w, *fh], @@ -108,41 +103,51 @@ transform_xy_unit!("fixed/x" "fixed/y" "fixed/xy"|self: Fixed, area|{ }); transform_xy_unit!("min/x" "min/y" "min/xy"|self: Min, area|{ - let [x, y, w, h] = self.content().layout(area).xywh(); + let area = Render::layout(&self.content(), area); match self { - Self::X(mw, _) => [x, y, w.max(*mw), h], - Self::Y(mh, _) => [x, y, w, h.max(*mh)], - Self::XY(mw, mh, _) => [x, y, w.max(*mw), h.max(*mh)], } }); + Self::X(mw, _) => [area.x(), area.y(), area.w().max(*mw), area.h()], + Self::Y(mh, _) => [area.x(), area.y(), area.w(), area.h().max(*mh)], + Self::XY(mw, mh, _) => [area.x(), area.y(), area.w().max(*mw), area.h().max(*mh)], + } +}); transform_xy_unit!("max/x" "max/y" "max/xy"|self: Max, area|{ let [x, y, w, h] = area.xywh(); - self.content().layout(match self { + Render::layout(&self.content(), match self { Self::X(fw, _) => [x, y, *fw, h], Self::Y(fh, _) => [x, y, w, *fh], - Self::XY(fw, fh, _) => [x, y, *fw, *fh], }.into()) }); + Self::XY(fw, fh, _) => [x, y, *fw, *fh], + }.into()) +}); -transform_xy_unit!("shrink/x" "shrink/y" "shrink/xy"|self: Shrink, area|self.content().layout( +transform_xy_unit!("shrink/x" "shrink/y" "shrink/xy"|self: Shrink, area|Render::layout( + &self.content(), [area.x(), area.y(), area.w().minus(self.dx()), area.h().minus(self.dy())].into())); -transform_xy_unit!("expand/x" "expand/y" "expand/xy"|self: Expand, area|self.content().layout( +transform_xy_unit!("expand/x" "expand/y" "expand/xy"|self: Expand, area|Render::layout( + &self.content(), [area.x(), area.y(), area.w().plus(self.dx()), area.h().plus(self.dy())].into())); transform_xy_unit!("push/x" "push/y" "push/xy"|self: Push, area|{ - let area = self.content().layout(area); - [area.x().plus(self.dx()), area.y().plus(self.dy()), area.w(), area.h()] }); + let area = Render::layout(&self.content(), area); + [area.x().plus(self.dx()), area.y().plus(self.dy()), area.w(), area.h()] +}); transform_xy_unit!("pull/x" "pull/y" "pull/xy"|self: Pull, area|{ - let area = self.content().layout(area); - [area.x().minus(self.dx()), area.y().minus(self.dy()), area.w(), area.h()] }); + let area = Render::layout(&self.content(), area); + [area.x().minus(self.dx()), area.y().minus(self.dy()), area.w(), area.h()] +}); transform_xy_unit!("margin/x" "margin/y" "margin/xy"|self: Margin, area|{ - let area = self.content().layout(area); + let area = Render::layout(&self.content(), area); let dx = self.dx(); let dy = self.dy(); - [area.x().minus(dx), area.y().minus(dy), area.w().plus(dy.plus(dy)), area.h().plus(dy.plus(dy))] }); + [area.x().minus(dx), area.y().minus(dy), area.w().plus(dy.plus(dy)), area.h().plus(dy.plus(dy))] +}); transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ - let area = self.content().layout(area); + let area = Render::layout(&self.content(), area); let dx = self.dx(); let dy = self.dy(); - [area.x().plus(dx), area.y().plus(dy), area.w().minus(dy.plus(dy)), area.h().minus(dy.plus(dy))] }); + [area.x().plus(dx), area.y().plus(dy), area.w().minus(dy.plus(dy)), area.h().minus(dy.plus(dy))] +}); diff --git a/output/src/output.rs b/output/src/output.rs index bc9e2b9..4eb1036 100644 --- a/output/src/output.rs +++ b/output/src/output.rs @@ -1,4 +1,5 @@ use crate::*; +use std::ops::Deref; /// Render target. pub trait Output: Send + Sync + Sized { diff --git a/output/src/output_content.rs b/output/src/output_content.rs index cf09897..ef199db 100644 --- a/output/src/output_content.rs +++ b/output/src/output_content.rs @@ -1,60 +1,88 @@ use crate::*; /// Composable renderable with static dispatch. -pub trait Content: Sized { - /// Return opaque [Render]able. - fn content (&self) -> Option + '_> { Option::<()>::None } +pub trait Content { + /// Return a [Render]able of a specific type. + fn content (&self) -> impl Render + '_ { + () + } + /// Perform layout. By default, delegates to [Self::content]. + fn layout (&self, area: E::Area) -> E::Area { + self.content().layout(area) + } + /// Draw to output. By default, delegates to [Self::content]. + fn render (&self, output: &mut E) { + self.content().render(output) + } +} + +/// Every pointer to [Content] is a [Content]. +impl> Content for &C { + fn content (&self) -> impl Render + '_ { (*self).content() } + fn layout (&self, area: E::Area) -> E::Area { (*self).layout(area) } + fn render (&self, output: &mut E) { (*self).render(output) } } /// The platonic ideal unit of [Content]: total emptiness at dead center (e=1vg^sqrt(-1)) -impl Content for () {} - -impl Content for fn(&mut E) { - fn content (&self) -> Option + '_> { - Some(self) - } -} - -impl> Content for fn()->T { - fn content (&self) -> Option + '_> { - Some(self()) - } +impl Content for () { + fn layout (&self, area: E::Area) -> E::Area { area.center().to_area_pos().into() } + fn render (&self, _: &mut E) {} } impl> Content for Option { - fn content (&self) -> Option + '_> { - if let Some(content) = self { - content.content() - } else { - None - } + fn content (&self) -> impl Render + '_ { + self.as_ref() } + fn layout (&self, area: E::Area) -> E::Area { + self.as_ref() + .map(|content|content.layout(area)) + .unwrap_or([0.into(), 0.into(), 0.into(), 0.into(),].into()) + } + fn render (&self, output: &mut E) { + self.as_ref() + .map(|content|content.render(output)); + } +} + +/// You can render from a box. +impl Content for RenderBox { + fn content (&self) -> impl Render + '_ { self.deref() } + //fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self } } /// You can render from an opaque pointer. impl Content for &dyn Render where Self: Sized { - fn content (&self) -> Option + '_> { + fn content (&self) -> impl Render + '_ { #[allow(suspicious_double_ref_op)] - Some(self.deref()) + self.deref() + } + fn layout (&self, area: E::Area) -> E::Area { + #[allow(suspicious_double_ref_op)] + Render::layout(self.deref(), area) + } + fn render (&self, output: &mut E) { + #[allow(suspicious_double_ref_op)] + Render::render(self.deref(), output) } } -/// Implement composable content for a struct. -#[macro_export] macro_rules! content { - // Implement for all [Output]s. - (|$self:ident:$Struct:ty| $content:expr) => { - impl Content for $Struct { - fn content (&$self) -> impl Render + '_ { Some($content) } +/// Implement [Content] with custom rendering for a struct. +#[macro_export] macro_rules! render { + (|$self:ident:$Struct:ident $(< + $($L:lifetime),* $($T:ident $(:$Trait:path)?),* + >)?, $to:ident | $render:expr) => { + impl <$($($L),*)? E: Output, $($T$(:$Trait)?),*> Content + for $Struct $(<$($L),* $($T),*>>)? { + fn render (&$self, $to: &mut E) { $render } } }; - // Implement for specific [Output]. ($Output:ty:| $self:ident: - $Struct:ident$(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)? - |$content:expr) => { + $Struct:ident $(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)?, $to:ident + |$render:expr) => { impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output> for $Struct $(<$($($L)? $($T)?),+>)? { - fn content (&$self) -> impl Render<$Output> + '_ { $content } + fn render (&$self, $to: &mut $Output) { $render } } }; } diff --git a/output/src/output_render.rs b/output/src/output_render.rs index d757597..3027b0c 100644 --- a/output/src/output_render.rs +++ b/output/src/output_render.rs @@ -2,86 +2,46 @@ use crate::*; /// Renderable with dynamic dispatch. pub trait Render { + /// Compute layout. + fn layout (&self, area: E::Area) -> E::Area; /// Write data to display. fn render (&self, output: &mut E); - /// Compute layout. - fn layout (&self, area: E::Area) -> E::Area { area } - + /// Perform type erasure, turning `self` into an opaque [RenderBox]. fn boxed <'a> (self) -> Box + 'a> where Self: Sized + 'a { Box::new(self) as Box + 'a> } - fn rc <'a> (self) -> Rc + 'a> where Self: Sized + 'a { - Rc::new(self) as Rc + 'a> - } } -impl Render for () { - fn render (&self, _: &mut E) {} +/// Every [Content] is also a [Render]. +/// However, the converse does not hold true. +/// Instead, the [Content::content] method returns an +/// opaque [Render] pointer. +impl> Render for C { + fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) } + fn render (&self, output: &mut E) { Content::render(self, output) } } -impl Render for fn(&mut E) { - fn render (&self, output: &mut E) { - self(output) - } -} +/// Opaque pointer to a renderable living on the heap. +/// +/// Return this from [Content::content] to use dynamic dispatch. +pub type RenderBox = Box>; -impl<'x, E: Output> Render for &(dyn Render + 'x) { - fn render (&self, output: &mut E) { - (*self).render(output) - } -} - -impl> Render for Box { - fn render (&self, output: &mut E) { - (**self).render(output) - } -} - -impl> Render for Option { - fn render (&self, output: &mut E) { - if let Some(render) = self { - render.render(output) - } - } -} - -impl> Render for &R { - fn render (&self, output: &mut E) { - (*self).render(output) - } -} - -impl> Render for &mut R { - fn render (&self, output: &mut E) { - (**self).render(output) - } -} - -impl> Render for [R] { - fn render (&self, output: &mut E) { - for render in self.iter() { - render.render(output) - } - } -} - -/// Implement custom rendering for a struct. -#[macro_export] macro_rules! render { - (|$self:ident:$Struct:ident $(< - $($L:lifetime),* $($T:ident $(:$Trait:path)?),* - >)?, $to:ident | $render:expr) => { - impl <$($($L),*)? E: Output, $($T$(:$Trait)?),*> Render - for $Struct $(<$($L),* $($T),*>>)? { - fn render (&$self, $to: &mut E) { $render } +/// Implement [Content] with composable content for a struct. +#[macro_export] macro_rules! content { + // Implement for all [Output]s. + (|$self:ident:$Struct:ty| $content:expr) => { + impl Content for $Struct { + fn content (&$self) -> impl Render + '_ { Some($content) } } }; + // Implement for specific [Output]. ($Output:ty:| $self:ident: - $Struct:ident $(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)?, $to:ident - |$render:expr) => { - impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Render<$Output> + $Struct:ident$(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)? + |$content:expr) => { + impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output> for $Struct $(<$($($L)? $($T)?),+>)? { - fn render (&$self, $to: &mut $Output) { $render } + fn content (&$self) -> impl Render<$Output> + '_ { $content } } }; } diff --git a/output/src/output_thunk.rs b/output/src/output_thunk.rs index f505c38..5a2cb5c 100644 --- a/output/src/output_thunk.rs +++ b/output/src/output_thunk.rs @@ -11,19 +11,65 @@ impl, F: Fn()->T> Thunk { } } impl, F: Fn()->T> Content for Thunk { - fn content (&self) -> Option> { - Some((self.1)()) + fn content (&self) -> impl Render { (self.1)() } +} + +/// Lazily-evaluated [Render]able with +/// mandatory stack allocation and dynamic dispatch. +pub struct ThunkBox( + PhantomData, + BoxBox>>, +); +impl ThunkBox { + pub const fn new (thunk: BoxBox>>) -> Self { + Self(PhantomData, thunk) } } +impl Content for ThunkBox { + fn content (&self) -> impl Render { (&self.1)() } +} +impl FromBox>>> for ThunkBox { + fn from (f: BoxBox>>) -> Self { + Self(PhantomData, f) + } +} + +//impl<'a, E: Output, F: Fn()->Box + 'a> + 'a> From for ThunkBox<'a, E> { + //fn from (f: F) -> Self { + //Self(Default::default(), Box::new(f)) + //} +//} pub struct ThunkRender(PhantomData, F); impl ThunkRender { pub fn new (render: F) -> Self { Self(PhantomData, render) } } -impl Render for ThunkRender { +impl Content for ThunkRender { fn render (&self, to: &mut E) { (self.1)(to) } } +pub struct ThunkLayout< + E: Output, + F1: Fn(E::Area)->E::Area, + F2: Fn(&mut E) +>( + PhantomData, + F1, + F2 +); +implE::Area, F2: Fn(&mut E)> ThunkLayout { + pub fn new (layout: F1, render: F2) -> Self { Self(PhantomData, layout, render) } +} +impl Content for ThunkLayout +where + E: Output, + F1: Fn(E::Area)->E::Area, + F2: Fn(&mut E) +{ + fn layout (&self, to: E::Area) -> E::Area { (self.1)(to) } + fn render (&self, to: &mut E) { (self.2)(to) } +} + #[derive(Debug, Default)] pub struct Memo { pub value: T, pub view: Arc> diff --git a/output/src/space.rs b/output/src/space.rs new file mode 100644 index 0000000..3e850fd --- /dev/null +++ b/output/src/space.rs @@ -0,0 +1,281 @@ +use crate::*; +use Direction::*; + +/// 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()) + } + } +} + +/// 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) + } +} + +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] } +} + +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() + } +} + +/// 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_measure.rs b/output/src/space_measure.rs index c2ca936..8279c9d 100644 --- a/output/src/space_measure.rs +++ b/output/src/space_measure.rs @@ -16,7 +16,7 @@ impl PartialEq for Measure { } // TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small -impl Render for Measure { +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); diff --git a/output/src/test.rs b/output/src/test.rs index 6bd653b..d324664 100644 --- a/output/src/test.rs +++ b/output/src/test.rs @@ -146,7 +146,7 @@ proptest! { () } } - impl Render for String { + impl Content for String { fn render (&self, to: &mut TestOutput) { to.area_mut().set_w(self.len() as u16); } @@ -162,7 +162,7 @@ proptest! { #[test] fn test_iter_map () { struct Foo; impl Content for Foo {} - fn _make_map + Send + Sync> (data: &Vec) -> impl Render { + fn _make_map + Send + Sync> (data: &Vec) -> impl Content { Map::new(||data.iter(), |_foo, _index|{}) } let _data = vec![Foo, Foo, Foo]; diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 9deff9e..bc39ee5 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -106,7 +106,7 @@ pub(crate) fn write_quote_to (out: &mut TokenStream2, quote: TokenStream2) { //#[tengri_proc::view(SomeOutput)] //impl SomeView { //#[tengri::view(":view-1")] - //fn view_1 (&self) -> impl Render + use<'_> { + //fn view_1 (&self) -> impl Content + use<'_> { //"view-1" //} //} @@ -114,7 +114,7 @@ pub(crate) fn write_quote_to (out: &mut TokenStream2, quote: TokenStream2) { //let written = quote! { #parsed }; //assert_eq!(format!("{written}"), format!("{}", quote! { //impl SomeView { - //fn view_1 (&self) -> impl Render + use<'_> { + //fn view_1 (&self) -> impl Content + use<'_> { //"view-1" //} //} diff --git a/tui/examples/tui.rs b/tui/examples/tui.rs index 677f0da..1d0124f 100644 --- a/tui/examples/tui.rs +++ b/tui/examples/tui.rs @@ -76,25 +76,25 @@ content!(TuiOut: |self: Example|{ //#[tengri_proc::view(TuiOut)] //impl Example { - //pub fn title (&self) -> impl Render + use<'_> { + //pub fn title (&self) -> impl Content + use<'_> { //Tui::bg(Color::Rgb(60, 10, 10), Push::y(1, Align::n(format!("Example {}/{}:", self.0 + 1, EXAMPLES.len())))).boxed() //} - //pub fn code (&self) -> impl Render + use<'_> { + //pub fn code (&self) -> impl Content + use<'_> { //Tui::bg(Color::Rgb(10, 60, 10), Push::y(2, Align::n(format!("{}", EXAMPLES[self.0])))).boxed() //} - //pub fn hello (&self) -> impl Render + use<'_> { + //pub fn hello (&self) -> impl Content + use<'_> { //Tui::bg(Color::Rgb(10, 100, 10), "Hello").boxed() //} - //pub fn world (&self) -> impl Render + use<'_> { + //pub fn world (&self) -> impl Content + use<'_> { //Tui::bg(Color::Rgb(100, 10, 10), "world").boxed() //} - //pub fn hello_world (&self) -> impl Render + use<'_> { + //pub fn hello_world (&self) -> impl Content + use<'_> { //"Hello world!".boxed() //} - //pub fn map_e (&self) -> impl Render + use<'_> { + //pub fn map_e (&self) -> impl Content + use<'_> { //Map::east(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed() //} - //pub fn map_s (&self) -> impl Render + use<'_> { + //pub fn map_s (&self) -> impl Content + use<'_> { //Map::south(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed() //} //} diff --git a/tui/src/tui_content.rs b/tui/src/tui_content.rs index 1eef3cc..9fc0c13 100644 --- a/tui/src/tui_content.rs +++ b/tui/src/tui_content.rs @@ -5,7 +5,7 @@ macro_rules! impl_content_layout_render { layout = $layout:expr; render = $render:expr) => { - impl Render<$Output> for $Struct { + impl Content<$Output> for $Struct { fn layout (&$self, $to: [u16;4]) -> [u16;4] { $layout } fn render (&$self, $to: &mut $Output) { $render } } @@ -25,19 +25,19 @@ mod tui_string; pub use self::tui_string::*; mod tui_style; pub use self::tui_style::*; mod tui_tryptich; pub use self::tui_tryptich::*; -impl> Render for std::sync::Arc { +impl> Content for std::sync::Arc { fn layout (&self, to: [u16;4]) -> [u16;4] { - Render::::layout(&**self, to) + Content::::layout(&**self, to) } fn render (&self, to: &mut TuiOut) { - Render::::render(&**self, to) + Content::::render(&**self, to) } } -impl> Content for Result> { - fn content (&self) -> Option + '_> { - Some(Bsp::a(self.as_ref().ok(), self.as_ref().err() - .map(|e|Tui::fg_bg(Color::Rgb(255,255,255), Color::Rgb(32,32,32), e.to_string())))) +impl> Content for Result> { + fn content (&self) -> impl Render { + Bsp::a(self.as_ref().ok(), self.as_ref().err() + .map(|e|Tui::fg_bg(Color::Rgb(255,255,255), Color::Rgb(32,32,32), e.to_string()))) } } diff --git a/tui/src/tui_content/_tui_focus.rs b/tui/src/tui_content/_tui_focus.rs index f3eba0e..cb1be40 100644 --- a/tui/src/tui_content/_tui_focus.rs +++ b/tui/src/tui_content/_tui_focus.rs @@ -243,7 +243,7 @@ impl FocusOrder for T { } pub trait FocusWrap { - fn wrap > (self, focus: T, content: &'_ W) -> impl Render + '_; + fn wrap > (self, focus: T, content: &'_ W) -> impl Content + '_; } pub fn to_focus_command (input: &TuiIn) -> Option> { diff --git a/tui/src/tui_content/tui_border.rs b/tui/src/tui_content/tui_border.rs index 62f2252..124b7cb 100644 --- a/tui/src/tui_content/tui_border.rs +++ b/tui/src/tui_content/tui_border.rs @@ -1,49 +1,40 @@ use crate::*; -pub struct Bordered(pub bool, pub S, pub W); -impl> Content for Bordered { - fn content (&self) -> Option + '_> { - Some(Fill::xy( - lay!(When::new(self.0, Border(self.0, self.1)), Padding::xy(1, 1, &self.2)) - )) - } -} +pub struct Bordered>(pub bool, pub S, pub W); +content!(TuiOut: |self: Bordered>|Fill::xy( + lay!(When::new(self.0, Border(self.0, self.1)), Padding::xy(1, 1, &self.2)) +)); pub struct Border(pub bool, pub S); -impl Render for Border { - fn layout (&self, area: [u16;4]) -> [u16;4] { - self.1.layout(area) - } - fn render (&self, to: &mut TuiOut) { - if self.0 { - let area = to.area(); - if area.w() > 0 && area.y() > 0 { - to.blit(&self.1.nw(), area.x(), area.y(), self.1.style()); - to.blit(&self.1.ne(), area.x() + area.w() - 1, area.y(), self.1.style()); - to.blit(&self.1.sw(), area.x(), area.y() + area.h() - 1, self.1.style()); - to.blit(&self.1.se(), area.x() + area.w() - 1, area.y() + area.h() - 1, self.1.style()); - for x in area.x()+1..area.x()+area.w()-1 { - to.blit(&self.1.n(), x, area.y(), self.1.style()); - to.blit(&self.1.s(), x, area.y() + area.h() - 1, self.1.style()); - } - for y in area.y()+1..area.y()+area.h()-1 { - to.blit(&self.1.w(), area.x(), y, self.1.style()); - to.blit(&self.1.e(), area.x() + area.w() - 1, y, self.1.style()); - } +render!(TuiOut: |self: Border, to| { + if self.0 { + let area = to.area(); + if area.w() > 0 && area.y() > 0 { + to.blit(&self.1.nw(), area.x(), area.y(), self.1.style()); + to.blit(&self.1.ne(), area.x() + area.w() - 1, area.y(), self.1.style()); + to.blit(&self.1.sw(), area.x(), area.y() + area.h() - 1, self.1.style()); + to.blit(&self.1.se(), area.x() + area.w() - 1, area.y() + area.h() - 1, self.1.style()); + for x in area.x()+1..area.x()+area.w()-1 { + to.blit(&self.1.n(), x, area.y(), self.1.style()); + to.blit(&self.1.s(), x, area.y() + area.h() - 1, self.1.style()); + } + for y in area.y()+1..area.y()+area.h()-1 { + to.blit(&self.1.w(), area.x(), y, self.1.style()); + to.blit(&self.1.e(), area.x() + area.w() - 1, y, self.1.style()); } } } -} +}); -pub trait BorderStyle: Render + Copy { +pub trait BorderStyle: Send + Sync + Copy { fn enabled (&self) -> bool; - fn enclose > (self, w: W) -> impl Render { + fn enclose > (self, w: W) -> impl Content { Bsp::b(Fill::xy(Border(self.enabled(), self)), w) } - fn enclose2 > (self, w: W) -> impl Render { + fn enclose2 > (self, w: W) -> impl Content { Bsp::b(Margin::xy(1, 1, Fill::xy(Border(self.enabled(), self))), w) } - fn enclose_bg > (self, w: W) -> impl Render { + fn enclose_bg > (self, w: W) -> impl Content { Tui::bg(self.style().unwrap().bg.unwrap_or(Color::Reset), Bsp::b(Fill::xy(Border(self.enabled(), self)), w)) } @@ -147,7 +138,7 @@ macro_rules! border { fn enabled (&self) -> bool { self.0 } } #[derive(Copy, Clone)] pub struct $T(pub bool, pub Style); - impl Render for $T { + impl Content for $T { fn render (&self, to: &mut TuiOut) { if self.enabled() { let _ = self.draw(to); } } diff --git a/tui/src/tui_content/tui_error.rs b/tui/src/tui_content/tui_error.rs index 9ec7773..e48f203 100644 --- a/tui/src/tui_content/tui_error.rs +++ b/tui/src/tui_content/tui_error.rs @@ -2,26 +2,23 @@ use crate::*; use ratatui::style::Stylize; // Thunks can be natural error boundaries! -pub struct ErrorBoundary>( - std::marker::PhantomData, Perhaps -); +pub struct ErrorBoundary>(std::marker::PhantomData, Perhaps); -impl> ErrorBoundary { +impl> ErrorBoundary { pub fn new (content: Perhaps) -> Self { Self(Default::default(), content) } } -impl> Render for ErrorBoundary { - fn render (&self, to: &mut TuiOut) { - match self.1.as_ref() { +impl> Content for ErrorBoundary { + fn content (&self) -> impl Render + '_ { + ThunkRender::new(|to|match self.1.as_ref() { Ok(Some(content)) => content.render(to), Ok(None) => to.blit(&"empty?", 0, 0, Some(Style::default().yellow())), - Err(e) => Tui::fg_bg( + Err(e) => Content::render(&Tui::fg_bg( Color::Rgb(255,224,244), Color::Rgb(96,24,24), Bsp::s( Bsp::e(Tui::bold(true, "oops. "), "rendering failed."), - Bsp::e("\"why?\" ", Tui::bold(true, &format!("{e}")))) - ).render(to) - } + Bsp::e("\"why?\" ", Tui::bold(true, &format!("{e}"))))), to) + }) } } diff --git a/tui/src/tui_content/tui_field.rs b/tui/src/tui_content/tui_field.rs index e0161fd..3e47e9f 100644 --- a/tui/src/tui_content/tui_field.rs +++ b/tui/src/tui_content/tui_field.rs @@ -1,30 +1,30 @@ use crate::*; pub struct FieldH(pub ItemTheme, pub T, pub U); -impl, U: Render> Content for FieldH { - fn content (&self) -> Option + '_> { +impl, U: Content> Content for FieldH { + fn content (&self) -> impl Render { let Self(ItemTheme { darkest, dark, lightest, .. }, title, value) = self; - Some(row!( + row!( Tui::fg_bg(dark.rgb, darkest.rgb, "▐"), Tui::fg_bg(lightest.rgb, dark.rgb, title), Tui::fg_bg(dark.rgb, darkest.rgb, "▌"), Tui::fg_bg(lightest.rgb, darkest.rgb, Tui::bold(true, value)), - )) + ) } } pub struct FieldV(pub ItemTheme, pub T, pub U); -impl, U: Render> Content for FieldV { - fn content (&self) -> Option + '_> { +impl, U: Content> Content for FieldV { + fn content (&self) -> impl Render { let Self(ItemTheme { darkest, dark, lightest, .. }, title, value) = self; - Some(Bsp::n( + Bsp::n( Align::w(Tui::bg(darkest.rgb, Tui::fg(lightest.rgb, Tui::bold(true, value)))), Fill::x(Align::w(row!( Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "▐")), Tui::bg(dark.rgb, Tui::fg(lightest.rgb, title)), Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "▌")), ))) - )) + ) } } @@ -40,9 +40,9 @@ pub struct Field { pub value_bg: Option, pub value_align: Option, } -impl, U: Render> Content for Field { - fn content (&self) -> Option + '_> { - Some("TODO") +impl, U: Content> Content for Field { + fn content (&self) -> impl Render { + "TODO" } } diff --git a/tui/src/tui_content/tui_number.rs b/tui/src/tui_content/tui_number.rs index 9e8eea1..46c4ef1 100644 --- a/tui/src/tui_content/tui_number.rs +++ b/tui/src/tui_content/tui_number.rs @@ -1,13 +1,5 @@ use crate::*; -impl Render for u64 { - fn render (&self, _to: &mut TuiOut) { - todo!() - } -} +render!(TuiOut: |self: u64, _to|todo!()); -impl Render for f64 { - fn render (&self, _to: &mut TuiOut) { - todo!() - } -} +render!(TuiOut: |self: f64, _to|todo!()); diff --git a/tui/src/tui_content/tui_phat.rs b/tui/src/tui_content/tui_phat.rs index 1117636..01a852f 100644 --- a/tui/src/tui_content/tui_phat.rs +++ b/tui/src/tui_content/tui_phat.rs @@ -12,24 +12,24 @@ impl Phat { pub const LO: &'static str = "▄"; pub const HI: &'static str = "▀"; /// A phat line - pub fn lo (fg: Color, bg: Color) -> impl Render { + pub fn lo (fg: Color, bg: Color) -> impl Content { Fixed::y(1, Tui::fg_bg(fg, bg, RepeatH(Self::LO))) } /// A phat line - pub fn hi (fg: Color, bg: Color) -> impl Render { + pub fn hi (fg: Color, bg: Color) -> impl Content { Fixed::y(1, Tui::fg_bg(fg, bg, RepeatH(Self::HI))) } } -impl> Content for Phat { - fn content (&self) -> Option + '_> { +impl> Content for Phat { + fn content (&self) -> impl Render { let [fg, bg, hi, lo] = self.colors; let top = Fixed::y(1, Self::lo(bg, hi)); let low = Fixed::y(1, Self::hi(bg, lo)); let content = Tui::fg_bg(fg, bg, &self.content); - Some(Min::xy( + Min::xy( self.width, self.height, Bsp::s(top, Bsp::n(low, Fill::xy(content))) - )) + ) } } diff --git a/tui/src/tui_content/tui_repeat.rs b/tui/src/tui_content/tui_repeat.rs index 03e2209..ecd2d40 100644 --- a/tui/src/tui_content/tui_repeat.rs +++ b/tui/src/tui_content/tui_repeat.rs @@ -2,10 +2,8 @@ use crate::*; use ratatui::prelude::Position; pub struct Repeat<'a>(pub &'a str); -impl Render for Repeat<'_> { - fn layout (&self, to: [u16;4]) -> [u16;4] { - to - } +impl Content for Repeat<'_> { + fn layout (&self, to: [u16;4]) -> [u16;4] { to } fn render (&self, to: &mut TuiOut) { let [x, y, w, h] = to.area().xywh(); let a = self.0.len(); @@ -21,10 +19,8 @@ impl Render for Repeat<'_> { } pub struct RepeatV<'a>(pub &'a str); -impl Render for RepeatV<'_> { - fn layout (&self, to: [u16;4]) -> [u16;4] { - to - } +impl Content for RepeatV<'_> { + fn layout (&self, to: [u16;4]) -> [u16;4] { to } fn render (&self, to: &mut TuiOut) { let [x, y, _w, h] = to.area().xywh(); for y in y..y+h { @@ -36,10 +32,8 @@ impl Render for RepeatV<'_> { } pub struct RepeatH<'a>(pub &'a str); -impl Render for RepeatH<'_> { - fn layout (&self, to: [u16;4]) -> [u16;4] { - to - } +impl Content for RepeatH<'_> { + fn layout (&self, to: [u16;4]) -> [u16;4] { to } fn render (&self, to: &mut TuiOut) { let [x, y, w, _h] = to.area().xywh(); for x in x..x+w { diff --git a/tui/src/tui_content/tui_scroll.rs b/tui/src/tui_content/tui_scroll.rs index 5f78c1d..fe8bf3a 100644 --- a/tui/src/tui_content/tui_scroll.rs +++ b/tui/src/tui_content/tui_scroll.rs @@ -23,7 +23,7 @@ impl ScrollbarH { const ICON_INC: &[char] = &[' ', '🞂', ' ']; } -impl Render for ScrollbarV { +impl Content for ScrollbarV { fn render (&self, to: &mut TuiOut) { let [x, y1, _w, h] = to.area().xywh(); let y2 = y1 + h; @@ -51,7 +51,7 @@ impl Render for ScrollbarV { } } -impl Render for ScrollbarH { +impl Content for ScrollbarH { fn render (&self, to: &mut TuiOut) { let [x1, y, w, _h] = to.area().xywh(); let x2 = x1 + w; diff --git a/tui/src/tui_content/tui_string.rs b/tui/src/tui_content/tui_string.rs index 0163dc2..57ae7f4 100644 --- a/tui/src/tui_content/tui_string.rs +++ b/tui/src/tui_content/tui_string.rs @@ -4,24 +4,24 @@ use unicode_width::{UnicodeWidthStr, UnicodeWidthChar}; impl_content_layout_render!(TuiOut: |self: &str, to| layout = to.center_xy([width_chars_max(to.w(), self), 1]); - render = {let [x, y, w, ..] = Render::layout(self, to.area()); + render = {let [x, y, w, ..] = Content::layout(self, to.area()); to.text(self, x, y, w)}); impl_content_layout_render!(TuiOut: |self: String, to| - layout = Render::::layout(&self.as_str(), to); - render = Render::::render(&self.as_str(), to)); + layout = Content::::layout(&self.as_str(), to); + render = Content::::render(&self.as_str(), to)); impl_content_layout_render!(TuiOut: |self: Arc, to| - layout = Render::::layout(&self.as_ref(), to); - render = Render::::render(&self.as_ref(), to)); + layout = Content::::layout(&self.as_ref(), to); + render = Content::::render(&self.as_ref(), to)); impl_content_layout_render!(TuiOut: |self: std::sync::RwLock, to| - layout = Render::::layout(&self.read().unwrap(), to); - render = Render::::render(&self.read().unwrap(), to)); + layout = Content::::layout(&self.read().unwrap(), to); + render = Content::::render(&self.read().unwrap(), to)); impl_content_layout_render!(TuiOut: |self: std::sync::RwLockReadGuard<'_, String>, to| - layout = Render::::layout(&**self, to); - render = Render::::render(&**self, to)); + layout = Content::::layout(&**self, to); + render = Content::::render(&**self, to)); fn width_chars_max (max: u16, text: impl AsRef) -> u16 { let mut width: u16 = 0; @@ -61,12 +61,12 @@ impl<'a, T: AsRef> TrimString { TrimStringRef(self.0, &self.1) } } -impl<'a, T: AsRef> Render for TrimString { +impl<'a, T: AsRef> Content for TrimString { fn layout (&self, to: [u16; 4]) -> [u16;4] { - Render::layout(&self.as_ref(), to) + Content::layout(&self.as_ref(), to) } fn render (&self, to: &mut TuiOut) { - Render::render(&self.as_ref(), to) + Content::render(&self.as_ref(), to) } } @@ -75,7 +75,7 @@ impl<'a, T: AsRef> Render for TrimString { /// Width is computed using [unicode_width]. pub struct TrimStringRef<'a, T: AsRef>(pub u16, pub &'a T); -impl> Render for TrimStringRef<'_, T> { +impl> Content for TrimStringRef<'_, T> { fn layout (&self, to: [u16; 4]) -> [u16;4] { [to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h()] } diff --git a/tui/src/tui_content/tui_style.rs b/tui/src/tui_content/tui_style.rs index 3673f53..10d79d4 100644 --- a/tui/src/tui_content/tui_style.rs +++ b/tui/src/tui_content/tui_style.rs @@ -1,70 +1,60 @@ use crate::*; pub trait TuiStyle { - fn fg > (color: Color, w: R) -> Foreground { + fn fg > (color: Color, w: R) -> Foreground { Foreground(color, w) } - fn bg > (color: Color, w: R) -> Background { + fn bg > (color: Color, w: R) -> Background { Background(color, w) } - fn fg_bg > (fg: Color, bg: Color, w: R) -> Background> { + fn fg_bg > (fg: Color, bg: Color, w: R) -> Background> { Background(bg, Foreground(fg, w)) } - fn modify > (enable: bool, modifier: Modifier, w: R) -> Modify { + fn modify > (enable: bool, modifier: Modifier, w: R) -> Modify { Modify(enable, modifier, w) } - fn bold > (enable: bool, w: R) -> Modify { + fn bold > (enable: bool, w: R) -> Modify { Self::modify(enable, Modifier::BOLD, w) } - fn border , S: BorderStyle> (enable: bool, style: S, w: R) -> Bordered { + fn border , S: BorderStyle> (enable: bool, style: S, w: R) -> Bordered { Bordered(enable, style, w) } } impl TuiStyle for Tui {} -pub struct Foreground(pub Color, pub R); -impl> Render for Foreground { - fn layout (&self, to: [u16;4]) -> [u16;4] { - self.1.layout(to) - } +pub struct Foreground>(pub Color, pub R); +impl> Content for Foreground { + fn content (&self) -> impl Render { &self.1 } fn render (&self, to: &mut TuiOut) { - let area = self.layout(to.area()); - to.fill_fg(area, self.0); - to.place(area, &self.1); + to.fill_fg(to.area(), self.0); + self.1.render(to) } } -pub struct Background(pub Color, pub R); -impl> Render for Background { - fn layout (&self, to: [u16;4]) -> [u16;4] { - self.1.layout(to) - } +pub struct Background>(pub Color, pub R); +impl> Content for Background { + fn content (&self) -> impl Render { &self.1 } fn render (&self, to: &mut TuiOut) { - let area = self.layout(to.area()); - to.fill_bg(area, self.0); - to.place(area, &self.1); + to.fill_bg(to.area(), self.0); + self.1.render(to) } } -pub struct Modify>(pub bool, pub Modifier, pub R); -impl> Render for Modify { - fn layout (&self, to: [u16;4]) -> [u16;4] { - self.2.layout(to) - } +pub struct Modify>(pub bool, pub Modifier, pub R); +impl> Content for Modify { + fn content (&self) -> impl Render { &self.2 } fn render (&self, to: &mut TuiOut) { to.fill_mod(to.area(), self.0, self.1); self.2.render(to) } } -pub struct Styled>(pub Option