mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-02-21 18:49:04 +01:00
refactor(output,tui): fix errors, now there's more...
Some checks failed
/ build (push) Has been cancelled
Some checks failed
/ build (push) Has been cancelled
This commit is contained in:
parent
4fa5d74fa2
commit
2dc74657d5
9 changed files with 713 additions and 754 deletions
196
tui/src/lib.rs
196
tui/src/lib.rs
|
|
@ -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<()> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue