use crate::*; use std::time::Duration; mod tui_buffer; pub use self::tui_buffer::*; mod tui_input; pub use self::tui_input::*; mod tui_event; pub use self::tui_event::*; mod tui_output; pub use self::tui_output::*; mod tui_perf; pub use self::tui_perf::*; // The `Tui` struct (the *engine*) implements the // `tengri_input::Input` and `tengri_output::Out` traits. // At launch, the `Tui` engine spawns two threads, the render thread and the input thread. // the application may further spawn other threads. All threads communicate using shared ownership: // `Arc>` and `Arc`. Thus, at launch the engine and application instances are expected to be wrapped in `Arc`. pub struct Tui { pub exited: Arc, pub backend: CrosstermBackend, pub buffer: Buffer, pub area: [u16;4], pub perf: PerfModel, } impl Tui { /// Construct a new TUI engine and wrap it for shared ownership. pub fn new () -> Usually>> { let backend = CrosstermBackend::new(stdout()); let Size { width, height } = backend.size()?; Ok(Arc::new(RwLock::new(Self { exited: Arc::new(AtomicBool::new(false)), buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }), area: [0, 0, width, height], backend, perf: Default::default(), }))) } /// True if done pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) } /// Prepare before run pub fn setup (&mut self) -> Usually<()> { let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler(); std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{ stdout().execute(LeaveAlternateScreen).unwrap(); CrosstermBackend::new(stdout()).show_cursor().unwrap(); disable_raw_mode().unwrap(); better_panic_handler(info); })); stdout().execute(EnterAlternateScreen)?; self.backend.hide_cursor()?; enable_raw_mode().map_err(Into::into) } /// Update the display buffer. pub fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer { if self.buffer.area != size { self.backend.clear_region(ClearType::All).unwrap(); self.buffer.resize(size); self.buffer.reset(); } let updates = self.buffer.diff(&buffer); self.backend.draw(updates.into_iter()).expect("failed to render"); self.backend.flush().expect("failed to flush output buffer"); std::mem::swap(&mut self.buffer, &mut buffer); buffer.reset(); buffer } /// Clean up after run pub fn teardown (&mut self) -> Usually<()> { stdout().execute(LeaveAlternateScreen)?; self.backend.show_cursor()?; disable_raw_mode().map_err(Into::into) } } pub trait TuiDraw = Draw; pub trait TuiLayout = Layout; pub trait TuiContent = Content; pub trait TuiHandle = Handle; pub trait TuiWidget = TuiDraw + TuiHandle; pub trait TuiRun { /// Run an app in the main loop. fn run (&self, state: &Arc>) -> Usually<()>; } impl TuiRun for Arc> { fn run (&self, state: &Arc>) -> Usually<()> { let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100)); self.write().unwrap().setup()?; let render_thread = TuiOut::run_output(self, state, Duration::from_millis(10))?; match render_thread.join() { Ok(result) => { self.write().unwrap().teardown()?; println!("\n\rRan successfully: {result:?}\n\r"); }, Err(error) => { self.write().unwrap().teardown()?; panic!("\n\rDraw thread failed: error={error:?}.\n\r") }, } Ok(()) } } #[cfg(feature = "dsl")] pub fn evaluate_output_expression_tui <'a, S> ( state: &S, output: &mut TuiOut, expr: impl Expression + 'a ) -> Usually where S: View + for<'b>Namespace<'b, bool> + for<'b>Namespace<'b, u16> + for<'b>Namespace<'b, Color> { // See `tengri_output::evaluate_output_expression` let head = expr.head()?; let mut frags = head.src()?.unwrap_or_default().split("/"); let args = expr.tail(); let arg0 = args.head(); let tail0 = args.tail(); let arg1 = tail0.head(); let tail1 = tail0.tail(); let _arg2 = tail1.head(); match frags.next() { Some("text") => if let Some(src) = args?.src()? { output.place(&src) }, Some("fg") => { let arg0 = arg0?.expect("fg: expected arg 0 (color)"); output.place(&Tui::fg( Namespace::::resolve(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color")), Thunk::new(move|output: &mut TuiOut|state.view(output, &arg1).unwrap()), )) }, Some("bg") => { let arg0 = arg0?.expect("bg: expected arg 0 (color)"); output.place(&Tui::bg( Namespace::::resolve(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color")), Thunk::new(move|output: &mut TuiOut|state.view(output, &arg1).unwrap()), )) }, _ => return Ok(false) }; Ok(true) }