From b294f2e62b970cf4e1cb133346132a0a113af575 Mon Sep 17 00:00:00 2001 From: same mf who else Date: Sat, 21 Feb 2026 18:13:21 +0200 Subject: [PATCH] add Tui::new, remove View --- tengri/tengri.rs | 40 +++++----------------------------------- tengri/tengri_impl.rs | 36 ++++++++++++++++++++++++++++++++++-- tengri/tengri_struct.rs | 1 + tengri/tengri_trait.rs | 20 -------------------- 4 files changed, 40 insertions(+), 57 deletions(-) diff --git a/tengri/tengri.rs b/tengri/tengri.rs index b1f8b58..e488c78 100644 --- a/tengri/tengri.rs +++ b/tengri/tengri.rs @@ -107,7 +107,7 @@ pub(crate) use ::std::{ #[cfg(feature = "dsl")] pub fn evaluate_output_expression <'a, O: Out + 'a, S> ( state: &S, output: &mut O, expr: &'a impl Expression ) -> Usually where - S: View + S: Understand + for<'b>Namespace<'b, bool> + for<'b>Namespace<'b, O::Unit> { @@ -282,7 +282,7 @@ pub(crate) use ::std::{ #[macro_export] macro_rules! tui_main { ($expr:expr) => { fn main () -> Usually<()> { - tengri::Tui::run(true, $expr)?; + tengri::Tui::new(stdout()).run(true, $expr)?; Ok(()) } }; @@ -323,37 +323,6 @@ macro_rules! border { )+} } -/// Run an app in the main loop. -pub fn tui_run + Handle + 'static> ( - join: bool, - state: &Arc> -) -> Usually>> { - let backend = CrosstermBackend::new(stdout()); - let Size { width, height } = backend.size()?; - let tui = Arc::new(RwLock::new(Tui { - exited: Arc::new(AtomicBool::new(false)), - buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }), - area: [0, 0, width, height], - perf: Default::default(), - backend, - })); - let _input_thread = tui_input(tui.clone(), state, Duration::from_millis(100)); - tui.write().unwrap().setup()?; - let render_thread = tui_output(tui.clone(), state, Duration::from_millis(10))?; - if join { - match render_thread.join() { - Ok(result) => { - tui.write().unwrap().teardown()?; - println!("\n\rRan successfully: {result:?}\n\r"); - }, - Err(error) => { - tui.write().unwrap().teardown()?; - panic!("\n\rDraw thread failed: error={error:?}.\n\r") - }, - } - } - Ok(tui) -} pub fn tui_setup ( backend: &mut CrosstermBackend @@ -487,7 +456,7 @@ pub fn tui_input + Send + Sync + 'static> ( #[cfg(feature = "dsl")] pub fn evaluate_output_expression_tui <'a, S> ( state: &S, output: &mut TuiOut, expr: impl Expression + 'a ) -> Usually where - S: View + S: Understand + for<'b>Namespace<'b, bool> + for<'b>Namespace<'b, u16> + for<'b>Namespace<'b, Color> @@ -909,7 +878,8 @@ pub(crate) fn width_chars_max (max: u16, text: impl AsRef) -> u16 { Ok(None) } } - let engine = Tui::run(false, TestComponent("hello world".into()))?; + let mut output = String::new(); + let engine = Tui::new(&mut output).run(false, TestComponent("hello world".into()))?; engine.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed); //engine.run(&state)?; Ok(()) diff --git a/tengri/tengri_impl.rs b/tengri/tengri_impl.rs index 3edc2b6..8387290 100644 --- a/tengri/tengri_impl.rs +++ b/tengri/tengri_impl.rs @@ -983,11 +983,38 @@ impl PerfModel { } impl Tui { + pub fn new (output: Stdout) -> Usually { + let backend = CrosstermBackend::new(output); + let Size { width, height } = backend.size()?; + Ok(Self { + exited: Arc::new(AtomicBool::new(false)), + buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }), + area: [0, 0, width, height], + perf: Default::default(), + backend, + }) + } /// Create and launch a terminal user interface. - pub fn run (join: bool, state: T) -> Usually>> where + pub fn run (self, join: bool, state: &Arc>) -> Usually>> where T: Handle + Draw + Send + Sync + 'static { - tui_run(join, &Arc::new(RwLock::new(state))) + let tui = Arc::new(RwLock::new(self)); + let _input_thread = tui_input(tui.clone(), state, Duration::from_millis(100)); + tui.write().unwrap().setup()?; + let render_thread = tui_output(tui.clone(), state, Duration::from_millis(10))?; + if join { + match render_thread.join() { + Ok(result) => { + tui.write().unwrap().teardown()?; + println!("\n\rRan successfully: {result:?}\n\r"); + }, + Err(error) => { + tui.write().unwrap().teardown()?; + panic!("\n\rDraw thread failed: error={error:?}.\n\r") + }, + } + } + Ok(tui) } /// True if done pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) } @@ -1021,6 +1048,11 @@ impl TuiEvent { Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self)) } } +impl From for TuiEvent { + fn from (c: char) -> Self { + Self(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE))) + } +} impl TuiKey { const SPLIT: char = '/'; #[cfg(feature = "dsl")] pub fn from_dsl (dsl: impl Language) -> Usually { diff --git a/tengri/tengri_struct.rs b/tengri/tengri_struct.rs index ae56877..59ae480 100644 --- a/tengri/tengri_struct.rs +++ b/tengri/tengri_struct.rs @@ -14,6 +14,7 @@ pub struct Tui { pub perf: PerfModel, } + #[derive(Debug, Clone)] pub struct TuiIn { /// Input event pub event: TuiEvent, diff --git a/tengri/tengri_trait.rs b/tengri/tengri_trait.rs index 8ac11ed..e03ce0f 100644 --- a/tengri/tengri_trait.rs +++ b/tengri/tengri_trait.rs @@ -112,26 +112,6 @@ pub trait Draw { fn draw (&self, to: &mut O); } -/// FIXME: This is a general implementation: should be called `Eval` and be part of [dizzle]. -/// Matches [Language] expressions to renderings for a given [Output] target. -pub trait View { - fn view_expr <'a> (&'a self, _output: &mut O, expr: &'a impl Expression) -> Usually { - Err(format!("View::view_expr: no exprs defined: {expr:?}").into()) - } - fn view_word <'a> (&'a self, _output: &mut O, word: &'a impl Symbol) -> Usually { - Err(format!("View::view_word: no words defined: {word:?}").into()) - } - fn view <'a> (&'a self, output: &mut O, dsl: &'a impl Language) -> Usually { - match (dsl.expr(), dsl.word()) { - (Ok(Some(e)), _ ) => self.view_expr(output, &e), - (_, Ok(Some(w))) => self.view_word(output, &w), - (Err(e), _ ) => Err(format!("invalid view expr:\n{dsl:?}\n{e}").into()), - (_, Err(w) ) => Err(format!("invalid view word:\n{dsl:?}\n{w}").into()), - (Ok(None), Ok(None) ) => Err(format!("empty view:\n{dsl:?}").into()), - } - } -} - /// Outputs combinator. pub trait Lay: Sized {}