diff --git a/dizzle b/dizzle index b6c1bb5..a3d7719 160000 --- a/dizzle +++ b/dizzle @@ -1 +1 @@ -Subproject commit b6c1bb5f9400c28de1709df78c18e0d4d5618a15 +Subproject commit a3d7719bcc955552a5dbcf755115333872e87de4 diff --git a/src/layout/axis.rs b/src/layout/axis.rs index 1a02ab8..0ea9cfa 100644 --- a/src/layout/axis.rs +++ b/src/layout/axis.rs @@ -308,11 +308,9 @@ impl <'a, S: Screen, I: Draw> Draw for Full<'a, S, I> { Self::WH(..) => (Some(x0), Some(y0), Some(w0), Some(h0)), _ => unreachable!() }; - Ok(to - .draw(to.area(), item)? - .map(|XYWH(x2, y2, w2, h2)|XYWH( - x1.unwrap_or(x2), y1.unwrap_or(y2), w1.unwrap_or(w2), h1.unwrap_or(h2), - ))) + Ok(to.draw(to.area(), item)?.map(|XYWH(x2, y2, w2, h2)|XYWH( + x1.unwrap_or(x2), y1.unwrap_or(y2), w1.unwrap_or(w2), h1.unwrap_or(h2), + ))) } } @@ -328,14 +326,14 @@ impl <'a, S: Screen, I: Draw, X: Into> + Copy> Draw for Ex }; let w1 = w1.unwrap_or(w0); let h1 = h1.unwrap_or(h0); - Ok(to - .draw(XYWH(x0, y0, w1, h1), item)? - .map(|XYWH(x2, y2, w2, h2)|match self { - Self::W(..) => XYWH(x0, y2, w1, h2), - Self::H(..) => XYWH(x2, y0, w2, h1), - Self::WH(..) => XYWH(x0, y0, w1, h1), - _ => unreachable!() - })) + let area = XYWH(x0, y0, w1, h1); + let area = to.draw(area, item)?; + Ok(area.map(|XYWH(x2, y2, w2, h2)|match self { + Self::W(..) => XYWH(x0, y2, w1, h2), + Self::H(..) => XYWH(x2, y0, w2, h1), + Self::WH(..) => XYWH(x0, y0, w1, h1), + _ => unreachable!() + })) } } diff --git a/src/layout/azimuth.rs b/src/layout/azimuth.rs index c948a9a..53c8853 100644 --- a/src/layout/azimuth.rs +++ b/src/layout/azimuth.rs @@ -185,52 +185,6 @@ fn_kw_layout!(kw_split |state, output, expr| { #[default] Below } -/// Two components, first one has fixed size along the transverse axis. -pub struct Bar(Split, N, A, B); - -/// Define a split with first component having fixed size along the transverse axis. -pub fn bar , B: Draw> ( - split: Split, n: impl Into, a: A, b: B -) -> Bar { - Bar(split, n.into(), a, b) -} - -impl, B: Draw> Draw for Bar { - fn draw (&self, to: &mut S) -> Drawn { - #[cfg(feature = "prof")] profiling::scope!("bar_draw"); - let Self(split, n, a, b, ..) = self; - let (area_a, area_b) = bar_areas(to, split, *n, a, b)?; - let (drawn_a, drawn_b) = draw_stacks(split, to, a, area_a, None, b, area_b, None)?; - Ok(stack_drawn(split, drawn_a, drawn_b)) - } -} - -pub fn bar_areas <'a, S: Screen> ( - to: &mut S, split: &Split, n: S::Unit, a: impl Draw, b: impl Draw, -) -> UsuallyRef<'a, (Option>, Option>)> { - let XYWH(x0, y0, w0, h0) = to.area(); - Ok(match split { - Split::South => ( - to.size(XYWH(x0, y0, w0, n), a)?, - to.size(XYWH(x0, y0 + n, w0, h0 - n), b)?, - ), - Split::East => ( - to.size(XYWH(x0, y0, n, h0), a)?, - to.size(XYWH(x0 + n, y0, w0 - n, h0), b)?, - ), - Split::North => ( - to.size(XYWH(x0, y0 + h0 - n, w0, n), a)?, - to.size(XYWH(x0, y0, w0, h0 - n), b)?, - ), - Split::West => ( - to.size(XYWH(x0 + w0 - n, y0, n, h0), a)?, - to.size(XYWH(x0, y0, w0 - n, h0), b)?, - ), - _ => todo!() - }) -} - -/// Dynamically laid out split pub struct Pair(Split, A, B); /// ``` @@ -246,7 +200,7 @@ pub struct Pair(Split, A, B); /// assert_eq!(size_of(&split(East, split(South, "foo", "bar"), "baz"))?, Some(XYWH(0, 0, 6, 2))); /// # return Ok(()); } /// ``` -pub fn split , B: Draw> ( +pub fn split <'a, S: Screen, A: Draw, B: Draw> ( split: Split, a: A, b: B ) -> Pair { Pair(split, a, b) @@ -304,7 +258,10 @@ fn draw_stacks <'a, S: Screen> ( /// # Ok(()) } /// ``` pub fn stack_areas <'a, S: Screen> ( - split: &Split, to: &mut S, a: impl Draw, b: impl Draw, + split: &Split, + to: &mut S, + a: impl Draw, + b: impl Draw, ) -> UsuallyRef<'a, (Option>, Option>)> { let area_a = to.size(None, a)?; Ok(match split { @@ -372,21 +329,6 @@ fn stack_drawn ( impl Split { - /// ``` - /// use tengri::*; - /// let _ = Split::Above.stack(&"", &""); - /// let _ = Split::Below.stack(&"", &""); - /// let _ = Split::North.stack(&"", &""); - /// let _ = Split::South.stack(&"", &""); - /// let _ = Split::East.stack(&"", &""); - /// let _ = Split::West.stack(&"", &""); - /// ``` - pub const fn bar <'a, S: Screen, A: Draw, B: Draw> (&self, n: S::Unit, a: A, b: B) - -> impl Draw - { - Bar(*self, n, a, b) - } - /// ``` /// use tengri::*; /// let _ = Split::Above.stack(&"", &""); diff --git a/src/lib.rs b/src/lib.rs index 92d0b69..d68e831 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1316,6 +1316,16 @@ pub trait AsMutOpt { fn as_mut_opt (&mut self) -> Option<&mut T>; } /// 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::*; /// struct MyWidget(bool); @@ -1329,6 +1339,39 @@ pub trait AsMutOpt { fn as_mut_opt (&mut self) -> Option<&mut T>; } fn draw (&self, to: &mut S) -> Drawn; } + ///// Emit a [Draw]able. + ///// + ///// Speculative. How to avoid conflicts with [Draw] proper? + //pub trait View { + //fn view (&self) -> impl Draw ; + //} + + //impl View for () { + //fn view (&self) -> impl Draw { + //() + //} + //} + + ///// Return a [Draw]able. + ///// + ///// ``` + ///// # use tengri::*; + ///// let _ = view::(||"drawable"); + ///// let _ = view::(||Some("drawable")); + ///// ``` + //pub const fn view Draw, F: Fn()->T> (view: F) -> impl View { + //ViewThunk(view, PhantomData) + //} + + ///// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. + //pub struct ViewThunk(pub F, std::marker::PhantomData); + + //impl Draw, F: Fn()->T> View for ViewThunk { + //fn view (&self) -> impl Draw { + //self.0() + //} + //} + /// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. pub struct DrawThunk(pub F, std::marker::PhantomData); @@ -1603,6 +1646,7 @@ pub trait AsMutOpt { fn as_mut_opt (&mut self) -> Option<&mut T>; } #[cfg(feature = "draw")] pub use self::color::*; #[cfg(feature = "draw")] mod color { use crate::*; + use dizzle::LanguageError::*; use ::ratatui::style::Color; use ::rand::distributions::uniform::UniformSampler; pub(crate) use ::palette::{ @@ -1854,5 +1898,86 @@ pub trait AsMutOpt { fn as_mut_opt (&mut self) -> Option<&mut T>; } } } +#[cfg(feature = "eval")] pub use self::eval::*; +#[cfg(feature = "eval")] mod eval { + use crate::*; + + /// ``` + /// # use ::tengri::{*, dizzle::*, ratatui::prelude::Color}; + /// + /// #[namespace(bool)] + /// #[namespace(u8)] + /// #[namespace(u16)] + /// #[namespace(Option)] + /// #[namespace(Color get_color)] + /// struct App; + /// + /// fn get_color (state: &App, src: impl Language) -> Perhaps { + /// Ok(if let Some(color) = Tui::eval_color_expr(state, &src)? { + /// Some(color) + /// } else if let Some(sym) = src.word()? { + /// Some(match sym { + /// ":color/bg" => Color::Rgb(28, 32, 36), + /// ":color/fg" => Color::Rgb(98, 92, 96), + /// _ => return Err(format!("not a color: {sym}").into()) + /// }) + /// } else { + /// return Err(format!("not a color: {:?}", src.src()?).into()) + /// }) + /// } + /// + /// impl Interpret>> for App { + /// fn interpret_expr <'a> (&'a self, to: &mut Tui, lang: &'a impl Language) + /// -> Usually>> + /// { + /// self.keyword(to, lang) + /// } + /// } + /// + /// impl Keywords> for App { + /// fn keywords () -> impl Iterator Perhaps>> { + /// [ + /// kw_when, kw_either, kw_split, kw_align, + /// kw_exact, kw_min, kw_max, kw_push, + /// kw_tui_text, kw_tui_fg, kw_tui_bg + /// ].into_iter() + /// } + /// } + /// + /// # fn main () -> tengri::Usually<()> { + /// let state = App; + /// let mut out = Tui::new(80, 25); + /// state.interpret_expr(&mut out, &"")?; + /// state.interpret_expr(&mut out, &"text Hello world!")?; + /// state.interpret_expr(&mut out, &"fg (g 0) (text Hello world!)")?; + /// state.interpret_expr(&mut out, &"bg (g 2) (text Hello world!)")?; + /// state.interpret_expr(&mut out, &"(bg (g 3) (fg (g 4) (text Hello world!)))")?; + /// # Ok(()) } + /// ``` + pub trait Keywords : 'static { + fn keywords <'a> () -> impl Iterator PerhapsRef<'a, V>>; + fn keyword <'a> (&self, to: &mut U, expr: &'a (impl Language + ?Sized)) -> PerhapsRef<'a, V> { + if let Some(expr) = expr.src()? { + for keyword in Self::keywords() { + if let Some(result) = keyword(self, to, &expr)? { + return Ok(Some(result)) + } + } + } + Ok(None) + } + } + + #[macro_export] macro_rules! impl_keywords { + ($T:ty, $U:ty, $V:ty [ $($kw:ident),* ]) => { + impl Keywords<$T, $U> for $V { + fn keywords <'a> () -> impl Iterator PerhapsRef<'a, $U>> { + [ $($kw),* ].into_iter() + } + } + } + } +} + #[cfg(feature = "term")] pub use self::term::*; #[cfg(feature = "term")] mod term; diff --git a/src/term.rs b/src/term.rs index dffe505..8b19db3 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,4 +1,5 @@ use crate::*; +use Color::*; //use unicode_width::{UnicodeWidthStr, UnicodeWidthChar}; //use rand::distributions::uniform::UniformSampler; pub(crate) use ::{ @@ -267,7 +268,7 @@ impl Tui { let Size { width, height } = backend.size().expect("get size failed"); let mut prev = Tui::new(width, height); let mut next = Tui::new(width, height); - Ok(Task::new_sleep(Some("tengri output"), exited.clone(), sleep, move |_perf| { + Ok(Task::new_sleep(Some("tengri output"), exited.clone(), sleep, move |perf| { let Size { width, height } = backend.size().expect("get size failed"); if let Some(state) = state.try_read() { prev.resize(&mut backend, width, height); @@ -408,7 +409,7 @@ impl<'a, T: Draw> Draw for ShowSizeOf { }) }); -//#[cfg(feature = "text")] pub use self::tui_text::*; +#[cfg(feature = "text")] pub use self::tui_text::*; #[cfg(feature = "text")] mod tui_text { use super::*;