From 1f60b43f616ff49ddd8187bcca8d31e9b6597cec Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Mon, 6 Jul 2026 20:40:52 +0300 Subject: [PATCH] reorganize, add Azimuth --- dizzle | 2 +- src/draw.rs | 76 ++--- src/draw/align.rs | 31 ++ src/{layout => draw}/area.rs | 0 src/draw/azimuth.rs | 12 + src/{ => draw}/color.rs | 7 + src/{space => draw}/coord.rs | 0 src/{space => draw}/iter.rs | 0 src/{ => draw}/layout.rs | 4 - src/{space => draw}/lrtb.rs | 2 +- src/draw/origin.rs | 42 +++ src/{space => draw}/sizer.rs | 4 +- src/draw/space.rs | 2 + src/{layout => draw}/split.rs | 7 +- src/draw/thunk.rs | 48 +++ src/{space => draw}/xywh.rs | 24 -- src/eval.rs | 56 +++- src/layout/align.rs | 15 - src/lib.rs | 5 +- src/origin.rs | 0 src/space.rs | 8 - src/term.rs | 540 +++++----------------------------- src/term/border.rs | 2 +- src/term/input.rs | 39 +++ src/term/output.rs | 343 +++++++++++++++++++++ src/text.rs | 2 +- 26 files changed, 677 insertions(+), 594 deletions(-) create mode 100644 src/draw/align.rs rename src/{layout => draw}/area.rs (100%) create mode 100644 src/draw/azimuth.rs rename src/{ => draw}/color.rs (99%) rename src/{space => draw}/coord.rs (100%) rename src/{space => draw}/iter.rs (100%) rename src/{ => draw}/layout.rs (94%) rename src/{space => draw}/lrtb.rs (98%) create mode 100644 src/draw/origin.rs rename src/{space => draw}/sizer.rs (82%) create mode 100644 src/draw/space.rs rename src/{layout => draw}/split.rs (95%) create mode 100644 src/draw/thunk.rs rename src/{space => draw}/xywh.rs (93%) delete mode 100644 src/layout/align.rs create mode 100644 src/origin.rs delete mode 100644 src/space.rs create mode 100644 src/term/input.rs create mode 100644 src/term/output.rs diff --git a/dizzle b/dizzle index f8e310b..af2a107 160000 --- a/dizzle +++ b/dizzle @@ -1 +1 @@ -Subproject commit f8e310b477afc1aab4701fdc52da474143d78a28 +Subproject commit af2a10751f87caef8e5526d1f692c941b8de8357 diff --git a/src/draw.rs b/src/draw.rs index 6675132..c5e895a 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,5 +1,4 @@ use crate::*; -pub use crate::space::*; /// Output target. /// @@ -70,14 +69,16 @@ pub trait Screen: Xy + Wh + Send + Sync + Sized { /// } /// ``` pub trait Draw { - fn draw (self, to: &mut S) -> Perhaps>; - fn layout (&self, area: XYWH) -> Perhaps> { + fn draw (self, to: &mut S) -> Drawn; + fn layout (&self, area: XYWH) -> Drawn { Ok(Some(area)) } } +pub type Drawn = Perhaps>; + impl Draw for () { - fn draw (self, _: &mut S) -> Perhaps> { + fn draw (self, _: &mut S) -> Drawn { Ok(None) } } @@ -87,7 +88,7 @@ impl_draw!(,>|self: Option, to: S|{ }); //impl> Draw for RwLock { - //fn draw (self, __: &mut S) -> Perhaps> { + //fn draw (self, __: &mut S) -> Drawn { //todo!() //} //} @@ -111,52 +112,21 @@ impl> Draw for &V { } } -/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. -pub struct ThunkPerhaps>>( - pub F, - std::marker::PhantomData -); - -implPerhaps>> Draw for Thunk { - fn draw (self, to: &mut T) -> Perhaps> { - (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 Perhaps>> ( - 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) }) +features! { + "draw": [ + align, + area, + azimuth, + color, + coord, + iter, + layout, + lrtb, + origin, + sizer, + space, + split, + thunk, + xywh + ] } diff --git a/src/draw/align.rs b/src/draw/align.rs new file mode 100644 index 0000000..8e4ebb1 --- /dev/null +++ b/src/draw/align.rs @@ -0,0 +1,31 @@ +use crate::*; + +pub struct Align(Option, T); + +impl_draw!(,>|self: Align, to: S|{ + todo!() +}); + +/// ``` +/// use tengri::*; +/// let _ = align(None, "unaligned"); +/// let _ = align(Azimuth::SE, "southeast"); +/// let _ = align(Some(Azimuth::SE), "southeast"); +/// ``` +pub fn align , U: Into>> (origin: U, it: T) -> Align { + Align(origin.into(), it) +} + +pub fn align_c > (it: T) -> Align { Align(Some(Azimuth::C), it) } +pub fn align_x > (it: T) -> Align { Align(Some(Azimuth::X), it) } +pub fn align_y > (it: T) -> Align { Align(Some(Azimuth::Y), it) } + +pub fn align_n > (it: T) -> Align { Align(Some(Azimuth::N), it) } +pub fn align_s > (it: T) -> Align { Align(Some(Azimuth::S), it) } +pub fn align_e > (it: T) -> Align { Align(Some(Azimuth::E), it) } +pub fn align_w > (it: T) -> Align { Align(Some(Azimuth::W), it) } + +pub fn align_ne > (it: T) -> Align { Align(Some(Azimuth::NE), it) } +pub fn align_se > (it: T) -> Align { Align(Some(Azimuth::SE), it) } +pub fn align_nw > (it: T) -> Align { Align(Some(Azimuth::NW), it) } +pub fn align_sw > (it: T) -> Align { Align(Some(Azimuth::SW), it) } diff --git a/src/layout/area.rs b/src/draw/area.rs similarity index 100% rename from src/layout/area.rs rename to src/draw/area.rs diff --git a/src/draw/azimuth.rs b/src/draw/azimuth.rs new file mode 100644 index 0000000..de72abc --- /dev/null +++ b/src/draw/azimuth.rs @@ -0,0 +1,12 @@ +use crate::*; + +/// Where is [0, 0] located? +/// +/// ``` +/// use tengri::draw::Azimuth; +/// let _ = Azimuth::NW.align(()); +/// ``` +#[cfg_attr(test, derive(Arbitrary))] +#[derive(Debug, Copy, Clone, Default)] pub enum Azimuth { + #[default] C, X, Y, NW, N, NE, E, SE, S, SW, W +} diff --git a/src/color.rs b/src/draw/color.rs similarity index 99% rename from src/color.rs rename to src/draw/color.rs index 82bdc70..ccea5e6 100644 --- a/src/color.rs +++ b/src/draw/color.rs @@ -29,6 +29,7 @@ pub fn rgb_to_okhsl (color: Color) -> Okhsl { } pub trait HasColor { fn color (&self) -> ItemColor; } + #[macro_export] macro_rules! has_color { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? { @@ -42,8 +43,11 @@ pub struct ItemColor { pub term: Color, pub okhsl: Okhsl } + impl_from!(ItemColor: |term: Color| Self { term, okhsl: rgb_to_okhsl(term) }); + impl_from!(ItemColor: |okhsl: Okhsl| Self { okhsl, term: okhsl_to_rgb(okhsl) }); + // A single color within item theme parameters, in OKHSL and RGB representations. impl ItemColor { #[cfg(feature = "term")] pub const fn from_tui (term: Color) -> Self { @@ -80,8 +84,11 @@ pub struct ItemTheme { pub darker: ItemColor, pub darkest: ItemColor, } + impl_from!(ItemTheme: |base: ItemColor| Self::from_item_color(base)); + impl_from!(ItemTheme: |base: Color| Self::from_tui_color(base)); + impl ItemTheme { #[cfg(feature = "term")] pub const G: [Self;256] = { let mut builder = dizzle::konst::array::ArrayBuilder::new(); diff --git a/src/space/coord.rs b/src/draw/coord.rs similarity index 100% rename from src/space/coord.rs rename to src/draw/coord.rs diff --git a/src/space/iter.rs b/src/draw/iter.rs similarity index 100% rename from src/space/iter.rs rename to src/draw/iter.rs diff --git a/src/layout.rs b/src/draw/layout.rs similarity index 94% rename from src/layout.rs rename to src/draw/layout.rs index 3f0173f..5db0b5b 100644 --- a/src/layout.rs +++ b/src/draw/layout.rs @@ -1,9 +1,5 @@ use crate::*; -mod align; pub use self::align::*; -mod area; pub use self::area::*; -mod split; pub use self::split::*; - pub enum Layout>, I: Draw> { __(PhantomData), MinW(X, I), diff --git a/src/space/lrtb.rs b/src/draw/lrtb.rs similarity index 98% rename from src/space/lrtb.rs rename to src/draw/lrtb.rs index a905e89..7f8b182 100644 --- a/src/space/lrtb.rs +++ b/src/draw/lrtb.rs @@ -1,5 +1,5 @@ use crate::*; -use Origin::*; +use Azimuth::*; impl> Lrtb for T {} diff --git a/src/draw/origin.rs b/src/draw/origin.rs new file mode 100644 index 0000000..da47996 --- /dev/null +++ b/src/draw/origin.rs @@ -0,0 +1,42 @@ +use crate::*; + +pub struct Origin(Option, T); + +impl_draw!(,>|self: Origin, to: S|{ + todo!() +}); + +/// ``` +/// use tengri::*; +/// let _ = origin(None, "origin unspecified"); +/// let _ = origin(Azimuth::SE, "southeast"); +/// let _ = origin(Some(Azimuth::SE), "southeast"); +/// ``` +pub fn origin , U: Into>> (origin: U, it: T) -> Origin { + Origin(origin.into(), it) +} + +pub fn origin_c > (it: T) -> Origin { Origin(Some(Azimuth::C), it) } +pub fn origin_x > (it: T) -> Origin { Origin(Some(Azimuth::X), it) } +pub fn origin_y > (it: T) -> Origin { Origin(Some(Azimuth::Y), it) } + +pub fn origin_n > (it: T) -> Origin { Origin(Some(Azimuth::N), it) } +pub fn origin_s > (it: T) -> Origin { Origin(Some(Azimuth::S), it) } +pub fn origin_e > (it: T) -> Origin { Origin(Some(Azimuth::E), it) } +pub fn origin_w > (it: T) -> Origin { Origin(Some(Azimuth::W), it) } + +pub fn origin_ne > (it: T) -> Origin { Origin(Some(Azimuth::NE), it) } +pub fn origin_se > (it: T) -> Origin { Origin(Some(Azimuth::SE), it) } +pub fn origin_nw > (it: T) -> Origin { Origin(Some(Azimuth::NW), it) } +pub fn origin_sw > (it: T) -> Origin { Origin(Some(Azimuth::SW), it) } + +/// Something that has `[0, 0]` at a particular point. +pub trait HasOrigin { + fn origin (&self) -> Azimuth; +} + +impl> HasOrigin for T { + fn origin (&self) -> Azimuth { + *self.as_ref() + } +} diff --git a/src/space/sizer.rs b/src/draw/sizer.rs similarity index 82% rename from src/space/sizer.rs rename to src/draw/sizer.rs index 90d09bf..7ff5ed5 100644 --- a/src/space/sizer.rs +++ b/src/draw/sizer.rs @@ -23,8 +23,8 @@ impl Sizer { 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); + self.0.store(area.map(|a|a.w()).unwrap_or(T::Unit::zero()).into(), Relaxed); + self.1.store(area.map(|a|a.h()).unwrap_or(T::Unit::zero()).into(), Relaxed); Ok(area) }) } diff --git a/src/draw/space.rs b/src/draw/space.rs new file mode 100644 index 0000000..6576b44 --- /dev/null +++ b/src/draw/space.rs @@ -0,0 +1,2 @@ +use crate::*; + diff --git a/src/layout/split.rs b/src/draw/split.rs similarity index 95% rename from src/layout/split.rs rename to src/draw/split.rs index 2c9bd62..3d64a75 100644 --- a/src/layout/split.rs +++ b/src/draw/split.rs @@ -1,4 +1,5 @@ use super::*; +use Azimuth::*; pub const fn east (a: impl Draw, b: impl Draw) -> impl Draw { Split::East.half(a, b) @@ -62,7 +63,7 @@ impl Split { to.show(area(area_b, b))?; } } - Ok(to.xywh()) // FIXME: compute and return actually used area + Ok(Some(to.xywh())) // FIXME: compute and return actually used area }) } @@ -82,8 +83,8 @@ impl Split { /// /// */ /// ``` - const fn origins (&self) -> (Origin, Origin) { - use Origin::*; + const fn origins (&self) -> (Azimuth, Azimuth) { + use Azimuth::*; match self { Self::South => (S, N), Self::East => (E, W), diff --git a/src/draw/thunk.rs b/src/draw/thunk.rs new file mode 100644 index 0000000..7e98b20 --- /dev/null +++ b/src/draw/thunk.rs @@ -0,0 +1,48 @@ +use crate::*; + +/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. +pub struct Thunk(pub F, std::marker::PhantomData); + +implPerhaps>> Draw for Thunk { + fn draw (self, to: &mut T) -> Perhaps> { + (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 Perhaps>> ( + 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/space/xywh.rs b/src/draw/xywh.rs similarity index 93% rename from src/space/xywh.rs rename to src/draw/xywh.rs index fa2cc8c..c831d14 100644 --- a/src/space/xywh.rs +++ b/src/draw/xywh.rs @@ -1,29 +1,6 @@ use super::*; -use Origin::*; use Split::*; -/// Where is [0, 0] located? -/// -/// ``` -/// use tengri::draw::Origin; -/// let _ = Origin::NW.align(()); -/// ``` -#[cfg_attr(test, derive(Arbitrary))] -#[derive(Debug, Copy, Clone, Default)] pub enum Origin { - #[default] C, X, Y, NW, N, NE, E, SE, S, SW, W -} - -/// Something that has `[0, 0]` at a particular point. -pub trait HasOrigin { - fn origin (&self) -> Origin; -} - -impl> HasOrigin for T { - fn origin (&self) -> Origin { - *self.as_ref() - } -} - pub trait Xy { fn x (&self) -> N; fn y (&self) -> N; @@ -262,4 +239,3 @@ pub const fn y_push (x: T::Unit, a: impl Draw) -> impl Draw { pub const fn y_pull (x: T::Unit, a: impl Draw) -> impl Draw { a } - diff --git a/src/eval.rs b/src/eval.rs index 63c579c..b3d0a4f 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,4 +1,5 @@ -use crate::{*, term::*, draw::*, space::*, lang::*, ratatui::prelude::*}; +use crate::{*, lang::*}; +use ratatui::style::Color; /// Some layout operations exist in XY, X, and Y variants that take 3 or 2 arguments. /// Their handling in [eval_view] is uniform and goes like this: @@ -78,7 +79,7 @@ macro_rules! eval_enum (( pub fn eval_view <'a, O: Screen + 'a, S> ( state: &S, output: &mut O, expr: &'a impl Expression ) -> Perhaps> where - S: Interpret> + S: Interpret>> + for<'b> Namespace<'b, bool> + for<'b> Namespace<'b, O::Unit> + for<'b> Namespace<'b, Option> @@ -100,35 +101,58 @@ pub fn eval_view <'a, O: Screen + 'a, S> ( let arg2 = tail1.head(); // First `frags.next()` calls returns the namespace. - Some(match frags.next() { + match frags.next() { Some("when") => when( state.namespace(arg0?)?.unwrap(), - thunk(move|output: &mut O|state.interpret(output, &arg1)) + thunk(move|output: &mut O|{ + state.interpret(output, &arg1) + }) ).draw(output), Some("either") => either( state.namespace(arg0?)?.unwrap(), - thunk(move|output: &mut O|state.interpret(output, &arg1)), - thunk(move|output: &mut O|state.interpret(output, &arg2)), + thunk(move|output: &mut O|{ + state.interpret(output, &arg1) + }), + thunk(move|output: &mut O|{ + state.interpret(output, &arg2) + }), ).draw(output), // Second `frags.next()` calls returns the namespace member. Some("bsp") => { let direction = eval_enum!("bsp", output, state, frags.next(), arg0, Split { - "n" => North, "s" => South, "e" => East, "w" => West, "a" => Above, "b" => Below + "n" => North, + "s" => South, + "e" => East, + "w" => West, + "a" => Above, + "b" => Below }); direction.half( - thunk(move|output: &mut O|state.interpret(output, &arg0)), - thunk(move|output: &mut O|state.interpret(output, &arg1)), + thunk(move|output: &mut O|{ + state.interpret(output, &arg0) + }), + thunk(move|output: &mut O|{ + state.interpret(output, &arg1) + }), ).draw(output) }, Some("align") => { - let alignment = eval_enum!("align", output, state, frags.next(), arg0, Origin { - "c" => C, "n" => N, "s" => S, "e" => E, "w" => W, "x" => X, "y" => Y + let alignment = eval_enum!("align", output, state, frags.next(), arg0, Azimuth { + "c" => C, + "n" => N, + "s" => S, + "e" => E, + "w" => W, + "x" => X, + "y" => Y + }); + let content = thunk(move|output: &mut O|{ + state.interpret(output, &arg0) }); - let content = thunk(move|output: &mut O|state.interpret(output, &arg0)); align(alignment, content).draw(output) }, @@ -154,7 +178,7 @@ pub fn eval_view <'a, O: Screen + 'a, S> ( _ => return Ok(None) - }).transpose() + } } /// Interpret TUI-specific layout operation. @@ -180,7 +204,7 @@ pub fn eval_view <'a, O: Screen + 'a, S> ( pub fn eval_view_tui <'a, S> ( state: &S, output: &mut Tui, expr: impl Expression + 'a ) -> Perhaps> where - S: Interpret> + S: Interpret>> + for<'b>Namespace<'b, bool> + for<'b>Namespace<'b, u16> + for<'b>Namespace<'b, Color> @@ -208,7 +232,7 @@ pub fn eval_view_tui <'a, S> ( output.show(fg(color, thunk(move|output: &mut Tui|{ state.interpret(output, &arg1)?; // FIXME?: don't max out the used area? - Ok(output.area().into()) + Ok(Some(output.area().into())) }))) }, @@ -218,7 +242,7 @@ pub fn eval_view_tui <'a, S> ( output.show(bg(color, thunk(move|output: &mut Tui|{ state.interpret(output, &arg1)?; // FIXME?: don't max out the used area? - Ok(output.area().into()) + Ok(Some(output.area().into())) }))) }, diff --git a/src/layout/align.rs b/src/layout/align.rs deleted file mode 100644 index 74af5c6..0000000 --- a/src/layout/align.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::*; - -/// ``` -/// use tengri::*; -/// let _ = align(None, "unaligned"); -/// let _ = align(Origin::SE, "southeast"); -/// let _ = align(Some(Origin::SE), "southeast"); -/// ``` -pub fn align , U: Into>> (origin: U, it: T) -> Align { - Align(origin.into(), it) -} - -pub struct Align(Option, T); - -impl_draw!(,>|self: Align, to: S|{ todo!() }); diff --git a/src/lib.rs b/src/lib.rs index d0ddf0e..f42030b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,7 @@ pub extern crate unicode_width; #[cfg(feature = "term")] pub extern crate crossterm; #[cfg(feature = "lang")] pub extern crate dizzle as lang; #[cfg(test)] #[macro_use] pub extern crate proptest; +#[cfg(test)] pub(crate) use proptest_derive::Arbitrary; pub(crate) use ::{ atomic_float::AtomicF64, @@ -47,9 +48,9 @@ features! { "time": [ time ], "play": [ exit, task ], "sing": [ sing ], - "draw": [ draw, space, layout, color ], "text": [ text ], - "term": [ term ] + "term": [ term ], + "draw": [ draw ] } /// Define a trait an implement it for various mutation-enabled wrapper types. */ diff --git a/src/origin.rs b/src/origin.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/space.rs b/src/space.rs deleted file mode 100644 index 985dd15..0000000 --- a/src/space.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::*; -#[cfg(test)] use proptest_derive::Arbitrary; - -mod coord; pub use self::coord::*; -mod iter; pub use self::iter::*; -mod sizer; pub use self::sizer::*; -mod xywh; pub use self::xywh::*; -mod lrtb; pub use self::lrtb::*; diff --git a/src/term.rs b/src/term.rs index 7571155..fb2b207 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,10 +1,15 @@ -mod border; -pub use self::border::*; +use crate::{*, lang::*}; + +mod border; pub use self::border::*; +mod event; pub use self::event::*; +mod keys; pub use self::keys::*; +mod buffer; pub use self::buffer::*; +mod input; pub use self::input::*; +mod output; pub use self::output::*; -use crate::{*, lang::*, draw::*, task::*, exit::*}; //use unicode_width::{UnicodeWidthStr, UnicodeWidthChar}; //use rand::distributions::uniform::UniformSampler; -use ::{ +pub(crate) use ::{ std::{ io::{stdout, Write}, time::Duration, @@ -27,6 +32,74 @@ use ::{ }, }; +/// Terminal output. +pub struct Tui( + /// Ratatui buffer; area is screen size + pub Buffer, + /// Current draw area + pub XYWH +); + +impl Deref for Tui { type Target = Buffer; fn deref (&self) -> &Buffer { &self.0 } } +impl DerefMut for Tui { fn deref_mut (&mut self) -> &mut Buffer { &mut self.0 } } +impl AsMut for Tui { fn as_mut (&mut self) -> &mut Buffer { &mut self.0 } } +impl Wide for Tui { fn w (&self) -> u16 { self.1.2 } } +impl Tall for Tui { fn h (&self) -> u16 { self.1.3 } } +impl HasOrigin for Tui { fn origin (&self) -> Azimuth { Azimuth::NW } } +impl Xy for Tui { fn x (&self) -> u16 { self.1.0 } fn y (&self) -> u16 { self.1.1 } } + +impl Tui { + pub fn new (width: u16, height: u16) -> Self { + Self(Buffer::empty(Rect { x: 0, y: 0, width, height }), XYWH(0, 0, width, height)) + } + 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(); + self.0.resize(size); + self.0.reset(); + } + } + 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"); + std::mem::swap(self, &mut next); + next.0.reset(); + } +} + +impl Tui { + pub fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> XYWH { + for row in 0..self.h() { + let y = self.y() + row; + for col in 0..self.w() { + let x = self.x() + col; + if x < self.0.area.width && y < self.0.area.height { + if let Some(cell) = self.0.cell_mut(Position { x, y }) { + callback(cell, col, row); + } + } + } + } + self.xywh() + } + pub fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) { + for cell in self.0.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