diff --git a/benches/tui_update.rs b/benches/tui_update.rs index 6c77c9132..cbc8ca467 100644 --- a/benches/tui_update.rs +++ b/benches/tui_update.rs @@ -1,15 +1,13 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use dioxus::prelude::*; -use dioxus_tui::TuiContext; +use dioxus_tui::{Config, TuiContext}; -criterion_group!(mbenches, update_boxes); +criterion_group!(mbenches, tui_update); criterion_main!(mbenches); /// This benchmarks the cache performance of the TUI for small edits by changing one box at a time. -fn update_boxes(c: &mut Criterion) { +fn tui_update(c: &mut Criterion) { let mut group = c.benchmark_group("Update boxes"); - // We can override the configuration on a per-group level - group.sample_size(10); // We can also use loops to define multiple benchmarks, even over multiple dimensions. for size in 1..=6 { @@ -19,12 +17,48 @@ fn update_boxes(c: &mut Criterion) { &size, |b, size| { b.iter(|| match size { - 1 => dioxus::tui::launch(app5), - 2 => dioxus::tui::launch(app10), - 3 => dioxus::tui::launch(app15), - 4 => dioxus::tui::launch(app20), - 5 => dioxus::tui::launch(app25), - 6 => dioxus::tui::launch(app30), + 1 => dioxus::tui::launch_cfg( + app5, + Config { + headless: true, + ..Default::default() + }, + ), + 2 => dioxus::tui::launch_cfg( + app10, + Config { + headless: true, + ..Default::default() + }, + ), + 3 => dioxus::tui::launch_cfg( + app15, + Config { + headless: true, + ..Default::default() + }, + ), + 4 => dioxus::tui::launch_cfg( + app20, + Config { + headless: true, + ..Default::default() + }, + ), + 5 => dioxus::tui::launch_cfg( + app25, + Config { + headless: true, + ..Default::default() + }, + ), + 6 => dioxus::tui::launch_cfg( + app30, + Config { + headless: true, + ..Default::default() + }, + ), _ => (), }) }, diff --git a/packages/tui/src/config.rs b/packages/tui/src/config.rs index 924163a7d..6e839d8a7 100644 --- a/packages/tui/src/config.rs +++ b/packages/tui/src/config.rs @@ -1,9 +1,11 @@ #[derive(Clone, Copy)] pub struct Config { pub rendering_mode: RenderingMode, - /// Should the terminal quit when the user presses `ctrl+c`? + /// Controls if the terminal quit when the user presses `ctrl+c`? /// To handle quiting on your own, use the [crate::TuiContext] root context. pub ctrl_c_quit: bool, + /// Controls if the terminal should dislay anything, usefull for testing. + pub headless: bool, } impl Default for Config { @@ -11,6 +13,7 @@ impl Default for Config { Self { rendering_mode: Default::default(), ctrl_c_quit: true, + headless: false, } } } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 0d8687b09..03e137111 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -15,7 +15,7 @@ use layout::StretchLayout; use std::{io, time::Duration}; use stretch2::{prelude::Size, Stretch}; use style_attributes::StyleModifier; -use tui::{backend::CrosstermBackend, Terminal}; +use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; mod config; mod hooks; @@ -51,18 +51,20 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { // Setup input handling let (event_tx, event_rx) = unbounded(); let event_tx_clone = event_tx.clone(); - std::thread::spawn(move || { - let tick_rate = Duration::from_millis(1000); - loop { - if crossterm::event::poll(tick_rate).unwrap() { - // if crossterm::event::poll(timeout).unwrap() { - let evt = crossterm::event::read().unwrap(); - if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() { - break; + if !cfg.headless { + std::thread::spawn(move || { + let tick_rate = Duration::from_millis(1000); + loop { + if crossterm::event::poll(tick_rate).unwrap() { + // if crossterm::event::poll(timeout).unwrap() { + let evt = crossterm::event::read().unwrap(); + if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() { + break; + } } } - } - }); + }); + } let cx = dom.base_scope(); cx.provide_root_context(state); @@ -101,13 +103,17 @@ fn render_vdom( .enable_all() .build()? .block_on(async { - enable_raw_mode().unwrap(); - let mut stdout = std::io::stdout(); - execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap(); - let backend = CrosstermBackend::new(io::stdout()); - let mut terminal = Terminal::new(backend).unwrap(); + let mut terminal = (!cfg.headless).then(|| { + enable_raw_mode().unwrap(); + let mut stdout = std::io::stdout(); + execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap(); + let backend = CrosstermBackend::new(io::stdout()); + Terminal::new(backend).unwrap() + }); + if let Some(terminal) = &mut terminal { + terminal.clear().unwrap(); + } - terminal.clear().unwrap(); let mut to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); let mut resized = true; @@ -125,9 +131,11 @@ fn render_vdom( if !to_rerender.is_empty() || resized { resized = false; - terminal.draw(|frame| { - // size is guaranteed to not change when rendering - let dims = frame.size(); + fn resize( + dims: Rect, + stretch: &mut Stretch, + rdom: &RealDom, + ) { let width = dims.width; let height = dims.height; let root_id = rdom.root_id(); @@ -142,9 +150,26 @@ fn render_vdom( }, ) .unwrap(); - let root = &rdom[rdom.root_id()]; - render::render_vnode(frame, &stretch, &rdom, &root, cfg); - })?; + } + if let Some(terminal) = &mut terminal { + terminal.draw(|frame| { + // size is guaranteed to not change when rendering + resize(frame.size(), &mut stretch, &rdom); + let root = &rdom[rdom.root_id()]; + render::render_vnode(frame, &stretch, &rdom, &root, cfg); + })?; + } else { + resize( + Rect { + x: 0, + y: 0, + width: 300, + height: 300, + }, + &mut stretch, + &rdom, + ); + } } use futures::future::{select, Either}; @@ -198,13 +223,15 @@ fn render_vdom( } } - disable_raw_mode()?; - execute!( - terminal.backend_mut(), - LeaveAlternateScreen, - DisableMouseCapture - )?; - terminal.show_cursor()?; + if let Some(terminal) = &mut terminal { + disable_raw_mode()?; + execute!( + terminal.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + )?; + terminal.show_cursor()?; + } Ok(()) })