add Tui::new, remove View

This commit is contained in:
same mf who else 2026-02-21 18:13:21 +02:00
parent 85ccb0737f
commit b294f2e62b
4 changed files with 40 additions and 57 deletions

View file

@ -107,7 +107,7 @@ pub(crate) use ::std::{
#[cfg(feature = "dsl")] pub fn evaluate_output_expression <'a, O: Out + 'a, S> ( #[cfg(feature = "dsl")] pub fn evaluate_output_expression <'a, O: Out + 'a, S> (
state: &S, output: &mut O, expr: &'a impl Expression state: &S, output: &mut O, expr: &'a impl Expression
) -> Usually<bool> where ) -> Usually<bool> where
S: View<O, ()> S: Understand<O, ()>
+ for<'b>Namespace<'b, bool> + for<'b>Namespace<'b, bool>
+ for<'b>Namespace<'b, O::Unit> + for<'b>Namespace<'b, O::Unit>
{ {
@ -282,7 +282,7 @@ pub(crate) use ::std::{
#[macro_export] macro_rules! tui_main { #[macro_export] macro_rules! tui_main {
($expr:expr) => { ($expr:expr) => {
fn main () -> Usually<()> { fn main () -> Usually<()> {
tengri::Tui::run(true, $expr)?; tengri::Tui::new(stdout()).run(true, $expr)?;
Ok(()) Ok(())
} }
}; };
@ -323,37 +323,6 @@ macro_rules! border {
)+} )+}
} }
/// Run an app in the main loop.
pub fn tui_run <T: Send + Sync + Draw<TuiOut> + Handle<TuiIn> + 'static> (
join: bool,
state: &Arc<RwLock<T>>
) -> Usually<Arc<RwLock<Tui>>> {
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 <W: Write> ( pub fn tui_setup <W: Write> (
backend: &mut CrosstermBackend<W> backend: &mut CrosstermBackend<W>
@ -487,7 +456,7 @@ pub fn tui_input <T: Handle<TuiIn> + Send + Sync + 'static> (
#[cfg(feature = "dsl")] pub fn evaluate_output_expression_tui <'a, S> ( #[cfg(feature = "dsl")] pub fn evaluate_output_expression_tui <'a, S> (
state: &S, output: &mut TuiOut, expr: impl Expression + 'a state: &S, output: &mut TuiOut, expr: impl Expression + 'a
) -> Usually<bool> where ) -> Usually<bool> where
S: View<TuiOut, ()> S: Understand<TuiOut, ()>
+ for<'b>Namespace<'b, bool> + for<'b>Namespace<'b, bool>
+ for<'b>Namespace<'b, u16> + for<'b>Namespace<'b, u16>
+ for<'b>Namespace<'b, Color> + for<'b>Namespace<'b, Color>
@ -909,7 +878,8 @@ pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
Ok(None) 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.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed);
//engine.run(&state)?; //engine.run(&state)?;
Ok(()) Ok(())

View file

@ -983,11 +983,38 @@ impl PerfModel {
} }
impl Tui { impl Tui {
pub fn new (output: Stdout) -> Usually<Self> {
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. /// Create and launch a terminal user interface.
pub fn run <T> (join: bool, state: T) -> Usually<Arc<RwLock<Self>>> where pub fn run <T> (self, join: bool, state: &Arc<RwLock<T>>) -> Usually<Arc<RwLock<Self>>> where
T: Handle<TuiIn> + Draw<TuiOut> + Send + Sync + 'static T: Handle<TuiIn> + Draw<TuiOut> + 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 /// True if done
pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) } 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)) Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self))
} }
} }
impl From<char> for TuiEvent {
fn from (c: char) -> Self {
Self(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE)))
}
}
impl TuiKey { impl TuiKey {
const SPLIT: char = '/'; const SPLIT: char = '/';
#[cfg(feature = "dsl")] pub fn from_dsl (dsl: impl Language) -> Usually<Self> { #[cfg(feature = "dsl")] pub fn from_dsl (dsl: impl Language) -> Usually<Self> {

View file

@ -14,6 +14,7 @@ pub struct Tui {
pub perf: PerfModel, pub perf: PerfModel,
} }
#[derive(Debug, Clone)] pub struct TuiIn { #[derive(Debug, Clone)] pub struct TuiIn {
/// Input event /// Input event
pub event: TuiEvent, pub event: TuiEvent,

View file

@ -112,26 +112,6 @@ pub trait Draw<O: Out> {
fn draw (&self, to: &mut O); 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<O, U> {
fn view_expr <'a> (&'a self, _output: &mut O, expr: &'a impl Expression) -> Usually<U> {
Err(format!("View::view_expr: no exprs defined: {expr:?}").into())
}
fn view_word <'a> (&'a self, _output: &mut O, word: &'a impl Symbol) -> Usually<U> {
Err(format!("View::view_word: no words defined: {word:?}").into())
}
fn view <'a> (&'a self, output: &mut O, dsl: &'a impl Language) -> Usually<U> {
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. /// Outputs combinator.
pub trait Lay<O: Out>: Sized {} pub trait Lay<O: Out>: Sized {}