diff --git a/src/draw.rs b/src/draw.rs index 67ee223..29dbac7 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,85 +1,17 @@ use crate::*; pub use crate::space::*; -/// Implement the [Draw] trait for a particular drawable and [Screen]. -/// -/// ``` -/// use tengri::{*, draw::*, term::*}; -/// struct MyDrawable; -/// impl_draw!(|self: MyDrawable, to: Tui|{ -/// todo!() -/// }); -/// ``` -#[macro_export] macro_rules! impl_draw ((| - $self:ident:$Self:ty, $to:ident:$To:ty -|$draw:block)=>{ impl Draw<$To> for $Self { - fn draw ($self, $to: &mut $To) -> Usually> $draw -} }); +mod draw; +pub use self::draw::*; -/// Drawable that supports dynamic dispatch. -/// -/// Drawables are composable, e.g. the [when] and [either] conditionals -/// or the layout constraints. -/// -/// Drawables are consumable, i.e. the [Draw::draw] method receives an -/// owned `self` and does not return it, consuming the drawable. -/// -/// To draw a thing multiple times, instead of explicitly constructing it -/// every time, implement the [View] trait instead, which will construct -/// a [Draw]able. -/// -/// ``` -/// use tengri::{*, draw::*}; -/// struct TestScreen(bool); -/// impl Screen for TestScreen { type Unit = u16; } -/// impl X for TestScreen { fn x (&self) -> u16 { 0 } } -/// impl Y for TestScreen { fn y (&self) -> u16 { 0 } } -/// struct TestWidget(bool); -/// impl Draw for TestWidget { -/// fn draw (self, screen: &mut TestScreen) -> Usually> { -/// screen.0 |= self.0; -/// } -/// } -/// let mut screen = TestScreen(false); -/// TestWidget(false).draw(&mut screen); -/// TestWidget(true).draw(&mut screen); -/// TestWidget(false).draw(&mut screen); -/// ``` -pub trait Draw { - fn draw (self, to: &mut T) -> Usually>; -} -impl> Draw for &D { - fn draw (self, __: &mut T) -> Usually> { - todo!() - } -} -impl> Draw for Option { - fn draw (self, __: &mut T) -> Usually> { - todo!() - } -} +mod view; +pub use self::view::*; -/// Emit a [Draw]able. -/// -/// Speculative. How to avoid conflicts with [Draw] proper? -pub trait View { - fn view (&self) -> impl Draw; -} +mod thunk; +pub use self::thunk::*; -pub const fn thunk Usually>> (draw: F) -> Thunk { - Thunk(draw, std::marker::PhantomData) -} - -/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. -pub struct ThunkUsually>>( - pub F, - std::marker::PhantomData -); -implUsually>> Draw for Thunk { - fn draw (self, to: &mut T) -> Usually> { - (self.0)(to) - } -} +mod screen; +pub use self::screen::*; /// Only render when condition is true. /// @@ -104,36 +36,3 @@ pub const fn when (condition: bool, draw: impl Draw) -> impl Draw pub const fn either (condition: bool, a: impl Draw, b: impl Draw) -> impl Draw { thunk(move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) }) } - -/// Output target. -/// -/// ``` -/// use tengri::{*, draw::*}; -/// -/// struct TestOut>(T); -/// -/// impl> Screen for TestOut { -/// type Unit = u16; -/// fn place_at + ?Sized> (&mut self, area: T, _: D) { -/// println!("placed: {area:?}"); -/// () -/// } -/// } -/// -/// impl_draw!(|self: String, to: TestOut|{ -/// to.area_mut().set_w(self.len() as u16); -/// }); -/// ``` -pub trait Screen: Space + Send + Sync + Sized { - type Unit: Coord; - /// Render drawable in area. - fn place <'t, T: Draw + ?Sized> (&mut self, content: &'t T) { - self.place_at(self.xywh(), content) - } - /// Render drawable in subarea specified by `area` - fn place_at <'t, T: Draw + ?Sized> ( - &mut self, _area: XYWH, _content: &'t T - ) { - unimplemented!() - } -} diff --git a/src/draw/draw.rs b/src/draw/draw.rs new file mode 100644 index 0000000..8701084 --- /dev/null +++ b/src/draw/draw.rs @@ -0,0 +1,60 @@ +use super::*; + +/// Implement the [Draw] trait for a particular drawable and [Screen]. +/// +/// ``` +/// use tengri::{*, draw::*, term::*}; +/// struct MyDrawable; +/// impl_draw!(|self: MyDrawable, to: Tui|{ +/// todo!() +/// }); +/// ``` +#[macro_export] macro_rules! impl_draw ((| + $self:ident:$Self:ty, $to:ident:$To:ty +|$draw:block)=>{ impl Draw<$To> for $Self { + fn draw ($self, $to: &mut $To) -> Usually> $draw +} }); + +/// Drawable that supports dynamic dispatch. +/// +/// Drawables are composable, e.g. the [when] and [either] conditionals +/// or the layout constraints. +/// +/// Drawables are consumable, i.e. the [Draw::draw] method receives an +/// owned `self` and does not return it, consuming the drawable. +/// +/// To draw a thing multiple times, instead of explicitly constructing it +/// every time, implement the [View] trait instead, which will construct +/// a [Draw]able. +/// +/// ``` +/// use tengri::{*, draw::*}; +/// struct MyScreen(bool); +/// impl Screen for MyScreen { type Unit = u16; } +/// impl X for MyScreen { fn x (&self) -> u16 { 0 } } +/// impl Y for MyScreen { fn y (&self) -> u16 { 0 } } +/// struct MyWidget(bool); +/// impl Draw for MyWidget { +/// fn draw (self, screen: &mut MyScreen) -> Usually> { +/// screen.0 |= self.0; +/// } +/// } +/// let mut screen = MyScreen(false); +/// MyWidget(false).draw(&mut screen); +/// MyWidget(true).draw(&mut screen); +/// MyWidget(false).draw(&mut screen); +/// ``` +pub trait Draw { + fn draw (self, to: &mut T) -> Usually>; +} +impl> Draw for &D { + fn draw (self, __: &mut T) -> Usually> { + todo!() + } +} +impl> Draw for Option { + fn draw (self, __: &mut T) -> Usually> { + todo!() + } +} + diff --git a/src/draw/screen.rs b/src/draw/screen.rs new file mode 100644 index 0000000..dc51744 --- /dev/null +++ b/src/draw/screen.rs @@ -0,0 +1,34 @@ +use super::*; + +/// Output target. +/// +/// ``` +/// use tengri::{*, draw::*}; +/// +/// struct TestOut>(T); +/// +/// impl> Screen for TestOut { +/// type Unit = u16; +/// fn place_at + ?Sized> (&mut self, area: T, _: D) { +/// println!("placed: {area:?}"); +/// () +/// } +/// } +/// +/// impl_draw!(|self: String, to: TestOut|{ +/// to.area_mut().set_w(self.len() as u16); +/// }); +/// ``` +pub trait Screen: Space + Send + Sync + Sized { + type Unit: Coord; + /// Render drawable in area. + fn place <'t, T: Draw + ?Sized> (&mut self, content: &'t T) { + self.place_at(self.xywh(), content) + } + /// Render drawable in subarea specified by `area` + fn place_at <'t, T: Draw + ?Sized> ( + &mut self, _area: XYWH, _content: &'t T + ) { + unimplemented!() + } +} diff --git a/src/draw/thunk.rs b/src/draw/thunk.rs new file mode 100644 index 0000000..9b1d4b5 --- /dev/null +++ b/src/draw/thunk.rs @@ -0,0 +1,16 @@ +use super::*; + +pub const fn thunk Usually>> (draw: F) -> Thunk { + Thunk(draw, std::marker::PhantomData) +} + +/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. +pub struct ThunkUsually>>( + pub F, + std::marker::PhantomData +); +implUsually>> Draw for Thunk { + fn draw (self, to: &mut T) -> Usually> { + (self.0)(to) + } +} diff --git a/src/draw/view.rs b/src/draw/view.rs new file mode 100644 index 0000000..82fe908 --- /dev/null +++ b/src/draw/view.rs @@ -0,0 +1,8 @@ +use super::*; + +/// Emit a [Draw]able. +/// +/// Speculative. How to avoid conflicts with [Draw] proper? +pub trait View { + fn view (&self) -> impl Draw; +} diff --git a/src/eval.rs b/src/eval.rs index c87eb56..4fc5934 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -172,11 +172,11 @@ pub fn eval_view <'a, O: Screen + 'a, S> ( /// # fn main () -> tengri::Usually<()> { /// let state = State; /// let mut out = Tui::default(); -/// tengri::eval_view_tui(&state, &mut out, "")?; -/// tengri::eval_view_tui(&state, &mut out, "text Hello world!")?; -/// tengri::eval_view_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?; -/// tengri::eval_view_tui(&state, &mut out, "bg (g 2) (text Hello world!)")?; -/// tengri::eval_view_tui(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?; +/// tengri::eval::eval_view_tui(&state, &mut out, "")?; +/// tengri::eval::eval_view_tui(&state, &mut out, "text Hello world!")?; +/// tengri::eval::eval_view_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?; +/// tengri::eval::eval_view_tui(&state, &mut out, "bg (g 2) (text Hello world!)")?; +/// tengri::eval::eval_view_tui(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?; /// # Ok(()) } /// ``` pub fn eval_view_tui <'a, S> ( diff --git a/src/sing.rs b/src/sing.rs index 7716129..cd76eb2 100644 --- a/src/sing.rs +++ b/src/sing.rs @@ -52,7 +52,7 @@ pub trait Audio { /// Event enum for JACK events. /// /// ``` -/// let event = tengri::JackEvent::XRun; +/// let event = tengri::sing::JackEvent::XRun; // kerpop /// ``` #[derive(Debug, Clone, PartialEq)] pub enum JackEvent { ThreadInit, @@ -70,7 +70,7 @@ pub trait Audio { /// Generic notification handler that emits [JackEvent] /// /// ``` -/// let notify = tengri::JackNotify(|_|{}); +/// let notify = tengri::sing::JackNotify(|_|{}); /// ``` pub struct JackNotify(pub T); diff --git a/src/space.rs b/src/space.rs index a2f7010..9a8650c 100644 --- a/src/space.rs +++ b/src/space.rs @@ -1,6 +1,9 @@ use crate::{*, draw::*}; #[cfg(test)] use proptest_derive::Arbitrary; +mod coord; +pub use self::coord::*; + mod xywh; pub use self::xywh::*; @@ -10,39 +13,11 @@ pub use self::origin::*; mod split; pub use self::split::*; -/// A numeric type that can be used as coordinate. -/// -/// FIXME: Replace with `num` crate? -/// FIXME: Use AsRef/AsMut? -/// -/// ``` -/// use tengri::draw::Coord; -/// let a: u16 = Coord::zero(); -/// let b: u16 = a.plus(1); -/// let c: u16 = a.minus(2); -/// let d = a.atomic(); -/// ``` -pub trait Coord: Send + Sync + Copy - + Add - + Sub - + Mul - + Div - + Ord + PartialEq + Eq - + Debug + Display + Default - + From + Into - + Into - + Into - + std::iter::Step -{ - /// Zero in own type. - fn zero () -> Self { 0.into() } - /// Addition. - fn plus (self, other: Self) -> Self; - /// Saturating subtraction. - fn minus (self, other: Self) -> Self { if self >= other { self - other } else { 0.into() } } - /// Convert to [AtomicUsize]. - fn atomic (self) -> AtomicUsize { AtomicUsize::new(self.into()) } -} +mod iter; +pub use self::iter::*; + +mod size; +pub use self::size::*; /// Horizontal axis. pub trait X { @@ -89,7 +64,7 @@ pub const fn w_exact (w: T::Unit, c: impl Draw) -> impl Draw { /// Shrink drawing area symmetrically. /// /// ``` -/// let padded = tengri::W(3).pad("Hello"); +/// let padded = tengri::space::w_pad(3, "Hello"); /// ``` pub const fn w_pad (x: T::Unit, draw: impl Draw) -> impl Draw { thunk(move|to: &mut T|draw.draw(todo!())) @@ -139,7 +114,7 @@ pub const fn h_exact (h: T::Unit, c: impl Draw) -> impl Draw { /// Shrink drawing area symmetrically. /// /// ``` -/// let padded = tengri::W::pad(3, "Hello"); +/// let padded = tengri::space::w_pad(3, "Hello"); /// ``` pub const fn h_pad (x: T::Unit, draw: impl Draw) -> impl Draw { thunk(move|to: &mut T|draw.draw(todo!())) @@ -170,7 +145,7 @@ pub const fn wh_pad (w: T::Unit, h: T::Unit, draw: impl Draw) /// Only draw content if area is above a certain size. /// /// ``` -/// let min = tengri::wh_min(3, 5, "Hello"); // 5x5 +/// let min = tengri::space::wh_min(3, 5, "Hello"); // 5x5 /// ``` pub const fn wh_min (w: Option, h: Option, draw: impl Draw) -> impl Draw @@ -181,7 +156,7 @@ pub const fn wh_min (w: Option, h: Option, draw: i /// Set the maximum width and/or height of the content. /// /// ``` -/// let max = tengri::wh_max(Some(3), Some(5), "Hello"); +/// let max = tengri::space::wh_max(Some(3), Some(5), "Hello"); /// ``` pub const fn wh_max (w: Option, h: Option, draw: impl Draw) -> impl Draw @@ -192,7 +167,7 @@ pub const fn wh_max (w: Option, h: Option, draw: i /// Set the maximum width and/or height of the content. /// /// ``` -/// let exact = tengri::wh_exact(Some(3), Some(5), "Hello"); +/// let exact = tengri::space::wh_exact(Some(3), Some(5), "Hello"); /// ``` pub const fn wh_exact (w: Option, h: Option, draw: impl Draw) -> impl Draw @@ -202,7 +177,7 @@ pub const fn wh_exact (w: Option, h: Option, draw: /// Limit size of drawing area /// ``` -/// let clipped = tengri::wh_clip(Some(3), Some(5), "Hello"); +/// let clipped = tengri::space::wh_clip(Some(3), Some(5), "Hello"); /// ``` pub const fn wh_clip ( w: Option, h: Option, draw: impl Draw @@ -213,68 +188,3 @@ pub const fn wh_clip ( pub const fn wh_full (a: impl Draw) -> impl Draw { a } pub const fn w_full (a: impl Draw) -> impl Draw { a } pub const fn h_full (a: impl Draw) -> impl Draw { a } - -/// Iterate over a collection of renderables: -/// -/// ``` -/// use tengri::draw::{Origin::*, Split::*}; -/// let _ = Below.iter([ -/// NW.align(W(15).max(W(10).min("Leftbar"))), -/// NE.align(W(12).max(W(10).min("Rightbar"))), -/// Center.align(W(40).max(H(20.max("Center")))) -/// ].iter(), |x|x); -/// ``` -pub fn iter < - S: Screen, - D: Draw, - V: Fn()->I, - I: Iterator, - F: Fn(&D)->dyn Draw, -> (_items: V, _cb: F) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) -} - -pub fn iter_north <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( - _iter: impl Fn()->I, _draw: impl Fn(D)->U, -) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) -} - -pub fn iter_east <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( - _iter: impl Fn()->I, _draw: impl Fn(D)->U, -) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) -} - -pub fn iter_south <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( - _iter: impl Fn()->I, _draw: impl Fn(D)->U, -) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) -} - -pub fn iter_west <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( - _iter: impl Fn()->I, _draw: impl Fn(D)->U, -) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) -} - -#[derive(Default, Debug)] -pub struct Size(AtomicUsize, AtomicUsize); -impl X for Size { - fn x (&self) -> u16 { 0 } - fn w (&self) -> u16 { self.0.load(Relaxed) as u16 } -} -impl Y for Size { - fn y (&self) -> u16 { 0 } - fn h (&self) -> u16 { self.1.load(Relaxed) as u16 } -} -impl Size { - pub const fn of (&self, of: impl Draw) -> impl Draw { - thunk(move|to: &mut T|{ - let area = of.draw(to)?; - self.0.store(area.w().into(), Relaxed); - self.1.store(area.h().into(), Relaxed); - Ok(area) - }) - } -} diff --git a/src/space/coord.rs b/src/space/coord.rs new file mode 100644 index 0000000..f5e6974 --- /dev/null +++ b/src/space/coord.rs @@ -0,0 +1,35 @@ +use super::*; + +/// A numeric type that can be used as coordinate. +/// +/// FIXME: Replace with `num` crate? +/// FIXME: Use AsRef/AsMut? +/// +/// ``` +/// use tengri::draw::Coord; +/// let a: u16 = Coord::zero(); +/// let b: u16 = a.plus(1); +/// let c: u16 = a.minus(2); +/// let d = a.atomic(); +/// ``` +pub trait Coord: Send + Sync + Copy + + Add + + Sub + + Mul + + Div + + Ord + PartialEq + Eq + + Debug + Display + Default + + From + Into + + Into + + Into + + std::iter::Step +{ + /// Zero in own type. + fn zero () -> Self { 0.into() } + /// Addition. + fn plus (self, other: Self) -> Self; + /// Saturating subtraction. + fn minus (self, other: Self) -> Self { if self >= other { self - other } else { 0.into() } } + /// Convert to [AtomicUsize]. + fn atomic (self) -> AtomicUsize { AtomicUsize::new(self.into()) } +} diff --git a/src/space/iter.rs b/src/space/iter.rs new file mode 100644 index 0000000..4bcfde8 --- /dev/null +++ b/src/space/iter.rs @@ -0,0 +1,46 @@ +use super::*; + +/// Iterate over a collection of renderables: +/// +/// ``` +/// use tengri::draw::{Origin::*, Split::*}; +/// let _ = Below.iter([ +/// NW.align(W(15).max(W(10).min("Leftbar"))), +/// NE.align(W(12).max(W(10).min("Rightbar"))), +/// Center.align(W(40).max(H(20.max("Center")))) +/// ].iter(), |x|x); +/// ``` +pub fn iter < + S: Screen, + D: Draw, + V: Fn()->I, + I: Iterator, + F: Fn(&D)->dyn Draw, +> (_items: V, _cb: F) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) +} + +pub fn iter_north <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( + _iter: impl Fn()->I, _draw: impl Fn(D)->U, +) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) +} + +pub fn iter_east <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( + _iter: impl Fn()->I, _draw: impl Fn(D)->U, +) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) +} + +pub fn iter_south <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( + _iter: impl Fn()->I, _draw: impl Fn(D)->U, +) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) +} + +pub fn iter_west <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( + _iter: impl Fn()->I, _draw: impl Fn(D)->U, +) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) +} + diff --git a/src/space/origin.rs b/src/space/origin.rs index 256a7f6..f3f7acc 100644 --- a/src/space/origin.rs +++ b/src/space/origin.rs @@ -73,7 +73,7 @@ impl> Draw for Anchor { /// /// ``` /// use tengri::draw::Origin; -/// let _ = Origin::NW.align(()) +/// let _ = Origin::NW.align(()); /// ``` #[cfg_attr(test, derive(Arbitrary))] #[derive(Debug, Copy, Clone, Default)] pub enum Origin { diff --git a/src/space/size.rs b/src/space/size.rs new file mode 100644 index 0000000..add71ae --- /dev/null +++ b/src/space/size.rs @@ -0,0 +1,28 @@ +use super::*; + +/// Uses [AtomicUsize] to measure size during\ +/// rendering (which is normally read-only). +#[derive(Default, Debug)] +pub struct Size(AtomicUsize, AtomicUsize); + +impl X for Size { + fn x (&self) -> u16 { 0 } + fn w (&self) -> u16 { self.0.load(Relaxed) as u16 } +} + +impl Y for Size { + fn y (&self) -> u16 { 0 } + fn h (&self) -> u16 { self.1.load(Relaxed) as u16 } +} + +impl Size { + pub const fn of (&self, of: impl Draw) -> impl Draw { + thunk(move|to: &mut T|{ + let area = of.draw(to)?; + self.0.store(area.w().into(), Relaxed); + self.1.store(area.h().into(), Relaxed); + Ok(area) + }) + } +} + diff --git a/src/space/split.rs b/src/space/split.rs index fabcb6b..f6f53b2 100644 --- a/src/space/split.rs +++ b/src/space/split.rs @@ -39,12 +39,12 @@ impl Split { /// ``` /// use tengri::draw::Split::*; - /// let _ = Above.bsp((), ()); - /// let _ = Below.bsp((), ()); - /// let _ = North.bsp((), ()); - /// let _ = South.bsp((), ()); - /// let _ = East.bsp((), ()); - /// let _ = West.bsp((), ()); + /// let _ = Above.half("", ""); + /// let _ = Below.half("", ""); + /// let _ = North.half("", ""); + /// let _ = South.half("", ""); + /// let _ = East.half("", ""); + /// let _ = West.half("", ""); /// ``` pub const fn half (&self, a: impl Draw, b: impl Draw) -> impl Draw { thunk(move|to: &mut T|{ diff --git a/src/space/xywh.rs b/src/space/xywh.rs index a4047db..c403f5a 100644 --- a/src/space/xywh.rs +++ b/src/space/xywh.rs @@ -3,8 +3,9 @@ use super::*; /// Point with size. /// /// ``` -/// let xywh = tengri::XYWH(0u16, 0, 0, 0); -/// assert_eq!(tengri::XYWH(10u16, 10, 20, 20).center(), tengri::XY(20, 20)); +/// use tengri::space::{XY, XYWH}; +/// let xywh = XYWH(0u16, 0, 0, 0); +/// assert_eq!(XYWH(10u16, 10, 20, 20).center(), XY(20, 20)); /// ``` /// /// * [ ] TODO: origin field (determines at which corner/side is X0 Y0) diff --git a/src/term.rs b/src/term.rs index 2251432..4be440c 100644 --- a/src/term.rs +++ b/src/term.rs @@ -645,9 +645,9 @@ pub const fn border (on: bool, style: S, draw: impl Draw> (result: Usually) -> impl Draw { thunk(move|to: &mut Tui|match result {