TuiThread factors out poll/sleep/perf details
Some checks failed
/ build (push) Has been cancelled

This commit is contained in:
same mf who else 2026-03-06 12:59:07 +02:00
parent f1dda6af07
commit ecba0cc64f
5 changed files with 322 additions and 270 deletions

View file

@ -4,7 +4,6 @@ namespace!(State: bool {});
namespace!(State: u16 {}); namespace!(State: u16 {});
namespace!(State: Color {}); namespace!(State: Color {});
handle!(TuiIn: |self: State, input|Action::from(input).eval(self).map(|_|None)); handle!(TuiIn: |self: State, input|Action::from(input).eval(self).map(|_|None));
from!(Action: |input: &TuiIn| todo!());
view!(State: TuiOut: [ evaluate_output_expression, evaluate_output_expression_tui ]); view!(State: TuiOut: [ evaluate_output_expression, evaluate_output_expression_tui ]);
draw!(State: TuiOut: [ draw_example ]); draw!(State: TuiOut: [ draw_example ]);
#[derive(Debug, Default)] struct State { #[derive(Debug, Default)] struct State {
@ -12,6 +11,7 @@ namespace!(State: Color {});
/** Command history (undo/redo) */ history: Vec<Action>, /** Command history (undo/redo) */ history: Vec<Action>,
/** User-controllable value */ cursor: usize, /** User-controllable value */ cursor: usize,
} }
impl_from!(Action: |input: &TuiIn| todo!());
#[derive(Debug)] enum Action { #[derive(Debug)] enum Action {
/** Increment cursor */ Next, /** Increment cursor */ Next,
/** Decrement cursor */ Prev /** Decrement cursor */ Prev

View file

@ -36,7 +36,7 @@ pub extern crate unicode_width; pub(crate) use unicode_width::*;
}; };
pub(crate) use ::std::{ pub(crate) use ::std::{
io::{stdout, Stdout, Write}, io::{stdout, Stdout, Write},
sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}}, sync::{Arc, Weak, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}},
fmt::{Debug, Display}, fmt::{Debug, Display},
ops::{Add, Sub, Mul, Div}, ops::{Add, Sub, Mul, Div},
marker::PhantomData, marker::PhantomData,
@ -156,8 +156,6 @@ pub(crate) use ::std::{
} }
} }
/// Define layout operation. /// Define layout operation.
/// ///
/// ``` /// ```

View file

