mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-02-21 10:39:03 +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
|
|
@ -9,8 +9,10 @@ bumpalo = [ "dep:bumpalo" ]
|
||||||
dsl = []
|
dsl = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dizzle = { path = "../../dizzle" }
|
atomic_float = { workspace = true }
|
||||||
bumpalo = { optional = true, workspace = true }
|
bumpalo = { workspace = true, optional = true }
|
||||||
|
dizzle = { path = "../../dizzle" }
|
||||||
|
quanta = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tengri = { path = "../tengri", features = [ "dsl", "tui" ] }
|
tengri = { path = "../tengri", features = [ "dsl", "tui" ] }
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ pub(crate) use std::ops::{Add, Sub, Mul, Div};
|
||||||
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, Ordering::Relaxed}};
|
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, Ordering::Relaxed}};
|
||||||
pub(crate) use std::marker::PhantomData;
|
pub(crate) use std::marker::PhantomData;
|
||||||
pub(crate) use dizzle::*;
|
pub(crate) use dizzle::*;
|
||||||
|
pub(crate) use quanta::Clock;
|
||||||
|
pub(crate) use atomic_float::AtomicF64;
|
||||||
|
|
||||||
// Define macros first, so that private macros are available in private modules:
|
// Define macros first, so that private macros are available in private modules:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,68 +41,68 @@ impl<O: Out> HasWH<O::Unit> for O {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: Coord> WH<N> {
|
impl<N: Coord> WH<N> {
|
||||||
fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] }
|
pub fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] }
|
||||||
fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] }
|
pub fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] }
|
||||||
fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
|
pub fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
|
||||||
if self.w() < w || self.h() < h { return Err(format!("min {w}x{h}").into()) }
|
if self.w() < w || self.h() < h { return Err(format!("min {w}x{h}").into()) }
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<N: Coord> XYWH<N> {
|
impl<N: Coord> XYWH<N> {
|
||||||
fn zero () -> Self {
|
pub fn zero () -> Self {
|
||||||
Self(0.into(), 0.into(), 0.into(), 0.into())
|
Self(0.into(), 0.into(), 0.into(), 0.into())
|
||||||
}
|
}
|
||||||
fn x2 (&self) -> N {
|
pub fn x2 (&self) -> N {
|
||||||
self.x().plus(self.w())
|
self.x().plus(self.w())
|
||||||
}
|
}
|
||||||
fn y2 (&self) -> N {
|
pub fn y2 (&self) -> N {
|
||||||
self.y().plus(self.h())
|
self.y().plus(self.h())
|
||||||
}
|
}
|
||||||
fn with_w (&self, w: N) -> XYWH<N> {
|
pub fn with_w (&self, w: N) -> XYWH<N> {
|
||||||
Self(self.x(), self.y(), w, self.h())
|
Self(self.x(), self.y(), w, self.h())
|
||||||
}
|
}
|
||||||
fn with_h (&self, h: N) -> XYWH<N> {
|
pub fn with_h (&self, h: N) -> XYWH<N> {
|
||||||
Self(self.x(), self.y(), self.w(), h)
|
Self(self.x(), self.y(), self.w(), h)
|
||||||
}
|
}
|
||||||
fn lrtb (&self) -> XYWH<N> {
|
pub fn lrtb (&self) -> XYWH<N> {
|
||||||
Self(self.x(), self.x2(), self.y(), self.y2())
|
Self(self.x(), self.x2(), self.y(), self.y2())
|
||||||
}
|
}
|
||||||
fn clipped_w (&self, w: N) -> XYWH<N> {
|
pub fn clipped_w (&self, w: N) -> XYWH<N> {
|
||||||
Self(self.x(), self.y(), self.w().min(w), self.h())
|
Self(self.x(), self.y(), self.w().min(w), self.h())
|
||||||
}
|
}
|
||||||
fn clipped_h (&self, h: N) -> XYWH<N> {
|
pub fn clipped_h (&self, h: N) -> XYWH<N> {
|
||||||
Self(self.x(), self.y(), self.w(), self.h().min(h))
|
Self(self.x(), self.y(), self.w(), self.h().min(h))
|
||||||
}
|
}
|
||||||
fn clipped (&self, wh: WH<N>) -> XYWH<N> {
|
pub fn clipped (&self, wh: WH<N>) -> XYWH<N> {
|
||||||
Self(self.x(), self.y(), wh.w(), wh.h())
|
Self(self.x(), self.y(), wh.w(), wh.h())
|
||||||
}
|
}
|
||||||
/// Iterate over every covered X coordinate.
|
/// Iterate over every covered X coordinate.
|
||||||
fn iter_x (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
|
pub fn iter_x (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
|
||||||
let Self(x, _, w, _) = *self;
|
let Self(x, _, w, _) = *self;
|
||||||
x..(x+w)
|
x..(x+w)
|
||||||
}
|
}
|
||||||
/// Iterate over every covered Y coordinate.
|
/// Iterate over every covered Y coordinate.
|
||||||
fn iter_y (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
|
pub fn iter_y (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
|
||||||
let Self(_, y, _, h) = *self;
|
let Self(_, y, _, h) = *self;
|
||||||
y..(y+h)
|
y..(y+h)
|
||||||
}
|
}
|
||||||
fn center (&self) -> XY<N> {
|
pub fn center (&self) -> XY<N> {
|
||||||
let Self(x, y, w, h) = self;
|
let Self(x, y, w, h) = self;
|
||||||
XY(self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into()))
|
XY(self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into()))
|
||||||
}
|
}
|
||||||
fn centered (&self) -> XY<N> {
|
pub fn centered (&self) -> XY<N> {
|
||||||
let Self(x, y, w, h) = *self;
|
let Self(x, y, w, h) = *self;
|
||||||
XY(x.minus(w/2.into()), y.minus(h/2.into()))
|
XY(x.minus(w/2.into()), y.minus(h/2.into()))
|
||||||
}
|
}
|
||||||
fn centered_x (&self, n: N) -> XYWH<N> {
|
pub fn centered_x (&self, n: N) -> XYWH<N> {
|
||||||
let Self(x, y, w, h) = *self;
|
let Self(x, y, w, h) = *self;
|
||||||
XYWH((x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into())
|
XYWH((x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into())
|
||||||
}
|
}
|
||||||
fn centered_y (&self, n: N) -> XYWH<N> {
|
pub fn centered_y (&self, n: N) -> XYWH<N> {
|
||||||
let Self(x, y, w, h) = *self;
|
let Self(x, y, w, h) = *self;
|
||||||
XYWH(x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n)
|
XYWH(x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n)
|
||||||
}
|
}
|
||||||
fn centered_xy (&self, [n, m]: [N;2]) -> XYWH<N> {
|
pub fn centered_xy (&self, [n, m]: [N;2]) -> XYWH<N> {
|
||||||
let Self(x, y, w, h) = *self;
|
let Self(x, y, w, h) = *self;
|
||||||
XYWH((x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m)
|
XYWH((x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m)
|
||||||
}
|
}
|
||||||
|
|
@ -847,3 +847,51 @@ impl<C, T, U> Field<C, T, U> {
|
||||||
Field::<C, T, V> { value, value_fg: fg, value_bg: bg, value_align: align, ..self }
|
Field::<C, T, V> { value, value_fg: fg, value_bg: bg, value_align: align, ..self }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for PerfModel {
|
||||||
|
fn default () -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: true,
|
||||||
|
clock: quanta::Clock::new(),
|
||||||
|
used: Default::default(),
|
||||||
|
window: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PerfModel {
|
||||||
|
pub fn get_t0 (&self) -> Option<u64> {
|
||||||
|
if self.enabled {
|
||||||
|
Some(self.clock.raw())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_t1 (&self, t0: Option<u64>) -> Option<std::time::Duration> {
|
||||||
|
if let Some(t0) = t0 {
|
||||||
|
if self.enabled {
|
||||||
|
Some(self.clock.delta(t0, self.clock.raw()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn update (&self, t0: Option<u64>, microseconds: f64) {
|
||||||
|
if let Some(t0) = t0 {
|
||||||
|
let t1 = self.clock.raw();
|
||||||
|
self.used.store(self.clock.delta_as_nanos(t0, t1) as f64, Relaxed);
|
||||||
|
self.window.store(microseconds, Relaxed,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn percentage (&self) -> Option<f64> {
|
||||||
|
let window = self.window.load(Relaxed) * 1000.0;
|
||||||
|
if window > 0.0 {
|
||||||
|
let used = self.used.load(Relaxed);
|
||||||
|
Some(100.0 * used / window)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -256,3 +256,15 @@ pub struct Field<C, T, U> {
|
||||||
pub value_bg: Option<C>,
|
pub value_bg: Option<C>,
|
||||||
pub value_align: Option<Direction>,
|
pub value_align: Option<Direction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Performance counter
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PerfModel {
|
||||||
|
pub enabled: bool,
|
||||||
|
|
||||||
|
pub clock: quanta::Clock,
|
||||||
|
// In nanoseconds. Time used by last iteration.
|
||||||
|
pub used: AtomicF64,
|
||||||
|
// In microseconds. Max prescribed time for iteration (frame, chunk...).
|
||||||
|
pub window: AtomicF64,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,3 +155,7 @@ pub trait Measured<O: Out> {
|
||||||
fn measure_width (&self) -> O::Unit { self.measure().w() }
|
fn measure_width (&self) -> O::Unit { self.measure().w() }
|
||||||
fn measure_height (&self) -> O::Unit { self.measure().h() }
|
fn measure_height (&self) -> O::Unit { self.measure().h() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait HasPerf {
|
||||||
|
fn perf (&self) -> &PerfModel;
|
||||||
|
}
|
||||||
|
|
|
||||||
196
tui/src/lib.rs
196
tui/src/lib.rs
|
|
@ -23,8 +23,8 @@ pub(crate) use ::{
|
||||||
better_panic::{Settings, Verbosity},
|
better_panic::{Settings, Verbosity},
|
||||||
palette::{*, convert::*, okhsl::*},
|
palette::{*, convert::*, okhsl::*},
|
||||||
ratatui::{
|
ratatui::{
|
||||||
prelude::{Color, Style, Buffer},
|
prelude::{Color, Style, Buffer, Position},
|
||||||
style::Modifier,
|
style::{Stylize, Modifier, Color::*},
|
||||||
backend::{Backend, CrosstermBackend, ClearType},
|
backend::{Backend, CrosstermBackend, ClearType},
|
||||||
layout::{Size, Rect},
|
layout::{Size, Rect},
|
||||||
buffer::Cell
|
buffer::Cell
|
||||||
|
|
@ -85,8 +85,155 @@ mod tui_structs; pub use self::tui_structs::*;
|
||||||
mod tui_traits; pub use self::tui_traits::*;
|
mod tui_traits; pub use self::tui_traits::*;
|
||||||
mod tui_impls; pub use self::tui_impls::*;
|
mod tui_impls; pub use self::tui_impls::*;
|
||||||
|
|
||||||
#[cfg(feature = "dsl")]
|
/// Run an app in the main loop.
|
||||||
pub fn evaluate_output_expression_tui <'a, S> (
|
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
|
state: &S, output: &mut TuiOut, expr: impl Expression + 'a
|
||||||
) -> Usually<bool> where
|
) -> Usually<bool> where
|
||||||
S: View<TuiOut, ()>
|
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 {
|
#[cfg(test)] mod tui_test {
|
||||||
use crate::*;
|
use crate::*;
|
||||||
#[test] fn test_tui_engine () -> Usually<()> {
|
#[test] fn test_tui_engine () -> Usually<()> {
|
||||||
|
|
|
||||||
1051
tui/src/tui_impls.rs
1051
tui/src/tui_impls.rs
File diff suppressed because it is too large
Load diff
|
|
@ -13,34 +13,29 @@ pub struct Tui {
|
||||||
pub perf: PerfModel,
|
pub perf: PerfModel,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)] pub struct BigBuffer {
|
#[derive(Debug, Clone)] pub struct TuiIn {
|
||||||
pub width: usize,
|
/// Input event
|
||||||
pub height: usize,
|
pub event: TuiEvent,
|
||||||
pub content: Vec<Cell>
|
/// Exit flag
|
||||||
|
pub exited: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent(pub Event);
|
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent(pub Event);
|
||||||
|
|
||||||
pub struct TuiKey(Option<KeyCode>, KeyModifiers);
|
pub struct TuiKey(Option<KeyCode>, KeyModifiers);
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Default)] pub struct TuiOut {
|
||||||
pub struct TuiIn {
|
pub buffer: Buffer,
|
||||||
/// Exit flag
|
pub area: XYWH<u16>,
|
||||||
pub exited: Arc<AtomicBool>,
|
|
||||||
/// Input event
|
|
||||||
pub event: TuiEvent,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performance counter
|
/// TUI buffer sized by `usize` instead of `u16`.
|
||||||
#[derive(Debug)]
|
#[derive(Default)] pub struct BigBuffer {
|
||||||
pub struct PerfModel {
|
pub width: usize,
|
||||||
pub enabled: bool,
|
pub height: usize,
|
||||||
pub clock: quanta::Clock,
|
pub content: Vec<Cell>
|
||||||
// In nanoseconds. Time used by last iteration.
|
|
||||||
pub used: AtomicF64,
|
|
||||||
// In microseconds. Max prescribed time for iteration (frame, chunk...).
|
|
||||||
pub window: AtomicF64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A color in OKHSL and RGB representations.
|
/// A color in OKHSL and RGB representations.
|
||||||
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor {
|
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor {
|
||||||
pub okhsl: Okhsl<f32>,
|
pub okhsl: Okhsl<f32>,
|
||||||
|
|
@ -57,29 +52,10 @@ pub struct PerfModel {
|
||||||
pub darkest: ItemColor,
|
pub darkest: ItemColor,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A cell that takes up 3 rows on its own,
|
pub struct Modify<T>(pub bool, pub Modifier, pub T);
|
||||||
/// but stacks, giving (N+1)*2 rows per N cells.
|
|
||||||
pub struct Phat<T> {
|
|
||||||
pub width: u16,
|
|
||||||
pub height: u16,
|
|
||||||
pub content: T,
|
|
||||||
pub colors: [Color;4],
|
|
||||||
}
|
|
||||||
pub struct Repeat<'a>(pub &'a str);
|
|
||||||
pub struct RepeatV<'a>(pub &'a str);
|
|
||||||
pub struct RepeatH<'a>(pub &'a str);
|
|
||||||
|
|
||||||
pub struct ScrollbarV {
|
pub struct Styled<T>(pub Option<Style>, pub T);
|
||||||
pub offset: usize,
|
|
||||||
pub length: usize,
|
|
||||||
pub total: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ScrollbarH {
|
|
||||||
pub offset: usize,
|
|
||||||
pub length: usize,
|
|
||||||
pub total: usize,
|
|
||||||
}
|
|
||||||
/// Displays an owned [str]-like with fixed maximum width.
|
/// Displays an owned [str]-like with fixed maximum width.
|
||||||
///
|
///
|
||||||
/// Width is computed using [unicode_width].
|
/// Width is computed using [unicode_width].
|
||||||
|
|
@ -90,35 +66,32 @@ pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
|
||||||
/// Width is computed using [unicode_width].
|
/// Width is computed using [unicode_width].
|
||||||
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
|
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
|
||||||
|
|
||||||
// Thunks can be natural error boundaries!
|
/// Thunks can be natural error boundaries!
|
||||||
pub struct ErrorBoundary<O: Out, T: Draw<O>>(
|
pub struct ErrorBoundary<O: Out, T: Draw<O>>(
|
||||||
std::marker::PhantomData<O>, Perhaps<T>
|
std::marker::PhantomData<O>,
|
||||||
|
Perhaps<T>
|
||||||
);
|
);
|
||||||
|
|
||||||
pub struct Modify<T>(pub bool, pub Modifier, pub T);
|
/// Repeat a string, e.g. for background
|
||||||
|
pub enum Repeat<'a> {
|
||||||
pub struct Styled<T>(pub Option<Style>, pub T);
|
X(&'a str),
|
||||||
|
Y(&'a str),
|
||||||
#[derive(Default)]
|
XY(&'a str)
|
||||||
pub struct TuiOut {
|
|
||||||
pub buffer: Buffer,
|
|
||||||
pub area: XYWH<u16>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Out for TuiOut {
|
/// Scroll indicator
|
||||||
type Unit = u16;
|
pub enum Scrollbar {
|
||||||
#[inline] fn area (&self) -> XYWH<u16> {
|
/// Horizontal scrollbar
|
||||||
self.area
|
X { offset: usize, length: usize, total: usize, },
|
||||||
}
|
/// Vertical scrollbar
|
||||||
#[inline] fn area_mut (&mut self) -> &mut XYWH<u16> {
|
Y { offset: usize, length: usize, total: usize, }
|
||||||
&mut self.area
|
}
|
||||||
}
|
|
||||||
#[inline] fn place_at <'t, T: Draw<Self> + ?Sized> (
|
/// A cell that takes up 3 rows on its own,
|
||||||
&mut self, area: XYWH<u16>, content: &'t T
|
/// but stacks, giving (N+1)*2 rows per N cells.
|
||||||
) {
|
pub struct Phat<T> {
|
||||||
let last = self.area();
|
pub width: u16,
|
||||||
*self.area_mut() = area;
|
pub height: u16,
|
||||||
content.draw(self);
|
pub content: T,
|
||||||
*self.area_mut() = last;
|
pub colors: [Color;4],
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,6 @@ pub trait TuiHandle = Handle<TuiIn>;
|
||||||
|
|
||||||
pub trait TuiWidget = TuiDraw + TuiHandle;
|
pub trait TuiWidget = TuiDraw + TuiHandle;
|
||||||
|
|
||||||
pub trait TuiRun<T: TuiWidget + 'static> {
|
|
||||||
/// Run an app in the main loop.
|
|
||||||
fn run (&self, state: &Arc<RwLock<T>>) -> Usually<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasColor { fn color (&self) -> ItemColor; }
|
pub trait HasColor { fn color (&self) -> ItemColor; }
|
||||||
|
|
||||||
pub trait BorderStyle: Content<TuiOut> + Copy {
|
pub trait BorderStyle: Content<TuiOut> + Copy {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue