refactor(output,tui): fix errors, now there's more...
Some checks failed
/ build (push) Has been cancelled

This commit is contained in:
same mf who else 2026-02-17 04:09:10 +02:00
parent 4fa5d74fa2
commit 2dc74657d5
9 changed files with 713 additions and 754 deletions

View file

@ -23,8 +23,8 @@ pub(crate) use ::{
better_panic::{Settings, Verbosity},
palette::{*, convert::*, okhsl::*},
ratatui::{
prelude::{Color, Style, Buffer},
style::Modifier,
prelude::{Color, Style, Buffer, Position},
style::{Stylize, Modifier, Color::*},
backend::{Backend, CrosstermBackend, ClearType},
layout::{Size, Rect},
buffer::Cell
@ -85,8 +85,155 @@ mod tui_structs; pub use self::tui_structs::*;
mod tui_traits; pub use self::tui_traits::*;
mod tui_impls; pub use self::tui_impls::*;
#[cfg(feature = "dsl")]
pub fn evaluate_output_expression_tui <'a, S> (
/// Run an app in the main loop.
pub fn tui_run <T>(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, state, Duration::from_millis(100));
tui.write().unwrap().setup()?;
let render_thread = tui_output(tui, state, Duration::from_millis(10))?;
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) -> 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)?;
backend.hide_cursor()?;
enable_raw_mode().map_err(Into::into)
}
pub fn tui_teardown (backend: &mut CrosstermBackend) -> Usually<()> {
stdout().execute(LeaveAlternateScreen)?;
backend.show_cursor()?;
disable_raw_mode().map_err(Into::into)
}
pub fn tui_resized (
backend: &mut CrosstermBackend, buffer: &mut Buffer, size: ratatui::prelude::Rect
) {
if buffer.area != size {
backend.clear_region(ClearType::All).unwrap();
buffer.resize(size);
buffer.reset();
}
}
pub fn tui_redrawn (
backend: &mut CrosstermBackend, buffer: &mut Buffer, new_buffer: &mut Buffer
) {
let updates = buffer.diff(&new_buffer);
backend.draw(updates.into_iter()).expect("failed to render");
backend.flush().expect("failed to flush output new_buffer");
std::mem::swap(&mut buffer, &mut new_buffer);
new_buffer.reset();
}
pub fn tui_update (buf: &mut Buffer, area: XY<u16>, callback: &impl Fn(&mut Cell, u16, u16)) {
for row in 0..area.h() {
let y = area.y() + row;
for col in 0..area.w() {
let x = area.x() + col;
if x < buf.area.width && y < buf.area.height {
if let Some(cell) = buf.cell_mut(ratatui::prelude::Position { x, y }) {
callback(cell, col, row);
}
}
}
}
}
/// Spawn the output thread.
pub fn tui_output <T: Draw<TuiOut> + Send + Sync + 'static> (
engine: &Arc<RwLock<Tui>>, state: &Arc<RwLock<T>>, timer: Duration
) -> Result<JoinHandle<()>, std::io::Error> {
let exited = engine.read().unwrap().exited.clone();
let engine = engine.clone();
let state = state.clone();
let Size { width, height } = engine.read().unwrap().backend.size().expect("get size failed");
let mut buffer = Buffer::empty(Rect { x: 0, y: 0, width, height });
std::thread::Builder::new()
.name("tui output thread".into())
.spawn(move || loop {
if exited.fetch_and(true, Relaxed) {
break
}
let t0 = engine.read().unwrap().perf.get_t0();
let Size { width, height } = engine.read().unwrap().backend.size()
.expect("get size failed");
if let Ok(state) = state.try_read() {
let size = Rect { x: 0, y: 0, width, height };
if buffer.area != size {
engine.write().unwrap().backend.clear_region(ClearType::All).expect("clear failed");
buffer.resize(size);
buffer.reset();
}
let mut output = TuiOut { buffer, area: XYWH(0, 0, width, height) };
state.draw(&mut output);
buffer = engine.write().unwrap().flip(output.buffer, size);
}
let t1 = (*engine.read().unwrap()).perf.get_t1(t0).unwrap();
buffer.set_string(0, 0, &format!("{:>3}.{:>3}ms", t1.as_millis(), t1.as_micros() % 1000), Style::default());
std::thread::sleep(timer);
})
}
/// Spawn the input thread.
pub fn tui_input <T: Handle<TuiIn> + Send + Sync + 'static> (
engine: &Arc<RwLock<Tui>>, state: &Arc<RwLock<T>>, timer: Duration
) -> JoinHandle<()> {
let exited = engine.read().unwrap().exited.clone();
let state = state.clone();
spawn(move || loop {
if exited.fetch_and(true, Relaxed) {
break
}
if poll(timer).is_ok() {
let event = read().unwrap();
match event {
Event::Key(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
}) => {
exited.store(true, Relaxed);
},
_ => {
let exited = exited.clone();
let event = TuiEvent::from_crossterm(event);
if let Err(e) = state.write().unwrap().handle(&TuiIn { exited, event }) {
panic!("{e}")
}
}
}
}
})
}
#[cfg(feature = "dsl")] pub fn evaluate_output_expression_tui <'a, S> (
state: &S, output: &mut TuiOut, expr: impl Expression + 'a
) -> Usually<bool> where
S: View<TuiOut, ()>
@ -296,6 +443,47 @@ border! {
}
}
pub fn okhsl_to_rgb (color: Okhsl<f32>) -> Color {
let Srgb { red, green, blue, .. }: Srgb<f32> = Srgb::from_color_unclamped(color);
Color::Rgb((red * 255.0) as u8, (green * 255.0) as u8, (blue * 255.0) as u8,)
}
pub fn rgb_to_okhsl (color: Color) -> Okhsl<f32> {
if let Color::Rgb(r, g, b) = color {
Okhsl::from_color(Srgb::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0))
} else {
unreachable!("only Color::Rgb is supported")
}
}
/// Trim string with [unicode_width].
pub fn trim_string (max_width: usize, input: impl AsRef<str>) -> String {
let input = input.as_ref();
let mut output = Vec::with_capacity(input.len());
let mut width: usize = 1;
let mut chars = input.chars();
while let Some(c) = chars.next() {
if width > max_width {
break
}
output.push(c);
width += c.width().unwrap_or(0);
}
return output.into_iter().collect()
}
pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
let mut width: u16 = 0;
let mut chars = text.as_ref().chars();
while let Some(c) = chars.next() {
width += c.width().unwrap_or(0) as u16;
if width > max {
break
}
}
return width
}
#[cfg(test)] mod tui_test {
use crate::*;
#[test] fn test_tui_engine () -> Usually<()> {