@ -37,7 +37,7 @@ impl<'a, T: AsRef<str>> TrimString<T> {
fn as_ref (&self) -> TrimStringRef<'_, T> { TrimStringRef(self.0, &self.1) } fn as_ref (&self) -> TrimStringRef<'_, T> { TrimStringRef(self.0, &self.1) }
} }
impl<O: Out, T: Draw<O>> ErrorBoundary<O, T> { impl<O: Out, T: Draw<O>> ErrorBoundary<O, T> {
pub fn new (content: Perhaps<T>) -> Self { Self(Default::default(), content) } pub fn new (content: Perhaps<T>) -> Self { Self(content, Default::default()) }
} }
impl<O: Out, T, L: Content<O>, V: Content<O>> HasContent<O> for FieldH<T, L, V> { impl<O: Out, T, L: Content<O>, V: Content<O>> HasContent<O> for FieldH<T, L, V> {
fn content (&self) -> impl Content<O> { Bsp::e(&self.1, &self.2) } fn content (&self) -> impl Content<O> { Bsp::e(&self.1, &self.2) }
@ -89,9 +89,6 @@ impl<S, T: Command<S>> Command<S> for Option<T> {
Ok(None) Ok(None)
} }
} }
impl<O: Out, T: Content<O>, F: Fn()->T> Lazy<O, T, F> {
pub const fn new (thunk: F) -> Self { Self(thunk, PhantomData) }
}
impl<O: Out> Layout<O> for Measure<O> {} impl<O: Out> Layout<O> for Measure<O> {}
impl<E: Out, T: AsRef<Measure<E>>> Measured<E> for T { impl<E: Out, T: AsRef<Measure<E>>> Measured<E> for T {
fn measure (&self) -> &Measure<E> { self.as_ref() } fn measure (&self) -> &Measure<E> { self.as_ref() }
@ -102,14 +99,16 @@ impl<O: Out> Clone for Measure<O> {
} }
} }
impl<O: Out, F: Fn(&mut O)> Thunk<O, F> { impl<O: Out, F: Fn(&mut O)> Thunk<O, F> {
pub const fn new (draw: F) -> Self { Self(PhantomData, draw) } pub const fn new (draw: F) -> Self { Self(draw, PhantomData) }
} }
impl<O: Out, F: Fn(&mut O)> Draw<O> for Thunk<O, F> { impl<O: Out, F: Fn(&mut O)> Draw<O> for Thunk<O, F> {
fn draw (&self, to: &mut O) { (self.1)(to) } fn draw (&self, to: &mut O) { (self.0)(to) }
} }
impl<O: Out, F: Fn(&mut O)> Layout<O> for Thunk<O, F> {} impl<O: Out, F: Fn(&mut O)> Layout<O> for Thunk<O, F> {}
impl<T: PartialEq, U> Memo<T, U> { impl<T: PartialEq, U> Memo<T, U> {
pub fn new (value: T, view: U) -> Self { Self { value, view: Arc::new(view.into()) } } pub fn new (value: T, view: U) -> Self {
Self { value, view: Arc::new(view.into()) }
}
pub fn update <R> (&mut self, newval: T, draw: impl Fn(&mut U, &T, &T)->R) -> Option<R> { pub fn update <R> (&mut self, newval: T, draw: impl Fn(&mut U, &T, &T)->R) -> Option<R> {
if newval != self.value { if newval != self.value {
let result = draw(&mut*self.view.write().unwrap(), &newval, &self.value); let result = draw(&mut*self.view.write().unwrap(), &newval, &self.value);
@ -528,7 +527,7 @@ impl<O: Out, Head: Layout<O>, Tail: Layout<O>> Layout<O> for Bsp<Head, Tail> {
fn layout_w (&self, area: XYWH<O::Unit>) -> O::Unit { fn layout_w (&self, area: XYWH<O::Unit>) -> O::Unit {
match self.0 { match self.0 {
Above | Below | North | South => self.1.layout_w(area).max(self.2.layout_w(area)), Above | Below | North | South => self.1.layout_w(area).max(self.2.layout_w(area)),
East | West => self.1.layout_w_min(area).plus(self.2.layout_w(area)), East | West => self.1.layout_w_min(area).plus(self.2.layout_w(area)),
} }
} }
fn layout_w_min (&self, area: XYWH<O::Unit>) -> O::Unit { fn layout_w_min (&self, area: XYWH<O::Unit>) -> O::Unit {
@ -546,7 +545,7 @@ impl<O: Out, Head: Layout<O>, Tail: Layout<O>> Layout<O> for Bsp<Head, Tail> {
fn layout_h (&self, area: XYWH<O::Unit>) -> O::Unit { fn layout_h (&self, area: XYWH<O::Unit>) -> O::Unit {
match self.0 { match self.0 {
Above | Below | East | West => self.1.layout_h(area).max(self.2.layout_h(area)), Above | Below | East | West => self.1.layout_h(area).max(self.2.layout_h(area)),
North | South => self.1.layout_h(area).plus(self.2.layout_h(area)), North | South => self.1.layout_h(area).plus(self.2.layout_h(area)),
} }
} }
fn layout_h_min (&self, area: XYWH<O::Unit>) -> O::Unit { fn layout_h_min (&self, area: XYWH<O::Unit>) -> O::Unit {
@ -558,7 +557,7 @@ impl<O: Out, Head: Layout<O>, Tail: Layout<O>> Layout<O> for Bsp<Head, Tail> {
fn layout_h_max (&self, area: XYWH<O::Unit>) -> O::Unit { fn layout_h_max (&self, area: XYWH<O::Unit>) -> O::Unit {
match self.0 { match self.0 {
Above | Below | North | South => self.1.layout_h_max(area).max(self.2.layout_h_max(area)), Above | Below | North | South => self.1.layout_h_max(area).max(self.2.layout_h_max(area)),
East | West => self.1.layout_h_max(area).plus(self.2.layout_h_max(area)), East | West => self.1.layout_h_max(area).plus(self.2.layout_h_max(area)),
} }
} }
fn layout (&self, area: XYWH<O::Unit>) -> XYWH<O::Unit> { fn layout (&self, area: XYWH<O::Unit>) -> XYWH<O::Unit> {
@ -871,60 +870,119 @@ mod xywh {
impl_from!(ItemColor: |okhsl: Okhsl<f32>| Self { okhsl, rgb: okhsl_to_rgb(okhsl) }); impl_from!(ItemColor: |okhsl: Okhsl<f32>| Self { okhsl, rgb: okhsl_to_rgb(okhsl) });
impl_debug!(BigBuffer |self, f| { write!(f, "[BB {}x{} ({})]", self.width, self.height, self.content.len()) }); impl_debug!(BigBuffer |self, f| { write!(f, "[BB {}x{} ({})]", self.width, self.height, self.content.len()) });
impl Tui { impl Tui {
/// True if done
pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) }
/// Prepare before run
pub fn setup (&self) -> Usually<()> { tui_setup(&mut*self.backend.write().unwrap()) }
/// Clean up after run
pub fn teardown (&self) -> Usually<()> { tui_teardown(&mut*self.backend.write().unwrap()) }
/// Apply changes to the display buffer.
pub fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer {
tui_resized(&mut*self.backend.write().unwrap(), &mut*self.buffer.write().unwrap(), size);
tui_redrawn(&mut*self.backend.write().unwrap(), &mut*self.buffer.write().unwrap(), &mut buffer);
buffer
}
/// Create the engine.
pub fn new (output: Box<dyn Write + Send + Sync>) -> Usually<Self> { pub fn new (output: Box<dyn Write + Send + Sync>) -> Usually<Self> {
let backend = CrosstermBackend::new(output); let backend = CrosstermBackend::new(output);
let Size { width, height } = backend.size()?; let Size { width, height } = backend.size()?;
Ok(Self { Ok(Self {
exited: Arc::new(AtomicBool::new(false)), exited: Arc::new(AtomicBool::new(false)),
buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }), buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }).into(),
area: [0, 0, width, height], area: XYWH(0, 0, width, height),
perf: Default::default(), perf: Default::default(),
backend, backend: backend.into(),
event: None,
error: None,
}) })
} }
/// Create and launch a terminal user interface. /// Run an amm in the engine.
pub fn run <T> (self, join: bool, state: &Arc<RwLock<T>>) -> Usually<Arc<RwLock<Self>>> where pub fn run <T> (mut self, join: bool, state: &Arc<RwLock<T>>) -> Usually<Arc<Self>> where
T: Handle<TuiIn> + Draw<TuiOut> + Send + Sync + 'static T: Handle<Tui> + Draw<Tui> + Send + Sync + 'static
{ {
let tui = Arc::new(RwLock::new(self)); self.setup()?;
let _input_thread = tui_input(tui.clone(), state, Duration::from_millis(100)); let tui = Arc::new(self);
tui.write().unwrap().setup()?; let _input_thread = tui_input(&tui, state, Duration::from_millis(100))?;
let render_thread = tui_output(tui.clone(), state, Duration::from_millis(10))?; let render_thread = tui_output(&tui, state, Duration::from_millis(10))?;
if join { if join {
match render_thread.join() { let result = render_thread.join();
Ok(result) => { tui.teardown()?;
tui.write().unwrap().teardown()?; match result {
println!("\n\rRan successfully: {result:?}\n\r"); Ok(result) => println!("\n\rRan successfully: {result:?}\n\r"),
}, Err(error) => panic!("\n\rDraw thread failed: error={error:?}.\n\r"),
Err(error) => {
tui.write().unwrap().teardown()?;
panic!("\n\rDraw thread failed: error={error:?}.\n\r")
},
} }
} }
Ok(tui) Ok(tui)
} }
/// True if done }
pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) }
/// Prepare before run /// Spawn the input thread.
pub fn setup (&mut self) -> Usually<()> { tui_setup(&mut self.backend) } pub fn tui_input <T: Handle<Tui> + Send + Sync + 'static> (
/// Clean up after run engine: &Arc<Tui>, state: &Arc<RwLock<T>>, poll: Duration
pub fn teardown (&mut self) -> Usually<()> { tui_teardown(&mut self.backend) } ) -> Result<TuiThread, std::io::Error> {
/// Apply changes to the display buffer. let state = state.clone();
pub fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer { let engine = engine.clone();
tui_resized(&mut self.backend, &mut self.buffer, size); TuiThread::new_poll(engine.exited.clone(), poll, move |_| {
tui_redrawn(&mut self.backend, &mut self.buffer, &mut buffer); let event = read().unwrap();
buffer match event {
Event::Key(KeyEvent {
modifiers: KeyModifiers::CONTROL,
code: KeyCode::Char('c'),
kind: KeyEventKind::Press,
state: KeyEventState::NONE
}) => {
engine.exited.store(true, Relaxed);
},
_ => {
let event = TuiEvent::from_crossterm(event);
if let Err(e) = state.write().unwrap().handle(&engine) {
panic!("{e}")
}
}
}
})
}
/// Spawn the output thread.
pub fn tui_output <T: Draw<Tui> + Send + Sync + 'static> (
engine: &Arc<Tui>, state: &Arc<RwLock<T>>, sleep: Duration
) -> Result<TuiThread, std::io::Error> {
let state = state.clone();
let mut engine = engine.clone();
let WH(width, height) = tui_wh(&engine);
let mut buffer = Buffer::empty(Rect { x: 0, y: 0, width, height });
TuiThread::new_sleep(engine.exited.clone(), sleep, move |perf| {
let WH(width, height) = tui_wh(&engine);
if let Ok(state) = state.try_read() {
let size = Rect { x: 0, y: 0, width, height };
if buffer.area != size {
engine.backend.write().unwrap()
.clear_region(ClearType::All).expect("pre-frame clear region failed");
buffer.resize(size);
buffer.reset();
}
state.draw(&engine);
buffer = engine.flip(*engine.buffer.write().unwrap(), size);
}
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
buffer.set_string(0, 0, &timer, Style::default());
})
}
fn tui_wh (engine: &Tui) -> WH<u16> {
let Size { width, height } = engine.backend.read().unwrap().size().expect("get size failed");
WH(width, height)
}
impl Input for Tui {
type Event = TuiEvent;
type Handled = bool;
fn event (&self) -> &TuiEvent {
self.event.as_ref().expect("input.event called outside of input handler")
} }
} }
impl Input for TuiIn { impl Done for Tui {
type Event = TuiEvent;
type Handled = bool;
fn event (&self) -> &TuiEvent { &self.event }
}
impl Done for TuiIn {
fn done (&self) { self.exited.store(true, Relaxed); } fn done (&self) { self.exited.store(true, Relaxed); }
fn is_done (&self) -> bool { self.exited.fetch_and(true, Relaxed) } fn is_done (&self) -> bool { self.exited.fetch_and(true, Relaxed) }
} }
@ -1003,7 +1061,7 @@ mod xywh {
} }
} }
impl Out for TuiOut { impl Out for Tui {
type Unit = u16; type Unit = u16;
#[inline] fn area (&self) -> XYWH<u16> { self.area } #[inline] fn area (&self) -> XYWH<u16> { self.area }
#[inline] fn area_mut (&mut self) -> &mut XYWH<u16> { &mut self.area } #[inline] fn area_mut (&mut self) -> &mut XYWH<u16> { &mut self.area }
@ -1015,9 +1073,11 @@ mod xywh {
} }
} }
impl TuiOut { impl Tui {
#[inline] pub fn with_rect (&mut self, area: XYWH<u16>) -> &mut Self { self.area = area; self } #[inline] pub fn with_rect (&mut self, area: XYWH<u16>) -> &mut Self { self.area = area; self }
pub fn update (&mut self, area: XYWH<u16>, callback: &impl Fn(&mut Cell, u16, u16)) { tui_update(&mut self.buffer, area, callback); } pub fn update (&mut self, area: XYWH<u16>, callback: &impl Fn(&mut Cell, u16, u16)) {
tui_update(&mut*self.buffer.write().unwrap(), area, callback);
}
pub fn fill_char (&mut self, area: XYWH<u16>, c: char) { self.update(area, &|cell,_,_|{cell.set_char(c);}) } pub fn fill_char (&mut self, area: XYWH<u16>, c: char) { self.update(area, &|cell,_,_|{cell.set_char(c);}) }
pub fn fill_bg (&mut self, area: XYWH<u16>, color: Color) { self.update(area, &|cell,_,_|{cell.set_bg(color);}) } pub fn fill_bg (&mut self, area: XYWH<u16>, color: Color) { self.update(area, &|cell,_,_|{cell.set_bg(color);}) }
pub fn fill_fg (&mut self, area: XYWH<u16>, color: Color) { self.update(area, &|cell,_,_|{cell.set_fg(color);}) } pub fn fill_fg (&mut self, area: XYWH<u16>, color: Color) { self.update(area, &|cell,_,_|{cell.set_fg(color);}) }
@ -1044,7 +1104,7 @@ mod xywh {
} }
} }
pub fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) { pub fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) {
for cell in self.buffer.content.iter_mut() { for cell in self.buffer.write().unwrap().content.iter_mut() {
cell.fg = fg; cell.fg = fg;
cell.bg = bg; cell.bg = bg;
cell.modifier = modifier; cell.modifier = modifier;
@ -1052,8 +1112,8 @@ mod xywh {
} }
pub fn blit (&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>) { pub fn blit (&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>) {
let text = text.as_ref(); let text = text.as_ref();
let buf = &mut self.buffer;
let style = style.unwrap_or(Style::default()); let style = style.unwrap_or(Style::default());
let mut buf = self.buffer.write().unwrap();
if x < buf.area.width && y < buf.area.height { if x < buf.area.width && y < buf.area.height {
buf.set_string(x, y, text, style); buf.set_string(x, y, text, style);
} }
@ -1063,7 +1123,7 @@ mod xywh {
/// TODO: do a paragraph (handle newlines) /// TODO: do a paragraph (handle newlines)
pub fn text (&mut self, text: &impl AsRef<str>, x0: u16, y: u16, max_width: u16) { pub fn text (&mut self, text: &impl AsRef<str>, x0: u16, y: u16, max_width: u16) {
let text = text.as_ref(); let text = text.as_ref();
let buf = &mut self.buffer; let mut buf = &mut self.buffer;
let mut string_width: u16 = 0; let mut string_width: u16 = 0;
for character in text.chars() { for character in text.chars() {
let x = x0 + string_width; let x = x0 + string_width;
@ -1072,7 +1132,7 @@ mod xywh {
if string_width > max_width { if string_width > max_width {
break break
} }
if let Some(cell) = buf.cell_mut(ratatui::prelude::Position { x, y }) { if let Some(cell) = buf.write().unwrap().cell_mut(ratatui::prelude::Position { x, y }) {
cell.set_char(character); cell.set_char(character);
} else { } else {
break break
@ -1098,9 +1158,7 @@ mod xywh {
} }
} }
pub fn tui_setup <W: Write> ( pub fn tui_setup <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()> {
backend: &mut CrosstermBackend<W>
) -> Usually<()> {
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler(); let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{ std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{
stdout().execute(LeaveAlternateScreen).unwrap(); stdout().execute(LeaveAlternateScreen).unwrap();
@ -1159,82 +1217,66 @@ mod xywh {
} }
} }
/// Spawn the output thread. impl TuiThread {
pub fn tui_output <T: Draw<TuiOut> + Send + Sync + 'static> ( /// Spawn a TUI thread that runs `callt least one, then repeats until `exit`.
engine: Arc<RwLock<Tui>>, state: &Arc<RwLock<T>>, timer: Duration pub fn new <F> (exit: Arc<AtomicBool>, call: F) -> Result<Self, std::io::Error>
) -> Result<JoinHandle<()>, std::io::Error> { where F: Fn(&PerfModel)->() + Send + Sync + 'static
let exited = engine.read().unwrap().exited.clone(); {
let engine = engine.clone(); let perf = Arc::new(PerfModel::default());
let state = state.clone(); Ok(Self {
let Size { width, height } = engine.read().unwrap().backend.size().expect("get size failed"); exit: exit.clone(),
let mut buffer = Buffer::empty(Rect { x: 0, y: 0, width, height }); perf: perf.clone(),
std::thread::Builder::new() join: std::thread::Builder::new().name("tengri tui output".into()).spawn(move || {
.name("tui output thread".into()) while !exit.fetch_and(true, Relaxed) {
.spawn(move || loop { let _ = perf.cycle(&call);
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) }; })?.into()
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 a TUI thread that runs `callt least one, then repeats
/// until `exit`, sleeping for `time` msec after every iteration.
pub fn new_sleep <F> (
exit: Arc<AtomicBool>, time: Duration, call: F
) -> Result<Self, std::io::Error>
where F: Fn(&PerfModel)->() + Send + Sync + 'static
{
Self::new(exit, move |perf| { let _ = call(perf); std::thread::sleep(time); })
}
/// Spawn a TUI thread that runs `callt least one, then repeats
/// until `exit`, using polling to run every `time` msec.
pub fn new_poll <F> (
exit: Arc<AtomicBool>, time: Duration, call: F
) -> Result<Self, std::io::Error>
where F: Fn(&PerfModel)->() + Send + Sync + 'static
{
Self::new(exit, move |perf| { if poll(time).is_ok() { let _ = call(perf); } })
}
pub fn join (self) -> Result<(), Box<dyn std::any::Any + Send>> {
self.join.join()
}
} }
/// Spawn the input thread. impl PerfModel {
pub fn tui_input <T: Handle<TuiIn> + Send + Sync + 'static> ( fn cycle <F: Fn(&Self)->T, T> (&self, call: &F) -> T {
engine: Arc<RwLock<Tui>>, state: &Arc<RwLock<T>>, timer: Duration let t0 = self.get_t0();
) -> JoinHandle<()> { let result = call(self);
let exited = engine.read().unwrap().exited.clone(); let _t1 = self.get_t1(t0).unwrap();
let state = state.clone(); result
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}")
}
}
}
}
})
} }
impl<T> Phat<T> { impl<T> Phat<T> {
pub const LO: &'static str = ""; pub const LO: &'static str = "";
pub const HI: &'static str = ""; pub const HI: &'static str = "";
/// A phat line /// A phat line
pub fn lo (fg: Color, bg: Color) -> impl Content<TuiOut> { pub fn lo (fg: Color, bg: Color) -> impl Content<Tui> {
Fixed::Y(1, Tui::fg_bg(fg, bg, Repeat::X(Self::LO))) Fixed::Y(1, Tui::fg_bg(fg, bg, Repeat::X(Self::LO)))
} }
/// A phat line /// A phat line
pub fn hi (fg: Color, bg: Color) -> impl Content<TuiOut> { pub fn hi (fg: Color, bg: Color) -> impl Content<Tui> {
Fixed::Y(1, Tui::fg_bg(fg, bg, Repeat::X(Self::HI))) Fixed::Y(1, Tui::fg_bg(fg, bg, Repeat::X(Self::HI)))
} }
} }
@ -1244,17 +1286,17 @@ mod xywh {
const ICON_DEC_H: &[char] = &[' ', '🞀', ' ']; const ICON_DEC_H: &[char] = &[' ', '🞀', ' '];
const ICON_INC_H: &[char] = &[' ', '🞂', ' ']; const ICON_INC_H: &[char] = &[' ', '🞂', ' '];
} }
impl<S: BorderStyle, W: Content<TuiOut>> HasContent<TuiOut> for Bordered<S, W> { impl<S: BorderStyle, W: Content<Tui>> HasContent<Tui> for Bordered<S, W> {
fn content (&self) -> impl Content<TuiOut> { fn content (&self) -> impl Content<Tui> {
Fill::XY(lay!( When::new(self.0, Border(self.0, self.1)), Pad::XY(1, 1, &self.2) )) Fill::XY(lay!( When::new(self.0, Border(self.0, self.1)), Pad::XY(1, 1, &self.2) ))
} }
} }
impl< impl<
A: Content<TuiOut>, A: Content<Tui>,
B: Content<TuiOut>, B: Content<Tui>,
C: Content<TuiOut>, C: Content<Tui>,
> HasContent<TuiOut> for Tryptich<A, B, C> { > HasContent<Tui> for Tryptich<A, B, C> {
fn content (&self) -> impl Content<TuiOut> { fn content (&self) -> impl Content<Tui> {
let Self { top, h, left: (w_a, ref a), middle: (w_b, ref b), right: (w_c, ref c) } = *self; let Self { top, h, left: (w_a, ref a), middle: (w_b, ref b), right: (w_c, ref c) } = *self;
Fixed::Y(h, if top { Fixed::Y(h, if top {
Bsp::a( Bsp::a(
@ -1275,8 +1317,8 @@ mod xywh {
}) })
} }
} }
impl<T: Content<TuiOut>> HasContent<TuiOut> for Phat<T> { impl<T: Content<Tui>> HasContent<Tui> for Phat<T> {
fn content (&self) -> impl Content<TuiOut> { fn content (&self) -> impl Content<Tui> {
let [fg, bg, hi, lo] = self.colors; let [fg, bg, hi, lo] = self.colors;
let top = Fixed::Y(1, Self::lo(bg, hi)); let top = Fixed::Y(1, Self::lo(bg, hi));
let low = Fixed::Y(1, Self::hi(bg, lo)); let low = Fixed::Y(1, Self::hi(bg, lo));
@ -1284,37 +1326,37 @@ mod xywh {
Min::XY(self.width, self.height, Bsp::s(top, Bsp::n(low, Fill::XY(content)))) Min::XY(self.width, self.height, Bsp::s(top, Bsp::n(low, Fill::XY(content))))
} }
} }
impl<T: Content<TuiOut>> Layout<TuiOut> for Modify<T> {} impl<T: Content<Tui>> Layout<Tui> for Modify<T> {}
impl<T: Content<TuiOut>> Layout<TuiOut> for Styled<T> {} impl<T: Content<Tui>> Layout<Tui> for Styled<T> {}
impl Layout<TuiOut> for Repeat<'_> {} impl Layout<Tui> for Repeat<'_> {}
impl Layout<TuiOut> for &str { impl Layout<Tui> for &str {
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> { fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
to.centered_xy([width_chars_max(to.w(), self), 1]) to.centered_xy([width_chars_max(to.w(), self), 1])
} }
} }
impl Layout<TuiOut> for String { impl Layout<Tui> for String {
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> { fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
self.as_str().layout(to) self.as_str().layout(to)
} }
} }
impl Layout<TuiOut> for Arc<str> { impl Layout<Tui> for Arc<str> {
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> { fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
self.as_ref().layout(to) self.as_ref().layout(to)
} }
} }
impl<'a, T: AsRef<str>> Layout<TuiOut> for TrimString<T> { impl<'a, T: AsRef<str>> Layout<Tui> for TrimString<T> {
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> { fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
Layout::layout(&self.as_ref(), to) Layout::layout(&self.as_ref(), to)
} }
} }
impl<'a, T: AsRef<str>> Layout<TuiOut> for TrimStringRef<'a, T> { impl<'a, T: AsRef<str>> Layout<Tui> for TrimStringRef<'a, T> {
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> { fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
XYWH(to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h()) XYWH(to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h())
} }
} }
impl<T: Draw<TuiOut>> Draw<TuiOut> for ErrorBoundary<TuiOut, T> { impl<T: Draw<Tui>> Draw<Tui> for ErrorBoundary<Tui, T> {
fn draw (&self, to: &mut TuiOut) { fn draw (&self, to: &mut Tui) {
match self.1.as_ref() { match self.0.as_ref() {
Ok(Some(content)) => content.draw(to), Ok(Some(content)) => content.draw(to),
Ok(None) => to.blit(&"empty?", 0, 0, Some(Style::default().yellow())), Ok(None) => to.blit(&"empty?", 0, 0, Some(Style::default().yellow())),
Err(e) => { Err(e) => {
@ -1328,32 +1370,33 @@ mod xywh {
} }
} }
impl Draw<TuiOut> for u64 { impl Draw<Tui> for u64 {
fn draw (&self, _to: &mut TuiOut) { fn draw (&self, _to: &mut Tui) {
todo!() todo!()
} }
} }
impl Draw<TuiOut> for f64 { impl Draw<Tui> for f64 {
fn draw (&self, _to: &mut TuiOut) { fn draw (&self, _to: &mut Tui) {
todo!() todo!()
} }
} }
impl Draw<TuiOut> for Repeat<'_> { impl Draw<Tui> for Repeat<'_> {
fn draw (&self, to: &mut TuiOut) { fn draw (&self, to: &mut Tui) {
let XYWH(x, y, w, h) = to.area(); let XYWH(x, y, w, h) = to.area();
let mut buf = to.buffer.write().unwrap();
match self { match self {
Self::X(c) => { Self::X(c) => {
for x in x..x+w { for x in x..x+w {
if let Some(cell) = to.buffer.cell_mut(Position::from((x, y))) { if let Some(cell) = buf.cell_mut(Position::from((x, y))) {
cell.set_symbol(&c); cell.set_symbol(&c);
} }
} }
}, },
Self::Y(c) => { Self::Y(c) => {
for y in y..y+h { for y in y..y+h {
if let Some(cell) = to.buffer.cell_mut(Position::from((x, y))) { if let Some(cell) = buf.cell_mut(Position::from((x, y))) {
cell.set_symbol(&c); cell.set_symbol(&c);
} }
} }
@ -1362,7 +1405,7 @@ mod xywh {
let a = c.len(); let a = c.len();
for (_v, y) in (y..y+h).enumerate() { for (_v, y) in (y..y+h).enumerate() {
for (u, x) in (x..x+w).enumerate() { for (u, x) in (x..x+w).enumerate() {
if let Some(cell) = to.buffer.cell_mut(Position::from((x, y))) { if let Some(cell) = buf.cell_mut(Position::from((x, y))) {
let u = u % a; let u = u % a;
cell.set_symbol(&c[u..u+1]); cell.set_symbol(&c[u..u+1]);
} }
@ -1373,14 +1416,15 @@ mod xywh {
} }
} }
impl Draw<TuiOut> for Scrollbar { impl Draw<Tui> for Scrollbar {
fn draw (&self, to: &mut TuiOut) { fn draw (&self, to: &mut Tui) {
let XYWH(x1, y1, w, h) = to.area(); let XYWH(x1, y1, w, h) = to.area();
let mut buf = to.buffer.write().unwrap();
match self { match self {
Self::X { .. } => { Self::X { .. } => {
let x2 = x1 + w; let x2 = x1 + w;
for (i, x) in (x1..=x2).enumerate() { for (i, x) in (x1..=x2).enumerate() {
if let Some(cell) = to.buffer.cell_mut(Position::from((x, y1))) { if let Some(cell) = buf.cell_mut(Position::from((x, y1))) {
if i < (Self::ICON_DEC_H.len()) { if i < (Self::ICON_DEC_H.len()) {
cell.set_fg(Rgb(255, 255, 255)); cell.set_fg(Rgb(255, 255, 255));
cell.set_bg(Rgb(0, 0, 0)); cell.set_bg(Rgb(0, 0, 0));
@ -1404,7 +1448,7 @@ mod xywh {
Self::Y { .. } => { Self::Y { .. } => {
let y2 = y1 + h; let y2 = y1 + h;
for (i, y) in (y1..=y2).enumerate() { for (i, y) in (y1..=y2).enumerate() {
if let Some(cell) = to.buffer.cell_mut(Position::from((x1, y))) { if let Some(cell) = buf.cell_mut(Position::from((x1, y))) {
if (i as usize) < (Self::ICON_DEC_V.len()) { if (i as usize) < (Self::ICON_DEC_V.len()) {
cell.set_fg(Rgb(255, 255, 255)); cell.set_fg(Rgb(255, 255, 255));
cell.set_bg(Rgb(0, 0, 0)); cell.set_bg(Rgb(0, 0, 0));
@ -1429,55 +1473,55 @@ mod xywh {
} }
} }
impl Draw<TuiOut> for &str { impl Draw<Tui> for &str {
fn draw (&self, to: &mut TuiOut) { fn draw (&self, to: &mut Tui) {
let XYWH(x, y, w, ..) = self.layout(to.area()); let XYWH(x, y, w, ..) = self.layout(to.area());
to.text(&self, x, y, w) to.text(&self, x, y, w)
} }
} }
impl Draw<TuiOut> for String { impl Draw<Tui> for String {
fn draw (&self, to: &mut TuiOut) { fn draw (&self, to: &mut Tui) {
self.as_str().draw(to) self.as_str().draw(to)
} }
} }
impl Draw<TuiOut> for Arc<str> { impl Draw<Tui> for Arc<str> {
fn draw (&self, to: &mut TuiOut) { self.as_ref().draw(to) } fn draw (&self, to: &mut Tui) { self.as_ref().draw(to) }
} }
impl<T: Content<TuiOut>> Draw<TuiOut> for Foreground<Color, T> { impl<T: Content<Tui>> Draw<Tui> for Foreground<Color, T> {
fn draw (&self, to: &mut TuiOut) { fn draw (&self, to: &mut Tui) {
let area = self.layout(to.area()); let area = self.layout(to.area());
to.fill_fg(area, self.0); to.fill_fg(area, self.0);
to.place_at(area, &self.1); to.place_at(area, &self.1);
} }
} }
impl<T: Content<TuiOut>> Draw<TuiOut> for Background<Color, T> { impl<T: Content<Tui>> Draw<Tui> for Background<Color, T> {
fn draw (&self, to: &mut TuiOut) { fn draw (&self, to: &mut Tui) {
let area = self.layout(to.area()); let area = self.layout(to.area());
to.fill_bg(area, self.0); to.fill_bg(area, self.0);
to.place_at(area, &self.1); to.place_at(area, &self.1);
} }
} }
impl<T: Content<TuiOut>> Draw<TuiOut> for Modify<T> { impl<T: Content<Tui>> Draw<Tui> for Modify<T> {
fn draw (&self, to: &mut TuiOut) { fn draw (&self, to: &mut Tui) {
to.fill_mod(to.area(), self.0, self.1); to.fill_mod(to.area(), self.0, self.1);
self.2.draw(to) self.2.draw(to)
} }
} }
impl<T: Content<TuiOut>> Draw<TuiOut> for Styled<T> { impl<T: Content<Tui>> Draw<Tui> for Styled<T> {
fn draw (&self, to: &mut TuiOut) { fn draw (&self, to: &mut Tui) {
to.place(&self.1); to.place(&self.1);
// TODO write style over area // TODO write style over area
} }
} }
impl<S: BorderStyle> Draw<TuiOut> for Border<S> { impl<S: BorderStyle> Draw<Tui> for Border<S> {
fn draw (&self, to: &mut TuiOut) { fn draw (&self, to: &mut Tui) {
let Border(enabled, style) = self; let Border(enabled, style) = self;
if *enabled { if *enabled {
let area = to.area(); let area = to.area();
@ -1499,20 +1543,21 @@ mod xywh {
} }
} }
impl<'a, T: AsRef<str>> Draw<TuiOut> for TrimString<T> { impl<'a, T: AsRef<str>> Draw<Tui> for TrimString<T> {
fn draw (&self, to: &mut TuiOut) { Draw::draw(&self.as_ref(), to) } fn draw (&self, to: &mut Tui) { Draw::draw(&self.as_ref(), to) }
} }
impl<T: AsRef<str>> Draw<TuiOut> for TrimStringRef<'_, T> { impl<T: AsRef<str>> Draw<Tui> for TrimStringRef<'_, T> {
fn draw (&self, target: &mut TuiOut) { fn draw (&self, target: &mut Tui) {
let area = target.area(); let area = target.area();
let mut buf = target.buffer.write().unwrap();
let mut width: u16 = 1; let mut width: u16 = 1;
let mut chars = self.1.as_ref().chars(); let mut chars = self.1.as_ref().chars();
while let Some(c) = chars.next() { while let Some(c) = chars.next() {
if width > self.0 || width > area.w() { if width > self.0 || width > area.w() {
break break
} }
if let Some(cell) = target.buffer.cell_mut(Position { if let Some(cell) = buf.cell_mut(Position {
x: area.x() + width - 1, x: area.x() + width - 1,
y: area.y() y: area.y()
}) { }) {
@ -1581,7 +1626,7 @@ mod xywh {
"bropen" | "openbracket" => Char('['), "bropen" | "openbracket" => Char('['),
"brclose" | "closebracket" => Char(']'), "brclose" | "closebracket" => Char(']'),
"pgup" | "pageup" => PageUp, "pgup" | "pageup" => PageUp,
"pgdn" | "pagedown" => PageDown, "pgdn" | "pagedown" => PageDown,
"f1" => F(1), "f1" => F(1),
"f2" => F(2), "f2" => F(2),
"f3" => F(3), "f3" => F(3),
@ -1625,16 +1670,16 @@ mod xywh {
/// Should be impl something or other... /// Should be impl something or other...
/// ///
/// ``` /// ```
/// use tengri::{Namespace, Understand, TuiOut, ratatui::prelude::Color}; /// use tengri::{Namespace, Understand, Tui, ratatui::prelude::Color};
/// ///
/// struct State; /// struct State;
/// impl<'b> Namespace<'b, bool> for State {} /// impl<'b> Namespace<'b, bool> for State {}
/// impl<'b> Namespace<'b, u16> for State {} /// impl<'b> Namespace<'b, u16> for State {}
/// impl<'b> Namespace<'b, Color> for State {} /// impl<'b> Namespace<'b, Color> for State {}
/// impl Understand<TuiOut, ()> for State {} /// impl Understand<Tui, ()> for State {}
/// # fn main () -> tengri::Usually<()> { /// # fn main () -> tengri::Usually<()> {
/// let state = State; /// let state = State;
/// let mut out = TuiOut::default(); /// let mut out = Tui::default();
/// tengri::evaluate_output_expression_tui(&state, &mut out, "")?; /// tengri::evaluate_output_expression_tui(&state, &mut out, "")?;
/// tengri::evaluate_output_expression_tui(&state, &mut out, "text Hello world!")?; /// tengri::evaluate_output_expression_tui(&state, &mut out, "text Hello world!")?;
/// tengri::evaluate_output_expression_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?; /// tengri::evaluate_output_expression_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?;
@ -1643,9 +1688,9 @@ mod xywh {
/// # Ok(()) } /// # Ok(()) }
/// ``` /// ```
pub fn evaluate_output_expression_tui <'a, S> ( pub fn evaluate_output_expression_tui <'a, S> (
state: &S, output: &mut TuiOut, expr: impl Expression + 'a state: &S, output: &mut Tui, expr: impl Expression + 'a
) -> Usually<bool> where ) -> Usually<bool> where
S: Understand<TuiOut, ()> S: Understand<Tui, ()>
+ for<'b>Namespace<'b, bool> + for<'b>Namespace<'b, bool>
+ for<'b>Namespace<'b, u16> + for<'b>Namespace<'b, u16>
+ for<'b>Namespace<'b, Color> + for<'b>Namespace<'b, Color>
@ -1668,7 +1713,7 @@ mod xywh {
Some("fg") => { Some("fg") => {
let arg0 = arg0?.expect("fg: expected arg 0 (color)"); let arg0 = arg0?.expect("fg: expected arg 0 (color)");
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color")); let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color"));
let thunk = Thunk::new(move|output: &mut TuiOut|state.understand(output, &arg1).unwrap()); let thunk = Thunk::new(move|output: &mut Tui|state.understand(output, &arg1).unwrap());
output.place(&Tui::fg(color, thunk)) output.place(&Tui::fg(color, thunk))
}, },
@ -1678,7 +1723,7 @@ mod xywh {
//panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}"); //panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}");
let arg0 = arg0?.expect("bg: expected arg 0 (color)"); let arg0 = arg0?.expect("bg: expected arg 0 (color)");
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color")); let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color"));
let thunk = Thunk::new(move|output: &mut TuiOut|state.understand(output, &arg1).unwrap()); let thunk = Thunk::new(move|output: &mut Tui|state.understand(output, &arg1).unwrap());
output.place(&Tui::bg(color, thunk)) output.place(&Tui::bg(color, thunk))
}, },
@ -1692,7 +1737,7 @@ mod xywh {
/// let _ = tengri::button_2("", "", true); /// let _ = tengri::button_2("", "", true);
/// let _ = tengri::button_2("", "", false); /// let _ = tengri::button_2("", "", false);
/// ``` /// ```
pub fn button_2 <'a> (key: impl Content<TuiOut>, label: impl Content<TuiOut>, editing: bool) -> impl Content<TuiOut> { pub fn button_2 <'a> (key: impl Content<Tui>, label: impl Content<Tui>, editing: bool) -> impl Content<Tui> {
Tui::bold(true, Bsp::e( Tui::bold(true, Bsp::e(
Tui::fg_bg(Tui::orange(), Tui::g(0), Bsp::e(Tui::fg(Tui::g(0), &""), Bsp::e(key, Tui::fg(Tui::g(96), &"")))), Tui::fg_bg(Tui::orange(), Tui::g(0), Bsp::e(Tui::fg(Tui::g(0), &""), Bsp::e(key, Tui::fg(Tui::g(96), &"")))),
When::new(!editing, Tui::fg_bg(Tui::g(255), Tui::g(96), label)))) When::new(!editing, Tui::fg_bg(Tui::g(255), Tui::g(96), label))))
@ -1703,8 +1748,8 @@ mod xywh {
/// let _ = tengri::button_3("", "", "", false); /// let _ = tengri::button_3("", "", "", false);
/// ``` /// ```
pub fn button_3 <'a> ( pub fn button_3 <'a> (
key: impl Content<TuiOut>, label: impl Content<TuiOut>, value: impl Content<TuiOut>, editing: bool, key: impl Content<Tui>, label: impl Content<Tui>, value: impl Content<Tui>, editing: bool,
) -> impl Content<TuiOut> { ) -> impl Content<Tui> {
Tui::bold(true, Bsp::e( Tui::bold(true, Bsp::e(
Tui::fg_bg(Tui::orange(), Tui::g(0), Tui::fg_bg(Tui::orange(), Tui::g(0),
Bsp::e(Tui::fg(Tui::g(0), &""), Bsp::e(key, Tui::fg(if editing { Tui::g(128) } else { Tui::g(96) }, "")))), Bsp::e(Tui::fg(Tui::g(0), &""), Bsp::e(key, Tui::fg(if editing { Tui::g(128) } else { Tui::g(96) }, "")))),
@ -1731,9 +1776,9 @@ mod xywh {
fn enabled (&self) -> bool { self.0 } fn enabled (&self) -> bool { self.0 }
} }
#[derive(Copy, Clone)] pub struct $T(pub bool, pub Style); #[derive(Copy, Clone)] pub struct $T(pub bool, pub Style);
impl Layout<TuiOut> for $T {} impl Layout<Tui> for $T {}
impl Draw<TuiOut> for $T { impl Draw<Tui> for $T {
fn draw (&self, to: &mut TuiOut) { fn draw (&self, to: &mut Tui) {
if self.enabled() { let _ = BorderStyle::draw(self, to); } if self.enabled() { let _ = BorderStyle::draw(self, to); }
} }
} }

View file

@ -2,56 +2,51 @@ pub use self::logical::*; mod logical {
use crate::*; use crate::*;
/// Thunks can be natural error boundaries! /// Thunks can be natural error boundaries!
///
/// ```
/// let _ = tengri::ErrorBoundary::<tengri::Tui, &'static str>::new(Ok(Some("hello")));
/// let _ = tengri::ErrorBoundary::<tengri::Tui, &'static str>::new(Ok(None));
/// let _ = tengri::ErrorBoundary::<tengri::Tui, &'static str>::new(Err("draw fail".into()));
/// ```
pub struct ErrorBoundary<O: Out, T: Draw<O>>( pub struct ErrorBoundary<O: Out, T: Draw<O>>(
pub std::marker::PhantomData<O>, pub Perhaps<T>,
pub Perhaps<T>
);
// TODO DOCUMENTME
pub struct Lazy<O, T, F>(
pub F,
pub PhantomData<(O, T)>
);
// TODO DOCUMENTME
pub struct Thunk<O: Out, F: Fn(&mut O)>(
pub PhantomData<O>, pub PhantomData<O>,
pub F
); );
// TODO DOCUMENTME /// Late-evaluate a rendering.
///
/// ```
/// let _ = tengri::Thunk::<tengri::Tui, _>::new(|out|{});
/// ```
pub struct Thunk<O: Out, F: Fn(&mut O)>(
pub F,
pub PhantomData<O>,
);
/// Memoize a rendering.
///
/// ```
/// let _ = tengri::Memo::new((), ());
/// ```
#[derive(Debug, Default)] pub struct Memo<T, U> { #[derive(Debug, Default)] pub struct Memo<T, U> {
pub value: T, pub value: T,
pub view: Arc<RwLock<U>> pub view: Arc<RwLock<U>>
} }
} }
pub use self::temporal::*; mod temporal {
use crate::*;
/// 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,
}
}
pub use self::spatial::*; mod spatial { pub use self::spatial::*; mod spatial {
use crate::*; use crate::*;
// TODO DOCUMENTME
pub struct Bordered<S, W>(pub bool, pub S, pub W);
// TODO DOCUMENTME
pub struct Border<S>(pub bool, pub S);
/// A binary split or layer. /// A binary split or layer.
///
/// ```
/// let _ = tengri::Bsp::n((), ());
/// let _ = tengri::Bsp::s((), ());
/// let _ = tengri::Bsp::e((), ());
/// let _ = tengri::Bsp::w((), ());
/// let _ = tengri::Bsp::a((), ());
/// let _ = tengri::Bsp::b((), ());
/// ```
pub struct Bsp<Head, Tail>( pub struct Bsp<Head, Tail>(
/// Direction of split /// Direction of split
pub(crate) Direction, pub(crate) Direction,
@ -118,7 +113,7 @@ pub use self::spatial::*; mod spatial {
/// A widget that tracks its rendered width and height. /// A widget that tracks its rendered width and height.
/// ///
/// ``` /// ```
/// let measure = tengri::Measure::<tengri::TuiOut>::default(); /// let measure = tengri::Measure::<tengri::Tui>::default();
/// ``` /// ```
#[derive(Default)] pub struct Measure<O: Out> { #[derive(Default)] pub struct Measure<O: Out> {
pub __: PhantomData<O>, pub __: PhantomData<O>,
@ -129,7 +124,7 @@ pub use self::spatial::*; mod spatial {
/// Show an item only when a condition is true. /// Show an item only when a condition is true.
/// ///
/// ``` /// ```
/// fn test () -> impl tengri::Draw<tengri::TuiOut> { /// fn test () -> impl tengri::Draw<tengri::Tui> {
/// tengri::when(true, "Yes") /// tengri::when(true, "Yes")
/// } /// }
/// ``` /// ```
@ -141,7 +136,7 @@ pub use self::spatial::*; mod spatial {
/// Show one item if a condition is true and another if the condition is false. /// Show one item if a condition is true and another if the condition is false.
/// ///
/// ``` /// ```
/// fn test () -> impl tengri::Draw<tengri::TuiOut> { /// fn test () -> impl tengri::Draw<tengri::Tui> {
/// tengri::either(true, "Yes", "No") /// tengri::either(true, "Yes", "No")
/// } /// }
/// ``` /// ```
@ -212,7 +207,7 @@ pub use self::spatial::*; mod spatial {
/// ``` /// ```
/// use ::tengri::*; /// use ::tengri::*;
/// let area = XYWH(10u16, 10, 20, 20); /// let area = XYWH(10u16, 10, 20, 20);
/// fn test (area: XYWH<u16>, item: &impl Draw<TuiOut>, expected: [u16;4]) { /// fn test (area: XYWH<u16>, item: &impl Draw<Tui>, expected: [u16;4]) {
/// //assert_eq!(Lay::layout(item, area), expected); /// //assert_eq!(Lay::layout(item, area), expected);
/// //assert_eq!(Draw::layout(item, area), expected); /// //assert_eq!(Draw::layout(item, area), expected);
/// }; /// };
@ -242,13 +237,19 @@ pub use self::spatial::*; mod spatial {
// TODO DOCUMENTME // TODO DOCUMENTME
pub enum Pad<U, A> { X(U, A), Y(U, A), XY(U, U, A), } pub enum Pad<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
// TODO DOCUMENTME
pub struct Bordered<S, W>(pub bool, pub S, pub W);
// TODO DOCUMENTME
pub struct Border<S>(pub bool, pub S);
/// TODO DOCUMENTME /// TODO DOCUMENTME
/// ///
/// ``` /// ```
/// use tengri::{Bounded, XYWH}; /// use tengri::{Bounded, XYWH};
/// let area = XYWH(0, 0, 0, 0); /// let area = XYWH(0, 0, 0, 0);
/// let content = ""; /// let content = "";
/// let bounded: Bounded<tengri::TuiOut, _> = Bounded(area, content); /// let bounded: Bounded<tengri::Tui, _> = Bounded(area, content);
/// ``` /// ```
pub struct Bounded<O: Out, D>(pub XYWH<O::Unit>, pub D); pub struct Bounded<O: Out, D>(pub XYWH<O::Unit>, pub D);
@ -314,24 +315,26 @@ pub use self::spatial::*; mod spatial {
/// # Ok(()) } /// # Ok(()) }
/// ``` /// ```
pub struct Tui { pub struct Tui {
/// Exit flag
pub exited: Arc<AtomicBool>, pub exited: Arc<AtomicBool>,
pub backend: CrosstermBackend<Box<dyn Write + Send + Sync>>, /// Error message
pub buffer: Buffer, pub error: Option<Arc<str>>,
pub area: [u16;4], /// Performance counter
pub perf: PerfModel, pub perf: PerfModel,
/// Ratatui backend
pub backend: RwLock<CrosstermBackend<Box<dyn Write + Send + Sync>>>,
/// Current tnput event
pub event: Option<TuiEvent>,
/// Current output buffer
pub buffer: RwLock<Buffer>,
/// FIXME unused?
pub area: XYWH<u16>,
} }
/// The TUI input event source. #[derive(Debug)] pub struct TuiThread {
/// pub exit: Arc<AtomicBool>,
/// ``` pub perf: Arc<PerfModel>,
/// use ::{tengri::{TuiIn, TuiEvent}, std::sync::Arc}; pub join: JoinHandle<()>,
/// let tui_in = TuiIn { event: 'f'.into(), exited: Arc::new(false.into()) };
/// ```
#[derive(Debug, Clone)] pub struct TuiIn {
/// Input event
pub event: TuiEvent,
/// Exit flag
pub exited: Arc<AtomicBool>,
} }
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent( #[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent(
@ -343,16 +346,6 @@ pub use self::spatial::*; mod spatial {
pub KeyModifiers pub KeyModifiers
); );
/// The TUI output render target.
///
/// ```
/// let tui_out = tengri::TuiOut::default();
/// ```
#[derive(Default)] pub struct TuiOut {
pub buffer: Buffer,
pub area: XYWH<u16>,
}
/// TUI buffer sized by `usize` instead of `u16`. /// TUI buffer sized by `usize` instead of `u16`.
#[derive(Default)] pub struct BigBuffer { #[derive(Default)] pub struct BigBuffer {
pub width: usize, pub width: usize,
@ -499,3 +492,19 @@ mod textual {
pub type BoxedJackEventHandler<'j> = pub type BoxedJackEventHandler<'j> =
Box<dyn Fn(JackEvent) + Send + Sync + 'j>; Box<dyn Fn(JackEvent) + Send + Sync + 'j>;
} }
pub use self::temporal::*; mod temporal {
use crate::*;
/// Performance counter
#[derive(Debug)]
pub struct PerfModel {
pub clock: quanta::Clock,
/// Measurement has a small cost. Disable it here.
pub enabled: bool,
// In nanoseconds. Time used by last iteration.
pub used: AtomicF64,
// In microseconds. Max prescribed time for iteration (frame, chunk...).
pub window: AtomicF64,
}
}

View file

@ -235,20 +235,20 @@ pub use self::output::*; mod output {
#[cfg(feature = "tui")] mod tui { #[cfg(feature = "tui")] mod tui {
use crate::*; use crate::*;
use ratatui::prelude::*; use ratatui::prelude::*;
pub trait TuiDraw = Draw<TuiOut>; pub trait TuiDraw = Draw<Tui>;
pub trait TuiLayout = crate::Layout<TuiOut>; pub trait TuiLayout = crate::Layout<Tui>;
pub trait TuiContent = Content<TuiOut>; pub trait TuiContent = Content<Tui>;
pub trait TuiHandle = Handle<TuiIn>; pub trait TuiHandle = Handle<Tui>;
pub trait TuiWidget = TuiDraw + TuiHandle; pub trait TuiWidget = TuiDraw + TuiHandle;
pub trait BorderStyle: Content<TuiOut> + Copy { pub trait BorderStyle: Content<Tui> + Copy {
fn enabled (&self) -> bool; fn enabled (&self) -> bool;
fn enclose (self, w: impl Content<TuiOut>) -> impl Content<TuiOut> { fn enclose (self, w: impl Content<Tui>) -> impl Content<Tui> {
Bsp::b(Fill::XY(Border(self.enabled(), self)), w) Bsp::b(Fill::XY(Border(self.enabled(), self)), w)
} }
fn enclose2 (self, w: impl Content<TuiOut>) -> impl Content<TuiOut> { fn enclose2 (self, w: impl Content<Tui>) -> impl Content<Tui> {
Bsp::b(Pad::XY(1, 1, Fill::XY(Border(self.enabled(), self))), w) Bsp::b(Pad::XY(1, 1, Fill::XY(Border(self.enabled(), self))), w)
} }
fn enclose_bg (self, w: impl Content<TuiOut>) -> impl Content<TuiOut> { fn enclose_bg (self, w: impl Content<Tui>) -> impl Content<Tui> {
Tui::bg(self.style().unwrap().bg.unwrap_or(Color::Reset), Tui::bg(self.style().unwrap().bg.unwrap_or(Color::Reset),
Bsp::b(Fill::XY(Border(self.enabled(), self)), w)) Bsp::b(Fill::XY(Border(self.enabled(), self)), w))
} }
@ -275,7 +275,7 @@ pub use self::output::*; mod output {
fn border_sw (&self) -> &str { Self::SW } fn border_sw (&self) -> &str { Self::SW }
fn border_se (&self) -> &str { Self::SE } fn border_se (&self) -> &str { Self::SE }
#[inline] fn draw <'a> ( #[inline] fn draw <'a> (
&self, to: &mut TuiOut &self, to: &mut Tui
) -> Usually<()> { ) -> Usually<()> {
if self.enabled() { if self.enabled() {
self.draw_horizontal(to, None)?; self.draw_horizontal(to, None)?;
@ -285,7 +285,7 @@ pub use self::output::*; mod output {
Ok(()) Ok(())
} }
#[inline] fn draw_horizontal ( #[inline] fn draw_horizontal (
&self, to: &mut TuiOut, style: Option<Style> &self, to: &mut Tui, style: Option<Style>
) -> Usually<XYWH<u16>> { ) -> Usually<XYWH<u16>> {
let area = to.area(); let area = to.area();
let style = style.or_else(||self.style_horizontal()); let style = style.or_else(||self.style_horizontal());
@ -297,7 +297,7 @@ pub use self::output::*; mod output {
Ok(area) Ok(area)
} }
#[inline] fn draw_vertical ( #[inline] fn draw_vertical (
&self, to: &mut TuiOut, style: Option<Style> &self, to: &mut Tui, style: Option<Style>
) -> Usually<XYWH<u16>> { ) -> Usually<XYWH<u16>> {
let area = to.area(); let area = to.area();
let style = style.or_else(||self.style_vertical()); let style = style.or_else(||self.style_vertical());
@ -315,7 +315,7 @@ pub use self::output::*; mod output {
Ok(area) Ok(area)
} }
#[inline] fn draw_corners ( #[inline] fn draw_corners (
&self, to: &mut TuiOut, style: Option<Style> &self, to: &mut Tui, style: Option<Style>
) -> Usually<XYWH<u16>> { ) -> Usually<XYWH<u16>> {
let area = to.area(); let area = to.area();
let style = style.or_else(||self.style_corners()); let style = style.or_else(||self.style_corners());