From 815bfe937935550bcc41940de68a64273ab61710 Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Tue, 28 Jul 2026 04:14:04 +0300 Subject: [PATCH 01/14] hoist connect_ fns --- src/sing.rs | 113 ++++++++++++++++++++--------------------- src/sing/jack.rs | 0 src/sing/jack_event.rs | 0 src/sing/jack_perf.rs | 0 4 files changed, 56 insertions(+), 57 deletions(-) delete mode 100644 src/sing/jack.rs delete mode 100644 src/sing/jack_event.rs delete mode 100644 src/sing/jack_perf.rs diff --git a/src/sing.rs b/src/sing.rs index c06a1e5..6149532 100644 --- a/src/sing.rs +++ b/src/sing.rs @@ -814,7 +814,6 @@ pub struct Connect { } impl Connect { - pub fn new > ( exact: Option>, re: Option>, @@ -827,62 +826,6 @@ impl Connect { connections } - pub fn midi_ins > ( - jack: &Jack<'static>, - name: &T, - midi_from: &[T], - midi_from_re: Option<&[T]>, - ) -> Usually> { - Ok(Connect::new( - Some(midi_from.into_iter()), - Some([].into_iter()), - midi_from_re.map(|x|x.into_iter())).iter().enumerate() - .map(|(index, connect)|jack.midi_in(&format!("{}/{index}", name.as_ref()), &[connect.clone()])) - .collect::>()?) - } - - pub fn midi_outs > ( - jack: &Jack<'static>, - name: &T, - midi_to: &[T], - midi_to_re: Option<&[T]>, - ) -> Usually> { - Ok(Connect::new( - Some(midi_to.into_iter()), - Some([].into_iter()), - midi_to_re.map(|x|x.into_iter())).iter().enumerate() - .map(|(index, connect)|jack.midi_out(&format!("{index}/{}", name.as_ref()), &[connect.clone()])) - .collect::>()?) - } - - pub fn audio_ins > ( - jack: &Jack<'static>, - name: &T, - audio_from: &[T], - audio_from_re: Option<&[T]>, - ) -> Usually> { - Ok(Connect::new( - Some(audio_from.into_iter()), - Some([].into_iter()), - audio_from_re.map(|x|x.into_iter())).iter().enumerate() - .map(|(index, connect)|jack.audio_in(&format!("{}/{index}", name.as_ref()), &[connect.clone()])) - .collect::>()?) - } - - pub fn audio_outs > ( - jack: &Jack<'static>, - name: &T, - audio_to: &[T], - audio_to_re: Option<&[T]>, - ) -> Usually> { - Ok(Connect::new( - Some(audio_to.into_iter()), - Some([].into_iter()), - audio_to_re.map(|x|x.into_iter())).iter().enumerate() - .map(|(index, connect)|jack.audio_out(&format!("{index}/{}", name.as_ref()), &[connect.clone()])) - .collect::>()?) - } - /// Connect to this exact port pub fn exact (name: impl AsRef) -> Self { let info = format!("=:{}", name.as_ref()).into(); @@ -923,3 +866,59 @@ impl Connect { }).into() } } + +pub fn connect_midi_ins > ( + jack: &Jack<'static>, + name: &T, + midi_from: &[T], + midi_from_re: Option<&[T]>, +) -> Usually> { + Ok(Connect::new( + Some(midi_from.into_iter()), + Some([].into_iter()), + midi_from_re.map(|x|x.into_iter())).iter().enumerate() + .map(|(index, connect)|jack.midi_in(&format!("{}/{index}", name.as_ref()), &[connect.clone()])) + .collect::>()?) +} + +pub fn connect_midi_outs > ( + jack: &Jack<'static>, + name: &T, + midi_to: &[T], + midi_to_re: Option<&[T]>, +) -> Usually> { + Ok(Connect::new( + Some(midi_to.into_iter()), + Some([].into_iter()), + midi_to_re.map(|x|x.into_iter())).iter().enumerate() + .map(|(index, connect)|jack.midi_out(&format!("{index}/{}", name.as_ref()), &[connect.clone()])) + .collect::>()?) +} + +pub fn connect_audio_ins > ( + jack: &Jack<'static>, + name: &T, + audio_from: &[T], + audio_from_re: Option<&[T]>, +) -> Usually> { + Ok(Connect::new( + Some(audio_from.into_iter()), + Some([].into_iter()), + audio_from_re.map(|x|x.into_iter())).iter().enumerate() + .map(|(index, connect)|jack.audio_in(&format!("{}/{index}", name.as_ref()), &[connect.clone()])) + .collect::>()?) +} + +pub fn connect_audio_outs > ( + jack: &Jack<'static>, + name: &T, + audio_to: &[T], + audio_to_re: Option<&[T]>, +) -> Usually> { + Ok(Connect::new( + Some(audio_to.into_iter()), + Some([].into_iter()), + audio_to_re.map(|x|x.into_iter())).iter().enumerate() + .map(|(index, connect)|jack.audio_out(&format!("{index}/{}", name.as_ref()), &[connect.clone()])) + .collect::>()?) +} diff --git a/src/sing/jack.rs b/src/sing/jack.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/sing/jack_event.rs b/src/sing/jack_event.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/sing/jack_perf.rs b/src/sing/jack_perf.rs deleted file mode 100644 index e69de29..0000000 From dee3d334534e9967bd5e924b4d279e6d0a704a61 Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Tue, 28 Jul 2026 04:14:13 +0300 Subject: [PATCH 02/14] add ShowSize --- src/draw/sizer.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/draw/sizer.rs b/src/draw/sizer.rs index 7ff5ed5..5e20693 100644 --- a/src/draw/sizer.rs +++ b/src/draw/sizer.rs @@ -29,3 +29,17 @@ impl Sizer { }) } } + +pub struct ShowSize; + +impl Draw for ShowSize { + fn layout (&self, area: XYWH) -> Perhaps> { + let info = format!("{area:?}"); + Ok(Some(XYWH(area.0, area.1, info.len() as u16, 1))) + } + fn draw (self, to: &mut Tui) -> Drawn { + let area = to.area(); + let info = format!("{area:?}"); + to.text(&info, area.0, area.1, info.len() as u16) + } +} From 25354099fe3cde43a242d41fdb673c4fce5c943e Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Tue, 28 Jul 2026 04:14:56 +0300 Subject: [PATCH 03/14] draw multiline strings --- dizzle | 2 +- src/draw/layout.rs | 22 +++++++++++----------- src/text.rs | 26 +++++++++++++++++++++----- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/dizzle b/dizzle index 4424ef7..1f59b27 160000 --- a/dizzle +++ b/dizzle @@ -1 +1 @@ -Subproject commit 4424ef7fc3fd8bfbea1fb55b4922f7a8cfd2a8ef +Subproject commit 1f59b275a774871e23e8c08637862a11a83c2653 diff --git a/src/draw/layout.rs b/src/draw/layout.rs index 0561f2a..28c9541 100644 --- a/src/draw/layout.rs +++ b/src/draw/layout.rs @@ -357,17 +357,17 @@ impl_draw!(,>|self: Align, to: S|{ let XYWH(x0, y0, w0, h0) = to.area(); if let Some(XYWH(x, y, w, h)) = self.1.layout(to.area())? { to.clip(match self.0 { - Some(NW) => XYWH(x0, y0, w, h), - Some(N) => XYWH(x0 + w0.sub(w) / 2.into(), y0, w, h), - Some(NE) => XYWH((x0 + w0).sub(w), y0, w, h), - Some(W) => XYWH(x0, y0 + h0.sub(h) / 2.into(), w, h), - Some(C) => XYWH(x0 + w0.sub(w) / 2.into(), y0 + h0.sub(h) / 2.into(), w, h), - Some(E) => XYWH((x0 + w0).sub(w), y0 + h0.sub(h) / 2.into(), w, h), - Some(SW) => XYWH(x0, (y0 + h0).sub(h), w, h), - Some(S) => XYWH(x0 + w0.sub(w) / 2.into(), (y0 + h0).sub(h), w, h), - Some(SE) => XYWH((x0 + w0).sub(w), (y0 + h0).sub(h), w, h), - Some(X) => XYWH(x0 + w0.sub(w) / 2.into(), y, w, h), - Some(Y) => XYWH(x, y0 + h0.sub(h) / 2.into(), w, h), + Some(NW) => XYWH(x0, y0, w, h), + Some(N) => XYWH(x0 + w0.minus(w) / 2.into(), y0, w, h), + Some(NE) => XYWH((x0 + w0).minus(w), y0, w, h), + Some(W) => XYWH(x0, y0 + h0.minus(h) / 2.into(), w, h), + Some(C) => XYWH(x0 + w0.minus(w) / 2.into(), y0 + h0.minus(h) / 2.into(), w, h), + Some(E) => XYWH((x0 + w0).minus(w), y0 + h0.minus(h) / 2.into(), w, h), + Some(SW) => XYWH(x0, (y0 + h0).minus(h), w, h), + Some(S) => XYWH(x0 + w0.minus(w) / 2.into(), (y0 + h0).minus(h), w, h), + Some(SE) => XYWH((x0 + w0).minus(w), (y0 + h0).minus(h), w, h), + Some(X) => XYWH(x0 + w0.minus(w) / 2.into(), y, w, h), + Some(Y) => XYWH(x, y0 + h0.minus(h) / 2.into(), w, h), None => to.area() }, |to|self.1.draw(to)) } else { diff --git a/src/text.rs b/src/text.rs index 3fd8806..9c00c64 100644 --- a/src/text.rs +++ b/src/text.rs @@ -3,16 +3,32 @@ pub(crate) use ::unicode_width::*; #[cfg(feature = "term")] mod impl_term { use super::*; - use crate::*; use ratatui::prelude::Position; impl_draw!(|self: String, to: Tui|{self.as_str().draw(to)}); impl_draw!(|self: std::sync::Arc, to: Tui|{self.as_ref().draw(to)}); impl_draw!(|self: &std::sync::Arc, to: Tui|{self.as_ref().draw(to)}); - impl_draw!(|self: &str, to: Tui|{ - let XYWH(x, y, w, ..) = to.1.centered_xy([width_chars_max(to.w(), self), 1]); - to.text(&self, x, y, w) - }); + impl Draw for &str { + fn layout (&self, area: XYWH) -> Perhaps> { + let XYWH(x, y, ..) = area; + let mut max_w = 0u16; + let mut max_h = 0u16; + for line in self.split("\n") { + max_h += 1; + max_w = max_w.max(line.len() as u16); + } + Ok(Some(XYWH(x, y, max_w, max_h))) + } + fn draw (self, to: &mut Tui) -> Drawn { + let area = self.layout(to.area())?.unwrap(); + ////let info = format!("{area:?}"); + //to.text(&self, area.0, area.1, self.len() as u16) + for (index, line) in self.split("\n").enumerate() { + let _ = to.text(&line, area.0, area.1 + index as u16, width_chars_max(area.2, line) as u16)?; + } + Ok(Some(area)) + } + } impl_draw!(,>|self: TrimString, to: Tui|{self.as_ref().draw(to)}); impl_draw!(,>|self: TrimStringRef<'_, T>, to: Tui|{ From d9d6340503160b5ea120fe419c744aaa50e4e191 Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Thu, 30 Jul 2026 14:51:09 +0300 Subject: [PATCH 04/14] fix example names in justfile --- Justfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Justfile b/Justfile index b233be1..23af11d 100644 --- a/Justfile +++ b/Justfile @@ -30,10 +30,10 @@ doc: cargo doc example-tui-00: - cargo run --example mode_0 + cargo run --example mode_00 example-tui-01: - cargo run --example mode_1 + cargo run --example mode_01 example-tui-02: - cargo run --example mode_2 + cargo run --example mode_02 From 49dcb4f3ba7b131205a7ee95a5c38e2f9cd8a4c5 Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Thu, 30 Jul 2026 14:51:25 +0300 Subject: [PATCH 05/14] add missing try_to_u8 in example --- examples/mode_01.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mode_01.rs b/examples/mode_01.rs index 18a9dae..39031c4 100644 --- a/examples/mode_01.rs +++ b/examples/mode_01.rs @@ -38,7 +38,7 @@ impl Interpret for State { Color::new_g(tail.head()?, try_to_u8) }, Some("rgb") if let Some(tail) = expr.tail()? => { - Color::new_rgb(tail.head()?) + Color::new_rgb(tail.head()?, try_to_u8) }, _ => Err(format!("not a color").into()) } From 33e135c61438d284de5d6a58fbf4ecebe13eb1ff Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Thu, 30 Jul 2026 14:51:45 +0300 Subject: [PATCH 06/14] don't use std::iter::Step on Coord --- src/draw/coord.rs | 2 +- src/draw/lrtb.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/draw/coord.rs b/src/draw/coord.rs index 053258e..6f0b611 100644 --- a/src/draw/coord.rs +++ b/src/draw/coord.rs @@ -22,7 +22,7 @@ pub trait Coord: Send + Sync + Copy + From + Into + Into + Into - + std::iter::Step + //+ std::iter::Step { /// Zero in own type. fn zero () -> Self { 0.into() } diff --git a/src/draw/lrtb.rs b/src/draw/lrtb.rs index 7f8b182..586f912 100644 --- a/src/draw/lrtb.rs +++ b/src/draw/lrtb.rs @@ -8,7 +8,7 @@ pub trait Lrtb: Xywh { // FIXME: factor origin [self.x(), self.y(), self.x()+self.w(), self.y()+self.h()] } - fn iter_x (&self) -> impl Iterator where Self: HasOrigin { + fn iter_x (&self) -> std::ops::Range where Self: HasOrigin { self.x_west()..self.x_east() } fn x_west (&self) -> N where Self: HasOrigin { @@ -26,7 +26,7 @@ pub trait Lrtb: Xywh { fn x_center (&self) -> N where Self: HasOrigin { todo!() } - fn iter_y (&self) -> impl Iterator where Self: HasOrigin { + fn iter_y (&self) -> std::ops::Range where Self: HasOrigin { self.y_north()..self.y_south() } fn y_north (&self) -> N where Self: HasOrigin { From 8970ae5d5d0465536f1d29312faec82561e3e70d Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Thu, 30 Jul 2026 14:52:22 +0300 Subject: [PATCH 07/14] stylistic --- src/eval.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index 35ec582..950b40c 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -221,7 +221,7 @@ pub fn eval_view <'a, O: Screen + 'a, S> ( /// # Ok(()) } /// ``` pub fn eval_view_tui <'a, S> ( - state: &S, output: &mut Tui, expr: impl Expression + 'a + state: &S, to: &mut Tui, expr: impl Expression + 'a ) -> Perhaps> where S: Interpret>> + for<'b>Namespace<'b, bool> @@ -239,7 +239,7 @@ pub fn eval_view_tui <'a, S> ( match frags.next() { Some("text") => { if let Some(src) = args?.src()? { - output.show(src) + to.show(src) } else { return Ok(None) } @@ -248,11 +248,10 @@ pub fn eval_view_tui <'a, S> ( Some("fg") => { let arg0 = arg0?.expect("fg: expected arg 0 (color)"); if let Some(color) = Namespace::namespace(state, arg0)? { - output.show(fg(color, thunk(move|output: &mut Tui|{ - state.interpret(output, &arg1)?; - // FIXME?: don't max out the used area? - Ok(Some(output.area().into())) - }))) + fg(color, thunk(move|to: &mut Tui|{ + state.interpret(to, &arg1)?; + Ok(Some(to.area().into())) // FIXME?: don't max out the used area? + })).draw(to) } else { return Err(format!("fg: {arg0:?}: not a color").into()) } @@ -261,11 +260,10 @@ pub fn eval_view_tui <'a, S> ( Some("bg") => { let arg0 = arg0?.expect("bg: expected arg 0 (color)"); if let Some(color) = Namespace::namespace(state, arg0)? { - output.show(bg(color, thunk(move|output: &mut Tui|{ - state.interpret(output, &arg1)?; - // FIXME?: don't max out the used area? - Ok(Some(output.area().into())) - }))) + bg(color, thunk(move|to: &mut Tui|{ + state.interpret(to, &arg1)?; + Ok(Some(to.area().into())) // FIXME?: don't max out the used area? + })).draw(to) } else { return Err(format!("bg: {arg0:?}: not a color").into()) } From 0a92184c444c8dae70146bd946f07bcf3008af5d Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Thu, 30 Jul 2026 14:52:35 +0300 Subject: [PATCH 08/14] disable unstable feature flags --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 155a53f..4b6fcfb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,11 @@ -#![feature(anonymous_lifetime_in_impl_trait)] +//#![feature(anonymous_lifetime_in_impl_trait)] //#![feature(associated_type_defaults)] //#![feature(const_default)] //#![feature(const_option_ops)] -#![feature(const_precise_live_drops)] -#![feature(const_trait_impl)] +//#![feature(const_precise_live_drops)] +//#![feature(const_trait_impl)] //#![feature(impl_trait_in_assoc_type)] -#![feature(step_trait)] +//#![feature(step_trait)] //#![feature(trait_alias)] //#![feature(type_alias_impl_trait)] //#![feature(type_changing_struct_update)] From 81bc0c67a3ddf69ea2636e1b5e1a0d3016e0704c Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Thu, 30 Jul 2026 14:52:41 +0300 Subject: [PATCH 09/14] bump dizzle --- dizzle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dizzle b/dizzle index 1f59b27..39eab8d 160000 --- a/dizzle +++ b/dizzle @@ -1 +1 @@ -Subproject commit 1f59b275a774871e23e8c08637862a11a83c2653 +Subproject commit 39eab8d0db2fcadcd7d5e661141a8791d780a876 From 5ca329292f808c137b6e2b7e80892e2a9e855696 Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Thu, 30 Jul 2026 16:38:32 +0300 Subject: [PATCH 10/14] fix warns --- dizzle | 2 +- src/draw.rs | 1 - src/draw/layout.rs | 2 ++ src/draw/space.rs | 2 -- src/term/buffer.rs | 3 +-- src/term/colors.rs | 2 +- src/term/keys.rs | 9 ++------- src/term/phat.rs | 2 -- src/term/repeat.rs | 2 +- src/term/scroll.rs | 6 +++--- src/text.rs | 6 +++++- 11 files changed, 16 insertions(+), 21 deletions(-) delete mode 100644 src/draw/space.rs diff --git a/dizzle b/dizzle index 39eab8d..0f06571 160000 --- a/dizzle +++ b/dizzle @@ -1 +1 @@ -Subproject commit 39eab8d0db2fcadcd7d5e661141a8791d780a876 +Subproject commit 0f06571f7fc7e5f87aadb82d531726d24bf769e8 diff --git a/src/draw.rs b/src/draw.rs index 6a7bc5b..36690f8 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -150,7 +150,6 @@ features! { layout, lrtb, sizer, - space, split, thunk, xywh diff --git a/src/draw/layout.rs b/src/draw/layout.rs index 28c9541..9074158 100644 --- a/src/draw/layout.rs +++ b/src/draw/layout.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + use crate::*; impl> Layout for T {} diff --git a/src/draw/space.rs b/src/draw/space.rs deleted file mode 100644 index 6576b44..0000000 --- a/src/draw/space.rs +++ /dev/null @@ -1,2 +0,0 @@ -use crate::*; - diff --git a/src/term/buffer.rs b/src/term/buffer.rs index fcd83f1..f95ab52 100644 --- a/src/term/buffer.rs +++ b/src/term/buffer.rs @@ -1,5 +1,4 @@ -use crate::*; -use crate::{*, lang::*, draw::*, task::*, exit::*}; +use crate::lang::*; use ::ratatui::buffer::Cell; /// TUI buffer sized by `usize` instead of `u16`. diff --git a/src/term/colors.rs b/src/term/colors.rs index f224b69..8d63fc2 100644 --- a/src/term/colors.rs +++ b/src/term/colors.rs @@ -1,6 +1,6 @@ use crate::*; use ratatui::prelude::Color; -use dizzle::{Ostensibly, Expression, LanguageError::*}; +use dizzle::{Expression, LanguageError::*}; pub trait ColorDsl: Sized { fn new_g (expr: T, try_to_u8: impl Fn(Perhaps<&str>)->Perhaps) -> Usually; diff --git a/src/term/keys.rs b/src/term/keys.rs index a0ccca8..f4ab04e 100644 --- a/src/term/keys.rs +++ b/src/term/keys.rs @@ -1,10 +1,5 @@ -use crate::{task::Task, term::TuiEvent}; -use ::std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}; -use ::std::time::Duration; -use ::dizzle::{Language, Symbol, Usually, Apply}; -use ::crossterm::event::{ - read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState -}; +use ::dizzle::{Language, Symbol, Usually}; +use ::crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; /// TUI key spec. #[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] diff --git a/src/term/phat.rs b/src/term/phat.rs index ec24914..9d3e6ed 100644 --- a/src/term/phat.rs +++ b/src/term/phat.rs @@ -1,5 +1,3 @@ -use crate::*; - /// Stackably padded. /// /// ``` diff --git a/src/term/repeat.rs b/src/term/repeat.rs index fe989e7..0ea6508 100644 --- a/src/term/repeat.rs +++ b/src/term/repeat.rs @@ -1,5 +1,5 @@ use crate::*; -use ratatui::{prelude::{Style, Position, Backend, Color}}; +use ratatui::{prelude::{Position}}; pub const fn x_repeat (c: &str) -> impl Draw { thunk(move|to: &mut Tui|{ diff --git a/src/term/scroll.rs b/src/term/scroll.rs index 3608237..8900360 100644 --- a/src/term/scroll.rs +++ b/src/term/scroll.rs @@ -1,5 +1,5 @@ use crate::*; -use ratatui::{prelude::{Style, Position, Backend, Color}}; +use ratatui::{prelude::{Position}}; pub const ICON_DEC_V: &[char] = &['▲']; pub const ICON_INC_V: &[char] = &['▼']; @@ -7,7 +7,7 @@ pub const ICON_DEC_H: &[char] = &[' ', '🞀', ' ']; pub const ICON_INC_H: &[char] = &[' ', '🞂', ' ']; pub fn x_scroll () -> impl Draw { - thunk(|Tui(buf, XYWH(x1, y1, w, h)): &mut Tui|{ + thunk(|Tui(buf, XYWH(x1, y1, w, _h)): &mut Tui|{ let x2 = *x1 + *w; for (i, x) in (*x1..=x2).enumerate() { if let Some(cell) = buf.cell_mut(Position::from((x, *y1))) { @@ -35,7 +35,7 @@ pub fn x_scroll () -> impl Draw { } pub fn y_scroll () -> impl Draw { - thunk(|Tui(buf, XYWH(x1, y1, w, h)): &mut Tui|{ + thunk(|Tui(buf, XYWH(x1, y1, _w, h)): &mut Tui|{ let y2 = *y1 + *h; for (i, y) in (*y1..=y2).enumerate() { if let Some(cell) = buf.cell_mut(Position::from((*x1, y))) { diff --git a/src/text.rs b/src/text.rs index 9c00c64..07d438c 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + use crate::*; pub(crate) use ::unicode_width::*; @@ -96,7 +98,9 @@ pub fn trim_string (max_width: usize, input: impl AsRef) -> String { pub struct TrimString>(pub u16, pub T); impl> AsRef for TrimString { fn as_ref (&self) -> &str { self.1.as_ref() } } impl<'a, T: AsRef> TrimString { - fn to_ref (&self) -> TrimStringRef<'_, T> { TrimStringRef(self.0, &self.1) } + fn to_ref (&self) -> TrimStringRef<'_, T> { + TrimStringRef(self.0, &self.1) + } } /// Displays a borrowed [str]-like with fixed maximum width From cd5b0cc1139a4ca62024e9f1d995cc1536593f16 Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Thu, 30 Jul 2026 17:44:40 +0300 Subject: [PATCH 11/14] wip: doctest layouts --- examples/mode_01.rs | 8 ++++++ src/draw.rs | 16 +++++++---- src/draw/layout.rs | 69 +++++++++++++++++++++++++-------------------- 3 files changed, 57 insertions(+), 36 deletions(-) diff --git a/examples/mode_01.rs b/examples/mode_01.rs index 39031c4..0ad5241 100644 --- a/examples/mode_01.rs +++ b/examples/mode_01.rs @@ -44,6 +44,14 @@ impl Interpret for State { } } } +fn try_to_u8 (src: Perhaps<&str>) -> Perhaps { + use std::str::FromStr; + if let Some(src) = src? { + Ok(Some(u8::from_str(src)?)) + } else { + Ok(None) + } +} impl Interpret>> for State { fn interpret_word (&self, to: &mut Tui, sym: &impl Language) -> Perhaps> { match sym.src()? { diff --git a/src/draw.rs b/src/draw.rs index 36690f8..9d27ad3 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -13,16 +13,20 @@ use crate::*; /// } /// impl Screen for TestOut { /// type Unit = u16; -/// fn show (&mut self, _: impl Draw) -> Perhaps> -/// { println!("placed"); Ok(None) } -/// fn area (&self) -> XYWH -/// { Default::default() } +/// fn show (&mut self, _: impl Draw) -> Perhaps> { +/// println!("placed"); +/// Ok(None) +/// } +/// fn area (&self) -> XYWH { +/// Default::default() +/// } /// fn clip ( /// &mut self, /// area: impl Into>>, /// draw: impl FnOnce(&mut Self)->T -/// ) -> T -/// { draw(self } +/// ) -> T { +/// draw(self) +/// } /// } /// /// impl_draw!(|self: String, to: TestOut|{ diff --git a/src/draw/layout.rs b/src/draw/layout.rs index 9074158..ecc1316 100644 --- a/src/draw/layout.rs +++ b/src/draw/layout.rs @@ -160,10 +160,14 @@ pub trait Layout: Draw + Sized { /// Use whole drawing area along one or both axes. /// /// ``` -/// use tengri::Layout; -/// let _ = "".full_w(); -/// let _ = "".full_h(); -/// let _ = "".full_wh(); +/// # fn doctest_layout_full () -> Result<(), Box> { +/// use tengri::{Layout, Draw, XYWH}; +/// let area = XYWH(0u16, 0, 80, 25); +/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1))); +/// assert_eq!("1".full_w().layout(area)?, Some(XYWH(0u16, 0, 80, 1))); +/// assert_eq!("1".full_h().layout(area)?, Some(XYWH(0u16, 0, 1, 25))); +/// assert_eq!("1".full_wh().layout(area)?, Some(XYWH(0u16, 0, 80, 25))); +/// # Ok(()) } /// ``` pub enum Full> { __(PhantomData), @@ -171,7 +175,6 @@ pub enum Full> { H(I), WH(I), } - impl_draw!(,>|self: Full, to: T|{ let XYWH(x0, y0, w0, h0) = to.area(); match self { @@ -197,10 +200,14 @@ impl_draw!(,>|self: Full, to: T|{ /// Move content in the positive direction of one or both axes. /// /// ``` -/// use tengri::Layout; -/// let _ = "".push_x(1); -/// let _ = "".push_y(1); -/// let _ = "".push_xy(1, 1); +/// # fn doctest_layout_push () -> Result<(), Box> { +/// use tengri::{Layout, Draw, XYWH}; +/// let area = XYWH(0u16, 0, 80, 25); +/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1))); +/// assert_eq!("1".push_x(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1))); +/// assert_eq!("1".push_y(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1))); +/// assert_eq!("1".push_xy(1, 1).layout(area)?, Some(XYWH(1u16, 1, 1, 1))); +/// # Ok(()) } /// ``` pub enum Push, X: Into>> { __(PhantomData), @@ -208,7 +215,6 @@ pub enum Push, X: Into>> { Y(I, X), XY(I, X, X), } - impl_draw!(, X: Into>,>|self: Push, to: T|{ match self { Self::__(_) => unreachable!(), @@ -234,10 +240,14 @@ impl_draw!(, X: Into>,>|self: Push Result<(), Box> { +/// use tengri::{Layout, Draw, XYWH}; +/// let area = XYWH(1u16, 1, 80, 25); +/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1))); +/// assert_eq!("1".pull_x(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1))); +/// assert_eq!("1".pull_y(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1))); +/// assert_eq!("1".pull_xy(1, 1).layout(area)?, Some(XYWH(0u16, 0, 1, 1))); +/// # } /// ``` pub enum Pull, X: Into>> { __(PhantomData), @@ -245,7 +255,6 @@ pub enum Pull, X: Into>> { Y(I, X), XY(I, X, X), } - impl_draw!(, X: Into>,>|self: Pull, _to: T|{ todo!() }); @@ -253,10 +262,14 @@ impl_draw!(, X: Into>,>|self: Pull Result<(), Box> { +/// use tengri::{Layout, Draw, XYWH}; +/// let area = XYWH(1u16, 1, 80, 25); +/// assert_eq!("1".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 5, 1))); +/// assert_eq!("1".min_h(5).layout(area)?, Some(XYWH(1u16, 1, 1, 5))); +/// assert_eq!("1".min_wh(5, 5).layout(area)?, Some(XYWH(1u16, 1, 5, 5))); +/// assert_eq!("123456".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 6, 1))); +/// # Ok(()) } /// ``` pub enum Min, X: Into>> { __(PhantomData), @@ -264,7 +277,6 @@ pub enum Min, X: Into>> { H(I, X), WH(I, X, X), } - impl_draw!(, X: Into>,>|self: Min, _to: T|{ todo!() }); @@ -272,10 +284,13 @@ impl_draw!(, X: Into>,>|self: Min /// Set maximum size of of drawing area. /// /// ``` -/// use tengri::Layout; -/// let _ = "".max_w(1); -/// let _ = "".max_h(1); -/// let _ = "".max_wh(1, 1); +/// # fn doctest_layout_max () -> Result<(), Box> { +/// use tengri::{Layout, Draw, XYWH}; +/// let area = XYWH(1u16, 1, 80, 25); +/// assert_eq!("12345".max_w(1).layout(area)?, Some(XYWH(1u16, 1, 1, 1))); +/// assert_eq!("12345".max_h(1).layout(area)?, Some(XYWH(1u16, 1, 1, 1))); +/// assert_eq!("12345".max_wh(1, 1).layout(area)?, Some(XYWH(1u16, 1, 5, 1))); +/// # Ok(()) } /// ``` pub enum Max, X: Into>> { __(PhantomData), @@ -283,7 +298,6 @@ pub enum Max, X: Into>> { H(I, X), WH(I, X, X), } - impl_draw!(, X: Into>,>|self: Max, to: T|{ let area: XYWH = to.area(); let (item, area) = match self { @@ -318,7 +332,6 @@ pub enum Exact, X: Into>> { H(I, X), WH(I, X, X), } - impl_draw!(, X: Into>,>|self: Exact, to: T|{ let area: XYWH = to.area(); let (item, area) = match self { @@ -347,13 +360,11 @@ pub enum Pad, X: Into>> { H(I, X), WH(I, X, X), } - impl_draw!(, X: Into>,>|self: Pad, _to: T|{ todo!() }); pub struct Align(Option, T); - impl_draw!(,>|self: Align, to: S|{ use Azimuth::*; let XYWH(x0, y0, w0, h0) = to.area(); @@ -405,13 +416,11 @@ pub struct Area>( pub Option>, pub T ); - impl_draw!(,>|self: Area, to: S|{ to.clip(self.0, |to|self.1.draw(to)) }); pub struct Origin(Option, T); - impl_draw!(,>|self: Origin, _to: S|{ todo!() }); From 8d2445d728df0eeaa7481cc4612f0a98ef7b968c Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Thu, 30 Jul 2026 18:01:19 +0300 Subject: [PATCH 12/14] wip: layout doctests 2 --- src/draw/layout.rs | 2 +- src/eval.rs | 12 ++---------- src/sing.rs | 2 +- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/draw/layout.rs b/src/draw/layout.rs index ecc1316..fba4d9b 100644 --- a/src/draw/layout.rs +++ b/src/draw/layout.rs @@ -247,7 +247,7 @@ impl_draw!(, X: Into>,>|self: Push, X: Into>> { __(PhantomData), diff --git a/src/eval.rs b/src/eval.rs index 950b40c..5359216 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -190,18 +190,10 @@ pub fn eval_view <'a, O: Screen + 'a, S> ( /// use tengri::{*, lang::*, ratatui::prelude::Color}; /// /// #[namespace(bool)] -/// #[namespace(u8 try_to_u8)] -/// #[namespace(u16 try_to_u16)] +/// #[namespace(u8)] +/// #[namespace(u16)] /// #[namespace(Color try_to_color)] -/// #[interpret(Tui -> Option>: try_eval_tui)] /// struct State; -/// tengri::lang::primitive!(u8: try_to_u8); -/// tengri::lang::primitive!(u16: try_to_u16); -/// tengri::lang::interpret!(|self: State, context: Tui, lang|->Option>{ -/// expression = { -/// "text" (...rest) => { todo!() } -/// } -/// }); /// impl Interpret>> for State { /// fn interpret_expr <'a> (&'a self, _: &mut Tui, lang: &'a impl Expression) /// -> Usually>> diff --git a/src/sing.rs b/src/sing.rs index 6149532..d7d4469 100644 --- a/src/sing.rs +++ b/src/sing.rs @@ -803,7 +803,7 @@ pub trait AddMidiOut { /// Port connection manager. /// /// ``` -/// let connect = tek::Connect::default(); +/// let connect = tengri::Connect::default(); /// ``` #[derive(Clone, Debug, Default)] pub struct Connect { From 859b173a1ea3e645df0cb91d6d008bc002f38aef Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Thu, 30 Jul 2026 18:05:32 +0300 Subject: [PATCH 13/14] test: add get_color (bulky!) --- src/eval.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/eval.rs b/src/eval.rs index 5359216..e0df0f1 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -192,8 +192,9 @@ pub fn eval_view <'a, O: Screen + 'a, S> ( /// #[namespace(bool)] /// #[namespace(u8)] /// #[namespace(u16)] -/// #[namespace(Color try_to_color)] +/// #[namespace(Color get_color)] /// struct State; +/// /// impl Interpret>> for State { /// fn interpret_expr <'a> (&'a self, _: &mut Tui, lang: &'a impl Expression) /// -> Usually>> @@ -202,6 +203,36 @@ pub fn eval_view <'a, O: Screen + 'a, S> ( /// } /// } /// +/// fn get_color (state: &State, src: impl Language) -> Perhaps { +/// if let Some(expr) = src.expr()? { +/// match (expr.head()?, expr.tail()?) { +/// (Some("g"), Some(tail)) => { +/// let n: u8 = state.namespace(tail.head().map_err(Into::into))?.ok_or(LanguageError::Domain("not gray"))?; +/// Ok(Some(Color::Rgb(n, n, n))) +/// }, +/// (Some("rgb"), Some(tail)) => { +/// let r: u8 = state.namespace(tail.head().map_err(Into::into))? +/// .ok_or(LanguageError::Domain("not red"))?; +/// let g: u8 = state.namespace(tail.tail().head().map_err(Into::into))? +/// .ok_or(LanguageError::Domain("not green"))?; +/// let b: u8 = state.namespace(tail.tail().tail().head().map_err(Into::into))? +/// .ok_or(LanguageError::Domain("not blue"))?; +/// Ok(Some(Color::Rgb(r, g, b))) +/// }, +/// (Some(_), _) => return Err(format!("not a color expression: {expr}").into()), +/// (None, _) => return Err(format!("not a color expression: {expr}").into()), +/// } +/// } else if let Ok(Some(sym)) = src.word() { +/// Ok(match sym { +/// ":color/bg" => Some(Color::Rgb(28, 32, 36)), +/// ":color/fg" => Some(Color::Rgb(98, 92, 96)), +/// _ => return Err(format!("not a color: {sym}").into()) +/// }) +/// } else { +/// return Err(format!("not a color: {:?}", src.src()?).into()) +/// } +/// } +/// /// # fn main () -> tengri::Usually<()> { /// let state = State; /// let mut out = Tui::new(80, 25); From 94c26f06ccf77bfd6dbe55665f58e87d0b0b2216 Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Thu, 30 Jul 2026 22:07:42 +0300 Subject: [PATCH 14/14] update example modes --- Justfile | 8 +- examples/mode_00.rs | 40 ++++++- examples/mode_01.rs | 174 +++++++---------------------- examples/mode_02.rs | 267 ++++++++++++++++++++++++++------------------ examples/mode_03.rs | 108 ++++++++++++++++++ 5 files changed, 347 insertions(+), 250 deletions(-) create mode 100644 examples/mode_03.rs diff --git a/Justfile b/Justfile index 23af11d..83c5796 100644 --- a/Justfile +++ b/Justfile @@ -29,11 +29,9 @@ doc: CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' RUSTDOCFLAGS='-Cinstrument-coverage' \ cargo doc -example-tui-00: +mode-00: cargo run --example mode_00 - -example-tui-01: +mode-01: cargo run --example mode_01 - -example-tui-02: +mode-02: cargo run --example mode_02 diff --git a/examples/mode_00.rs b/examples/mode_00.rs index 9ab858c..31b148d 100644 --- a/examples/mode_00.rs +++ b/examples/mode_00.rs @@ -1,3 +1,37 @@ -//! Mode 0: Direct draw -use ::{std::sync::{Arc, RwLock}, ratatui::style::Color, tengri::*}; -fn main () {} +//! Mode 00: Direct draw, direct control + +use ::std::sync::{Arc, RwLock}; +use ::crossterm::event::{Event::*, KeyEvent, KeyCode::*}; +use ::ratatui::style::Color; +use ::tengri::{*, lang::*}; + +tui_app!(State { + /** User-controllable value. */ + cursor: usize, +}); + +tui_view!(self: State { + thunk(|to: &mut Tui|{ + let cursor = format!("Cursor: {}", self.cursor); + let _ = "DEMO [MODE 00]".align_sw().draw(to); + let _ = ShowSize.align_se().draw(to); + let _ = format!("Cursor: {}", self.cursor).align_c().draw(to); + Ok(Some(to.area())) + }) +}); + +tui_keys!(self: State, input { + Ok(if let Key(KeyEvent { code, .. }) = input.0 { + match code { + Up | Right => { + self.cursor = (self.cursor + 1) % 10; + () + }, + Down | Left => { + self.cursor = if self.cursor > 0 { self.cursor - 1 } else { 10 - 1 }; + () + }, + _ => {} + } + }) +}); diff --git a/examples/mode_01.rs b/examples/mode_01.rs index 0ad5241..d7ee626 100644 --- a/examples/mode_01.rs +++ b/examples/mode_01.rs @@ -1,161 +1,63 @@ -//! Mode 01 +//! Mode 01: Direct view, actions with history + use ::std::sync::{Arc, RwLock}; use ::crossterm::event::{Event::*, KeyEvent, KeyCode::*}; use ::ratatui::style::Color; use ::tengri::{*, lang::*}; +use itertools::Itertools; + tui_app!(State { /** Command history (undo/redo). */ history: Vec, /** User-controllable value. */ cursor: usize, - /** Rendered window size. */ - size: Sizer, }); + tui_keys!(self: State, input { Ok(if let Key(KeyEvent { code, .. }) = input.0 { match code { - Up | Right => { self.next()?.map(|x|self.history.push(x)); }, - Down | Left => { self.prev()?.map(|x|self.history.push(x)); }, - _ => {} + Down | Right => Action::Next, + Up | Left => Action::Prev, + _ => { return Ok(()) } } + .apply(self)? + .map(|x|self.history.push(x)); }) }); + tui_view!(self: State { - let index = self.cursor + 1; - let wh = (self.size.w(), self.size.h()); - let src = VIEWS.get(self.cursor).unwrap_or(&""); - let heading = format!("State {}/{} in {:?}", index, VIEWS.len(), &wh); - let title = bg(Color::Rgb(60, 10, 10), heading.align_n().push_y(1)); - let code = bg(Color::Rgb(10, 60, 10), format!("{}", src).align_n().push_y(2)); - let widget = thunk(move|to: &mut Tui|self.interpret(to, &src)); - self.size.of(south(title, north(code, widget))) + let title = "Demo Mode 00"; + let items = self.history.iter().take(10).map(|x|format!("{x:?}")).join("\n"); + let history = format!("History: {}\n{items}", self.history.len()); + let cursor = format!("Cursor: {}", self.cursor); + north( + east(title.align_sw(), ShowSize.align_se()), + east(history.align_c(), cursor.align_c()), + ) }); -impl Interpret for State { - fn interpret_expr (&self, to: &mut Tui, expr: &impl Language) -> Usually { - let expr = expr.expr()?; - match expr.head()? { - Some("g") if let Some(tail) = expr.tail()? => { - Color::new_g(tail.head()?, try_to_u8) - }, - Some("rgb") if let Some(tail) = expr.tail()? => { - Color::new_rgb(tail.head()?, try_to_u8) - }, - _ => Err(format!("not a color").into()) - } - } -} -fn try_to_u8 (src: Perhaps<&str>) -> Perhaps { - use std::str::FromStr; - if let Some(src) = src? { - Ok(Some(u8::from_str(src)?)) - } else { - Ok(None) - } -} -impl Interpret>> for State { - fn interpret_word (&self, to: &mut Tui, sym: &impl Language) -> Perhaps> { - match sym.src()? { - Some(":foo") => "foo".draw(to), - Some(":bar") => "bar".draw(to), - Some(":foobar") => "FOOBAR".draw(to), - _ => todo!() - } - } - fn interpret_expr (&self, to: &mut Tui, src: &impl Expression) -> Perhaps> { - Ok(Some(if let Some(area) = eval_view(self, to, src)? { - area - } else if let Some(area) = eval_view_tui(self, to, src)? { - area - } else { - return Err(format!("App::interpret_expr: unexpected: {src:?}").into()) - })) - } -} -impl State { - fn next (&mut self) -> Perhaps { - self.cursor = (self.cursor + 1) % VIEWS.len(); - Ok(Some(Action::Prev)) - } - fn prev (&mut self) -> Perhaps { - self.cursor = if self.cursor > 0 { self.cursor - 1 } else { VIEWS.len() - 1 }; - Ok(Some(Action::Next)) - } -} -#[derive(Debug)] -enum Action { + +#[derive(Debug)] enum Action { /** Increment cursor */ Next, /** Decrement cursor */ Prev, } + impl Action { - fn eval (&self, state: &mut State) -> Perhaps { + fn apply (&self, state: &mut State) -> Perhaps { use Action::*; - match self { Next => state.next(), Prev => state.prev(), } + match self { + Next => state.next(), + Prev => state.prev(), + } + } +} + +impl State { + fn next (&mut self) -> Perhaps { + self.cursor = (self.cursor + 1) % 10; + Ok(Some(Action::Prev)) + } + fn prev (&mut self) -> Perhaps { + self.cursor = if self.cursor > 0 { self.cursor - 1 } else { 10 - 1 }; + Ok(Some(Action::Next)) } } -const VIEWS: &'static [&'static str] = &[ - stringify! { :foobar }, - stringify! { (bg (g 8) :foobar) }, - stringify! { (fill/xy :foobar) }, - stringify! { (bsp/s :foo :bar) }, - stringify! { (fixed/xy 20 10 :foobar) }, - stringify! { (bsp/s (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) }, - stringify! { (bsp/e (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) }, - stringify! { (bsp/n (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) }, - stringify! { (bsp/w (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) }, - stringify! { (bsp/a (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) }, - stringify! { (bsp/b (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) }, - stringify! { - (bsp/s - (bsp/e (align/nw (fixed/xy 5 3 :foo)) - (bsp/e (align/n (fixed/xy 5 3 :foo)) - (align/ne (fixed/xy 5 3 :foo)))) - (bsp/s - (bsp/e (align/w (fixed/xy 5 3 :foo)) - (bsp/e (align/c (fixed/xy 5 3 :foo)) - (align/e (fixed/xy 5 3 :foo)))) - (bsp/e (align/sw (fixed/xy 5 3 :foo)) - (bsp/e (align/s (fixed/xy 5 3 :foo)) - (align/se (fixed/xy 5 3 :foo)))))) - }, - stringify! { - (bsp/s - (bsp/e (fixed/xy 8 5 (align/nw :foo)) - (bsp/e (fixed/xy 8 5 (align/n :foo)) - (fixed/xy 8 5 (align/ne :foo)))) - (bsp/s - (bsp/e (fixed/xy 8 5 (align/w :foo)) - (bsp/e (fixed/xy 8 5 (align/c :foo)) - (fixed/xy 8 5 (align/e :foo)))) - (bsp/e (fixed/xy 8 5 (align/sw :foo)) - (bsp/e (fixed/xy 8 5 (align/s :foo)) - (fixed/xy 8 5 (align/se :foo)))))) - }, - stringify! { - (bsp/s - (bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/nw :foo))) - (bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/n :foo))) - (grow/xy 1 1 (fixed/xy 8 5 (align/ne :foo))))) - (bsp/s - (bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/w :foo))) - (bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/c :foo))) - (grow/xy 1 1 (fixed/xy 8 5 (align/e :foo))))) - (bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/sw :foo))) - (bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/s :foo))) - (grow/xy 1 1 (fixed/xy 8 5 (align/se :foo))))))) - }, - stringify! { :map-e }, - stringify! { (align/c :map-e) }, - stringify! { :map-s }, - stringify! { (align/c :map-s) }, - stringify! { - (align/c (bg/behind :bg0 (margin/xy 1 1 (col - (bg/behind :bg1 (border/around :border1 (margin/xy 2 1 :label1))) - (bg/behind :bg2 (border/around :border2 (margin/xy 4 2 :label2))) - (bg/behind :bg3 (border/around :border3 (margin/xy 6 3 :label3))))))) - }, -]; -//handle!(TuiIn: |self: State, input|Action::from(input).eval(self).map(|_|None)); - //view!(State: Tui: [ evaluate_output_expression, evaluate_output_expression_tui ]); - //draw!(State: Tui: [ draw_example ]); -//impl_from!(Action: |input: &TuiIn| todo!()); -//fn draw_example (state: &State, to: &mut Tui) {} diff --git a/examples/mode_02.rs b/examples/mode_02.rs index 40fba02..70cec30 100644 --- a/examples/mode_02.rs +++ b/examples/mode_02.rs @@ -1,107 +1,162 @@ -//! Mode 02 -use ::{std::sync::{Arc, RwLock}, ratatui::style::Color, tengri::*}; -fn main () {} +//! Mode 02: Inline Dizzle config -//#[tengri_proc::expose] -//impl Example { - //fn _todo_u16_stub (&self) -> u16 { todo!() } - //fn _todo_bool_stub (&self) -> bool { todo!() } - //fn _todo_usize_stub (&self) -> usize { todo!() } - ////[bool] => {} - ////[u16] => {} - ////[usize] => {} -//} - -//#[tengri_proc::view(TuiOut)] -//impl Example { - //pub fn title (&self) -> impl Content + use<'_> { - //Tui::bg(Color::Rgb(60, 10, 10), Push::y(1, Align::n(format!("Example {}/{}:", self.0 + 1, VIEWS.len())))).boxed() - //} - //pub fn code (&self) -> impl Content + use<'_> { - //Tui::bg(Color::Rgb(10, 60, 10), Push::y(2, Align::n(format!("{}", VIEWS[self.0])))).boxed() - //} - //pub fn hello (&self) -> impl Content + use<'_> { - //Tui::bg(Color::Rgb(10, 100, 10), "Hello").boxed() - //} - //pub fn world (&self) -> impl Content + use<'_> { - //Tui::bg(Color::Rgb(100, 10, 10), "world").boxed() - //} - //pub fn hello_world (&self) -> impl Content + use<'_> { - //"Hello world!".boxed() - //} - //pub fn map_e (&self) -> impl Content + use<'_> { - //Map::east(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed() - //} - //pub fn map_s (&self) -> impl Content + use<'_> { - //Map::south(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed() - //} -//} - - //fn content (&self) -> dyn Draw { - //let border_style = Style::default().fg(Color::Rgb(0,0,0)); - //Align::Center(Layers::new(move|add|{ - - //add(&Background(Color::Rgb(0,128,128)))?; - - //add(&Margin::XY(1, 1, Stack::down(|add|{ - - //add(&Layers::new(|add|{ - //add(&Background(Color::Rgb(128,96,0)))?; - //add(&Border(Square(border_style)))?; - //add(&Margin::XY(2, 1, "..."))?; - //Ok(()) - //}).debug())?; - - //add(&Layers::new(|add|{ - //add(&Background(Color::Rgb(128,64,0)))?; - //add(&Border(Lozenge(border_style)))?; - //add(&Margin::XY(4, 2, "---"))?; - //Ok(()) - //}).debug())?; - - //add(&Layers::new(|add|{ - //add(&Background(Color::Rgb(96,64,0)))?; - //add(&Border(SquareBold(border_style)))?; - //add(&Margin::XY(6, 3, "~~~"))?; - //Ok(()) - //}).debug())?; - - //Ok(()) - //})).debug())?; - - //Ok(()) - - //})) - ////Align::Center(Margin::X(1, Layers::new(|add|{ - ////add(&Background(Color::Rgb(128,0,0)))?; - ////add(&Stack::down(|add|{ - ////add(&Margin::Y(1, Layers::new(|add|{ - ////add(&Background(Color::Rgb(0,128,0)))?; - ////add(&Align::Center("12345"))?; - ////add(&Align::Center("FOO")) - ////})))?; - ////add(&Margin::XY(1, 1, Layers::new(|add|{ - ////add(&Align::Center("1234567"))?; - ////add(&Align::Center("BAR"))?; - ////add(&Background(Color::Rgb(0,0,128))) - ////}))) - ////})) - ////}))) - - ////Align::Y(Layers::new(|add|{ - ////add(&Background(Color::Rgb(128,0,0)))?; - ////add(&Margin::X(1, Align::Center(Stack::down(|add|{ - ////add(&Align::X(Margin::Y(1, Layers::new(|add|{ - ////add(&Background(Color::Rgb(0,128,0)))?; - ////add(&Align::Center("12345"))?; - ////add(&Align::Center("FOO")) - ////})))?; - ////add(&Margin::XY(1, 1, Layers::new(|add|{ - ////add(&Align::Center("1234567"))?; - ////add(&Align::Center("BAR"))?; - ////add(&Background(Color::Rgb(0,0,128))) - ////})))?; - ////Ok(()) - ////}))))) - ////})) - //} +use ::std::sync::{Arc, RwLock}; +use ::crossterm::event::{Event::*, KeyEvent, KeyCode::*}; +use ::ratatui::style::Color; +use ::tengri::{*, lang::*}; +tui_app!(State { + /** Command history (undo/redo). */ + history: Vec, + /** User-controllable value. */ + cursor: usize, + /** Rendered window size. */ + size: Sizer, +}); +tui_keys!(self: State, input { + Ok(if let Key(KeyEvent { code, .. }) = input.0 { + match code { + Up | Right => { self.next()?.map(|x|self.history.push(x)); }, + Down | Left => { self.prev()?.map(|x|self.history.push(x)); }, + _ => {} + } + }) +}); +tui_view!(self: State { + let index = self.cursor + 1; + let wh = (self.size.w(), self.size.h()); + let src = VIEWS.get(self.cursor).unwrap_or(&""); + let heading = format!("State {}/{} in {:?}", index, VIEWS.len(), &wh); + let title = bg(Color::Rgb(60, 10, 10), heading.align_n().push_y(1)); + let code = bg(Color::Rgb(10, 60, 10), format!("{}", src).align_n().push_y(2)); + let widget = thunk(move|to: &mut Tui|self.interpret(to, &src)); + self.size.of(south(title, north(code, widget))) +}); +impl Interpret for State { + fn interpret_expr (&self, to: &mut Tui, expr: &impl Language) -> Usually { + let expr = expr.expr()?; + match expr.head()? { + Some("g") if let Some(tail) = expr.tail()? => { + Color::new_g(tail.head()?, try_to_u8) + }, + Some("rgb") if let Some(tail) = expr.tail()? => { + Color::new_rgb(tail.head()?, try_to_u8) + }, + _ => Err(format!("not a color").into()) + } + } +} +fn try_to_u8 (src: Perhaps<&str>) -> Perhaps { + use std::str::FromStr; + if let Some(src) = src? { + Ok(Some(u8::from_str(src)?)) + } else { + Ok(None) + } +} +impl Interpret>> for State { + fn interpret_word (&self, to: &mut Tui, sym: &impl Language) -> Perhaps> { + match sym.src()? { + Some(":foo") => "foo".draw(to), + Some(":bar") => "bar".draw(to), + Some(":foobar") => "FOOBAR".draw(to), + _ => todo!() + } + } + fn interpret_expr (&self, to: &mut Tui, src: &impl Expression) -> Perhaps> { + Ok(Some(if let Some(area) = eval_view(self, to, src)? { + area + } else if let Some(area) = eval_view_tui(self, to, src)? { + area + } else { + return Err(format!("App::interpret_expr: unexpected: {src:?}").into()) + })) + } +} +impl State { + fn next (&mut self) -> Perhaps { + self.cursor = (self.cursor + 1) % VIEWS.len(); + Ok(Some(Action::Prev)) + } + fn prev (&mut self) -> Perhaps { + self.cursor = if self.cursor > 0 { self.cursor - 1 } else { VIEWS.len() - 1 }; + Ok(Some(Action::Next)) + } +} +#[derive(Debug)] +enum Action { + /** Increment cursor */ Next, + /** Decrement cursor */ Prev, +} +impl Action { + fn eval (&self, state: &mut State) -> Perhaps { + use Action::*; + match self { Next => state.next(), Prev => state.prev(), } + } +} +const VIEWS: &'static [&'static str] = &[ + stringify! { :foobar }, + stringify! { (bg (g 8) :foobar) }, + stringify! { (fill/xy :foobar) }, + stringify! { (bsp/s :foo :bar) }, + stringify! { (fixed/xy 20 10 :foobar) }, + stringify! { (bsp/s (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) }, + stringify! { (bsp/e (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) }, + stringify! { (bsp/n (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) }, + stringify! { (bsp/w (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) }, + stringify! { (bsp/a (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) }, + stringify! { (bsp/b (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) }, + stringify! { + (bsp/s + (bsp/e (align/nw (fixed/xy 5 3 :foo)) + (bsp/e (align/n (fixed/xy 5 3 :foo)) + (align/ne (fixed/xy 5 3 :foo)))) + (bsp/s + (bsp/e (align/w (fixed/xy 5 3 :foo)) + (bsp/e (align/c (fixed/xy 5 3 :foo)) + (align/e (fixed/xy 5 3 :foo)))) + (bsp/e (align/sw (fixed/xy 5 3 :foo)) + (bsp/e (align/s (fixed/xy 5 3 :foo)) + (align/se (fixed/xy 5 3 :foo)))))) + }, + stringify! { + (bsp/s + (bsp/e (fixed/xy 8 5 (align/nw :foo)) + (bsp/e (fixed/xy 8 5 (align/n :foo)) + (fixed/xy 8 5 (align/ne :foo)))) + (bsp/s + (bsp/e (fixed/xy 8 5 (align/w :foo)) + (bsp/e (fixed/xy 8 5 (align/c :foo)) + (fixed/xy 8 5 (align/e :foo)))) + (bsp/e (fixed/xy 8 5 (align/sw :foo)) + (bsp/e (fixed/xy 8 5 (align/s :foo)) + (fixed/xy 8 5 (align/se :foo)))))) + }, + stringify! { + (bsp/s + (bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/nw :foo))) + (bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/n :foo))) + (grow/xy 1 1 (fixed/xy 8 5 (align/ne :foo))))) + (bsp/s + (bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/w :foo))) + (bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/c :foo))) + (grow/xy 1 1 (fixed/xy 8 5 (align/e :foo))))) + (bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/sw :foo))) + (bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/s :foo))) + (grow/xy 1 1 (fixed/xy 8 5 (align/se :foo))))))) + }, + stringify! { :map-e }, + stringify! { (align/c :map-e) }, + stringify! { :map-s }, + stringify! { (align/c :map-s) }, + stringify! { + (align/c (bg/behind :bg0 (margin/xy 1 1 (col + (bg/behind :bg1 (border/around :border1 (margin/xy 2 1 :label1))) + (bg/behind :bg2 (border/around :border2 (margin/xy 4 2 :label2))) + (bg/behind :bg3 (border/around :border3 (margin/xy 6 3 :label3))))))) + }, +]; +//handle!(TuiIn: |self: State, input|Action::from(input).eval(self).map(|_|None)); + //view!(State: Tui: [ evaluate_output_expression, evaluate_output_expression_tui ]); + //draw!(State: Tui: [ draw_example ]); +//impl_from!(Action: |input: &TuiIn| todo!()); +//fn draw_example (state: &State, to: &mut Tui) {} diff --git a/examples/mode_03.rs b/examples/mode_03.rs new file mode 100644 index 0000000..30eec8c --- /dev/null +++ b/examples/mode_03.rs @@ -0,0 +1,108 @@ +//! Mode 03: Hot reloaded Dizzle config + +use ::{std::sync::{Arc, RwLock}, ratatui::style::Color, tengri::*}; +fn main () {} + +//#[tengri_proc::expose] +//impl Example { + //fn _todo_u16_stub (&self) -> u16 { todo!() } + //fn _todo_bool_stub (&self) -> bool { todo!() } + //fn _todo_usize_stub (&self) -> usize { todo!() } + ////[bool] => {} + ////[u16] => {} + ////[usize] => {} +//} + +//#[tengri_proc::view(TuiOut)] +//impl Example { + //pub fn title (&self) -> impl Content + use<'_> { + //Tui::bg(Color::Rgb(60, 10, 10), Push::y(1, Align::n(format!("Example {}/{}:", self.0 + 1, VIEWS.len())))).boxed() + //} + //pub fn code (&self) -> impl Content + use<'_> { + //Tui::bg(Color::Rgb(10, 60, 10), Push::y(2, Align::n(format!("{}", VIEWS[self.0])))).boxed() + //} + //pub fn hello (&self) -> impl Content + use<'_> { + //Tui::bg(Color::Rgb(10, 100, 10), "Hello").boxed() + //} + //pub fn world (&self) -> impl Content + use<'_> { + //Tui::bg(Color::Rgb(100, 10, 10), "world").boxed() + //} + //pub fn hello_world (&self) -> impl Content + use<'_> { + //"Hello world!".boxed() + //} + //pub fn map_e (&self) -> impl Content + use<'_> { + //Map::east(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed() + //} + //pub fn map_s (&self) -> impl Content + use<'_> { + //Map::south(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed() + //} +//} + + //fn content (&self) -> dyn Draw { + //let border_style = Style::default().fg(Color::Rgb(0,0,0)); + //Align::Center(Layers::new(move|add|{ + + //add(&Background(Color::Rgb(0,128,128)))?; + + //add(&Margin::XY(1, 1, Stack::down(|add|{ + + //add(&Layers::new(|add|{ + //add(&Background(Color::Rgb(128,96,0)))?; + //add(&Border(Square(border_style)))?; + //add(&Margin::XY(2, 1, "..."))?; + //Ok(()) + //}).debug())?; + + //add(&Layers::new(|add|{ + //add(&Background(Color::Rgb(128,64,0)))?; + //add(&Border(Lozenge(border_style)))?; + //add(&Margin::XY(4, 2, "---"))?; + //Ok(()) + //}).debug())?; + + //add(&Layers::new(|add|{ + //add(&Background(Color::Rgb(96,64,0)))?; + //add(&Border(SquareBold(border_style)))?; + //add(&Margin::XY(6, 3, "~~~"))?; + //Ok(()) + //}).debug())?; + + //Ok(()) + //})).debug())?; + + //Ok(()) + + //})) + ////Align::Center(Margin::X(1, Layers::new(|add|{ + ////add(&Background(Color::Rgb(128,0,0)))?; + ////add(&Stack::down(|add|{ + ////add(&Margin::Y(1, Layers::new(|add|{ + ////add(&Background(Color::Rgb(0,128,0)))?; + ////add(&Align::Center("12345"))?; + ////add(&Align::Center("FOO")) + ////})))?; + ////add(&Margin::XY(1, 1, Layers::new(|add|{ + ////add(&Align::Center("1234567"))?; + ////add(&Align::Center("BAR"))?; + ////add(&Background(Color::Rgb(0,0,128))) + ////}))) + ////})) + ////}))) + + ////Align::Y(Layers::new(|add|{ + ////add(&Background(Color::Rgb(128,0,0)))?; + ////add(&Margin::X(1, Align::Center(Stack::down(|add|{ + ////add(&Align::X(Margin::Y(1, Layers::new(|add|{ + ////add(&Background(Color::Rgb(0,128,0)))?; + ////add(&Align::Center("12345"))?; + ////add(&Align::Center("FOO")) + ////})))?; + ////add(&Margin::XY(1, 1, Layers::new(|add|{ + ////add(&Align::Center("1234567"))?; + ////add(&Align::Center("BAR"))?; + ////add(&Background(Color::Rgb(0,0,128))) + ////})))?; + ////Ok(()) + ////}))))) + ////})) + //}