diff --git a/dizzle b/dizzle index e984fbb..2dbf9d8 160000 --- a/dizzle +++ b/dizzle @@ -1 +1 @@ -Subproject commit e984fbb9fe8e588399744d50841d006454c16657 +Subproject commit 2dbf9d8797a3642f69813be4ef3613836e061b94 diff --git a/src/draw.rs b/src/draw.rs index d6462fc..76dc916 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,5 +1,5 @@ -mod draw; -pub use self::draw::*; +use crate::*; +pub use crate::space::*; mod view; pub use self::view::*; @@ -10,29 +10,57 @@ pub use self::thunk::*; mod screen; pub use self::screen::*; -use crate::*; -pub use crate::space::*; - -/// Only render when condition is true. +/// Implement the [Draw] trait for a particular drawable and [Screen]. /// /// ``` -/// # use tengri::draw::*; -/// # fn test () -> impl tengri::draw::Draw { -/// when(true, "Yes") -/// # } +/// use tengri::{*, draw::*, term::*}; +/// struct MyDrawable; +/// impl_draw!(|self: MyDrawable, to: Tui|{ +/// todo!("your draw logic") +/// }); /// ``` -pub const fn when (condition: bool, draw: impl Draw) -> impl Draw { - thunk(move|to: &mut T|if condition { draw.draw(to) } else { Ok(Default::default()) }) -} +#[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 +} }); -/// Render one thing if a condition is true and another false. +/// 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::*; -/// # fn test () -> impl tengri::draw::Draw { -/// either(true, "Yes", "No") -/// # } +/// use tengri::{*, draw::*, term::*}; +/// struct MyWidget(bool); +/// impl Draw for MyWidget { +/// fn draw (self, to: &mut Tui) -> Usually> { +/// todo!("your draw logic") +/// } +/// } /// ``` -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) }) +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!() + } +} +//impl> Draw for Arc { + //fn draw (self, __: &mut T) -> Usually> { + //todo!() + //} +//} diff --git a/src/draw/draw.rs b/src/draw/draw.rs deleted file mode 100644 index 8701084..0000000 --- a/src/draw/draw.rs +++ /dev/null @@ -1,60 +0,0 @@ -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 index ea6b858..eab296b 100644 --- a/src/draw/screen.rs +++ b/src/draw/screen.rs @@ -9,7 +9,7 @@ use super::*; /// /// impl> Screen for TestOut { /// type Unit = u16; -/// fn place_at + ?Sized> (&mut self, area: T, _: D) { +/// fn show + ?Sized> (&mut self, area: T, _: D) { /// println!("placed: {area:?}"); /// () /// } @@ -21,14 +21,6 @@ use super::*; /// ``` 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!("place_at") - } + fn show <'t, T: Draw> (&mut self, area: XYWH, content: T); } diff --git a/src/draw/thunk.rs b/src/draw/thunk.rs index 9b1d4b5..a56b5f3 100644 --- a/src/draw/thunk.rs +++ b/src/draw/thunk.rs @@ -1,16 +1,51 @@ 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) } } + +/// Basic [Draw]able closure. +/// +/// ``` +/// # use tengri::{draw::*, term::*}; +/// # fn test () -> impl tengri::draw::Draw { +/// thunk(|to: &mut Tui|Ok(to.1)) +/// # } +/// ``` +pub const fn thunk Usually>> ( + draw: F +) -> Thunk { + Thunk(draw, std::marker::PhantomData) +} + +/// Only render when condition is true. +/// +/// ``` +/// # use tengri::{draw::*, term::*}; +/// # fn test () -> impl tengri::draw::Draw { +/// when(true, "Yes") +/// # } +/// ``` +pub const fn when (condition: bool, draw: impl Draw) -> impl Draw { + thunk(move|to: &mut T|if condition { draw.draw(to) } else { Ok(Default::default()) }) +} + +/// Render one thing if a condition is true and another false. +/// +/// ``` +/// # use tengri::{draw::*, term::*}; +/// # fn test () -> impl tengri::draw::Draw { +/// either(true, "Yes", "No") +/// # } +/// ``` +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) }) +} diff --git a/src/eval.rs b/src/eval.rs index 4fc5934..8709747 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -59,23 +59,16 @@ macro_rules! eval_enum (( /// Interpret layout operation. /// /// ``` -/// # use tengri::{lang::*, draw::*, eval::*}; -/// struct Target { xywh: XYWH /*platform-specific*/} -/// impl tengri::Out for Target { -/// type Unit = u16; -/// fn area (&self) -> XYWH { self.xywh } -/// fn area_mut (&mut self) -> &mut XYWH { &mut self.xywh } -/// fn show <'t, T: Draw + ?Sized> (&mut self, area: XYWH, content: &'t T) {} -/// } +/// # use tengri::{lang::*, draw::*, eval::*, term::*}; /// /// struct State {/*app-specific*/} /// impl<'b> Namespace<'b, bool> for State {} /// impl<'b> Namespace<'b, u16> for State {} -/// impl Interpret for State {} +/// impl Interpret> for State {} /// /// # fn main () -> tengri::Usually<()> { /// let state = State {}; -/// let mut target = Target { xywh: Default::default() }; +/// let mut target = Tui::new(80, 25); /// eval_view(&state, &mut target, &"")?; /// eval_view(&state, &mut target, &"(when true (text hello))")?; /// eval_view(&state, &mut target, &"(either true (text hello) (text world))")?; @@ -197,13 +190,15 @@ pub fn eval_view_tui <'a, S> ( match frags.next() { Some("text") => { - if let Some(src) = args?.src()? { output.place(&src) } + if let Some(src) = args?.src()? { + output.show(output.xywh(), &src) + } }, Some("fg") => { - let arg0 = arg0?.expect("fg: expected arg 0 (color)"); + let arg0 = arg0?.expect("fg: expected arg 0 (color)"); let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color")); - output.place(&fg(color, thunk(move|output: &mut Tui|{ + output.show(output.xywh(), &fg(color, thunk(move|output: &mut Tui|{ state.interpret(output, &arg1)?; // FIXME?: don't max out the used area? Ok(output.area().into()) @@ -211,9 +206,9 @@ pub fn eval_view_tui <'a, S> ( }, Some("bg") => { - let arg0 = arg0?.expect("bg: expected arg 0 (color)"); + let arg0 = arg0?.expect("bg: expected arg 0 (color)"); let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color")); - output.place(&bg(color, thunk(move|output: &mut Tui|{ + output.show(output.xywh(), &bg(color, thunk(move|output: &mut Tui|{ state.interpret(output, &arg1)?; // FIXME?: don't max out the used area? Ok(output.area().into()) diff --git a/src/lib.rs b/src/lib.rs index c7fe21d..394f592 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,7 +93,7 @@ pub use ::dizzle::{Usually, Perhaps, impl_default}; // FIXME: support attrs (docstrings) $($Variant $({ $($arg: $Arg),* })?),* } - impl ::tengri::dizzle::Act<$State> for $Command { + impl ::tengri::lang::Act<$State> for $Command { fn act (&self, $state: &mut $State) -> Perhaps { match self { $(Self::$Variant $({ $($arg),* })? => $body,)* diff --git a/src/space.rs b/src/space.rs index 9a8650c..fd9ac7d 100644 --- a/src/space.rs +++ b/src/space.rs @@ -125,13 +125,17 @@ pub trait Space: X + Y { // FIXME: factor origin fn lrtb (&self) -> [N;4] { [self.x(), self.y(), self.x()+self.w(), self.y()+self.h()] } } + impl + Y> Space for T {} + pub const fn xy_push (x: T::Unit, y: T::Unit, a: impl Draw) -> impl Draw { a } + pub const fn xy_pull (x: T::Unit, y: T::Unit, a: impl Draw) -> impl Draw { a } + /// Shrink drawing area symmetrically. /// /// ``` @@ -142,6 +146,7 @@ pub const fn wh_pad (w: T::Unit, h: T::Unit, draw: impl Draw) { thunk(move|to: &mut T|draw.draw(todo!())) } + /// Only draw content if area is above a certain size. /// /// ``` @@ -161,7 +166,7 @@ pub const fn wh_min (w: Option, h: Option, draw: i pub const fn wh_max (w: Option, h: Option, draw: impl Draw) -> impl Draw { - thunk(move|to: &mut T|draw.draw(todo!())) + thunk(move|_to: &mut T|draw.draw(todo!())) } /// Set the maximum width and/or height of the content. @@ -172,7 +177,7 @@ pub const fn wh_max (w: Option, h: Option, draw: i pub const fn wh_exact (w: Option, h: Option, draw: impl Draw) -> impl Draw { - thunk(move|to: &mut T|draw.draw(todo!())) + thunk(move|_to: &mut T|draw.draw(todo!())) } /// Limit size of drawing area @@ -182,7 +187,7 @@ pub const fn wh_exact (w: Option, h: Option, draw: pub const fn wh_clip ( w: Option, h: Option, draw: impl Draw ) -> impl Draw { - thunk(move|to: &mut T|draw.draw(todo!())) + thunk(move|_to: &mut T|draw.draw(todo!())) } pub const fn wh_full (a: impl Draw) -> impl Draw { a } diff --git a/src/space/iter.rs b/src/space/iter.rs index 4bcfde8..a431f3d 100644 --- a/src/space/iter.rs +++ b/src/space/iter.rs @@ -33,7 +33,7 @@ pub fn iter_east <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( } pub fn iter_south <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( - _iter: impl Fn()->I, _draw: impl Fn(D)->U, + _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U, ) -> impl Draw { thunk(move|_to: &mut S|{ todo!() }) } diff --git a/src/space/split.rs b/src/space/split.rs index f6f53b2..4fb42cc 100644 --- a/src/space/split.rs +++ b/src/space/split.rs @@ -54,12 +54,12 @@ impl Split { let b = origin_b.align(b); match self { Self::Below => { - to.place_at(area_b, &b); - to.place_at(area_b, &a); + to.show(area_b, &b); + to.show(area_b, &a); }, _ => { - to.place_at(area_a, &a); - to.place_at(area_a, &b); + to.show(area_a, &a); + to.show(area_a, &b); } } Ok(to.xywh()) // FIXME: compute and return actually used area diff --git a/src/term.rs b/src/term.rs index ef9ddd1..622f636 100644 --- a/src/term.rs +++ b/src/term.rs @@ -119,25 +119,12 @@ pub fn tui_output < prev.resize(&mut backend, width, height); state.view().draw(&mut next).expect("draw failed"); // TODO draw error prev.redraw(&mut backend, &mut next); - //tui_redraw(&mut backend, &mut prev, &mut next); } let timer = format!("{:>3.3}ms", perf.used.load(Relaxed)); prev.set_string(0, 0, &timer, Style::default()); })?) } -pub fn tui_redraw <'b, W: Write> ( - back: &mut CrosstermBackend, - mut prev: &'b mut Buffer, - mut next: &'b mut Buffer -) { - let updates = prev.diff(&next); - back.draw(updates.into_iter()).expect("failed to render"); - Backend::flush(back).expect("failed to flush output new"); - std::mem::swap(&mut prev, &mut next); - next.reset(); -} - /// Terminal output. pub struct Tui( /// Ratatui buffer; area is screen size @@ -147,10 +134,10 @@ pub struct Tui( ); impl Tui { - fn new (width: u16, height: u16) -> Self { + pub fn new (width: u16, height: u16) -> Self { Self(Buffer::empty(Rect { x: 0, y: 0, width, height }), XYWH(0, 0, width, height)) } - fn resize (&mut self, back: &mut CrosstermBackend, width: u16, height: u16) { + pub fn resize (&mut self, back: &mut CrosstermBackend, width: u16, height: u16) { let size = Rect { x: 0, y: 0, width, height }; if self.0.area != size { back.clear_region(ClearType::All).unwrap(); @@ -158,7 +145,7 @@ impl Tui { self.0.reset(); } } - fn redraw <'b, W: Write> (&'b mut self, back: &mut CrosstermBackend, mut next: &'b mut Self) { + pub fn redraw <'b, W: Write> (&'b mut self, back: &mut CrosstermBackend, mut next: &'b mut Self) { let updates = self.0.diff(&next.0); back.draw(updates.into_iter()).expect("failed to render"); Backend::flush(back).expect("failed to flush output new"); @@ -167,7 +154,18 @@ impl Tui { } } -impl Screen for Tui { type Unit = u16; } +impl Screen for Tui { + type Unit = u16; + /// Render drawable in subarea specified by `area` + fn show <'t, T: Draw> ( + &mut self, area: XYWH, content: T + ) { + let previous_area = self.1; + self.1 = area; + let _result_area = content.draw(self); + self.1 = previous_area; + } +} impl Deref for Tui { type Target = Buffer; fn deref (&self) -> &Buffer { &self.0 } }