mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-03-13 12:10:44 +01:00
This commit is contained in:
parent
f1dda6af07
commit
ecba0cc64f
5 changed files with 322 additions and 270 deletions
|
|
@ -4,7 +4,6 @@ namespace!(State: bool {});
|
|||
namespace!(State: u16 {});
|
||||
namespace!(State: Color {});
|
||||
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 ]);
|
||||
draw!(State: TuiOut: [ draw_example ]);
|
||||
#[derive(Debug, Default)] struct State {
|
||||
|
|
@ -12,6 +11,7 @@ namespace!(State: Color {});
|
|||
/** Command history (undo/redo) */ history: Vec<Action>,
|
||||
/** User-controllable value */ cursor: usize,
|
||||
}
|
||||
impl_from!(Action: |input: &TuiIn| todo!());
|
||||
#[derive(Debug)] enum Action {
|
||||
/** Increment cursor */ Next,
|
||||
/** Decrement cursor */ Prev
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ pub extern crate unicode_width; pub(crate) use unicode_width::*;
|
|||
};
|
||||
pub(crate) use ::std::{
|
||||
io::{stdout, Stdout, Write},
|
||||
sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}},
|
||||
sync::{Arc, Weak, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}},
|
||||
fmt::{Debug, Display},
|
||||
ops::{Add, Sub, Mul, Div},
|
||||
marker::PhantomData,
|
||||
|
|
@ -156,8 +156,6 @@ pub(crate) use ::std::{
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Define layout operation.
|
||||
///
|
||||
/// ```
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ impl<'a, T: AsRef<str>> TrimString<T> {
|
|||
fn as_ref (&self) -> TrimStringRef<'_, T> { TrimStringRef(self.0, &self.1) }
|
||||
}
|
||||
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> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
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<E: Out, T: AsRef<Measure<E>>> Measured<E> for T {
|
||||
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> {
|
||||
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> {
|
||||
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<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> {
|
||||
if newval != self.value {
|
||||
let result = draw(&mut*self.view.write().unwrap(), &newval, &self.value);
|
||||
|
|
@ -871,60 +870,119 @@ mod xywh {
|
|||
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 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> {
|
||||
let backend = CrosstermBackend::new(output);
|
||||
let Size { width, height } = backend.size()?;
|
||||
Ok(Self {
|
||||
exited: Arc::new(AtomicBool::new(false)),
|
||||
buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }),
|
||||
area: [0, 0, width, height],
|
||||
buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }).into(),
|
||||
area: XYWH(0, 0, width, height),
|
||||
perf: Default::default(),
|
||||
backend,
|
||||
backend: backend.into(),
|
||||
event: None,
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
/// Create and launch a terminal user interface.
|
||||
pub fn run <T> (self, join: bool, state: &Arc<RwLock<T>>) -> Usually<Arc<RwLock<Self>>> where
|
||||
T: Handle<TuiIn> + Draw<TuiOut> + Send + Sync + 'static
|
||||
/// Run an amm in the engine.
|
||||
pub fn run <T> (mut self, join: bool, state: &Arc<RwLock<T>>) -> Usually<Arc<Self>> where
|
||||
T: Handle<Tui> + Draw<Tui> + Send + Sync + 'static
|
||||
{
|
||||
let tui = Arc::new(RwLock::new(self));
|
||||
let _input_thread = tui_input(tui.clone(), state, Duration::from_millis(100));
|
||||
tui.write().unwrap().setup()?;
|
||||
let render_thread = tui_output(tui.clone(), state, Duration::from_millis(10))?;
|
||||
self.setup()?;
|
||||
let tui = Arc::new(self);
|
||||
let _input_thread = tui_input(&tui, state, Duration::from_millis(100))?;
|
||||
let render_thread = tui_output(&tui, state, Duration::from_millis(10))?;
|
||||
if join {
|
||||
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")
|
||||
},
|
||||
let result = render_thread.join();
|
||||
tui.teardown()?;
|
||||
match result {
|
||||
Ok(result) => println!("\n\rRan successfully: {result:?}\n\r"),
|
||||
Err(error) => panic!("\n\rDraw thread failed: error={error:?}.\n\r"),
|
||||
}
|
||||
}
|
||||
Ok(tui)
|
||||
}
|
||||
/// True if done
|
||||
pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) }
|
||||
/// Prepare before run
|
||||
pub fn setup (&mut self) -> Usually<()> { tui_setup(&mut self.backend) }
|
||||
/// Clean up after run
|
||||
pub fn teardown (&mut self) -> Usually<()> { tui_teardown(&mut self.backend) }
|
||||
/// Apply changes to the display buffer.
|
||||
pub fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer {
|
||||
tui_resized(&mut self.backend, &mut self.buffer, size);
|
||||
tui_redrawn(&mut self.backend, &mut self.buffer, &mut buffer);
|
||||
buffer
|
||||
}
|
||||
}
|
||||
|
||||
impl Input for TuiIn {
|
||||
/// Spawn the input thread.
|
||||
pub fn tui_input <T: Handle<Tui> + Send + Sync + 'static> (
|
||||
engine: &Arc<Tui>, state: &Arc<RwLock<T>>, poll: Duration
|
||||
) -> Result<TuiThread, std::io::Error> {
|
||||
let state = state.clone();
|
||||
let engine = engine.clone();
|
||||
TuiThread::new_poll(engine.exited.clone(), poll, move |_| {
|
||||
let event = read().unwrap();
|
||||
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 }
|
||||
fn event (&self) -> &TuiEvent {
|
||||
self.event.as_ref().expect("input.event called outside of input handler")
|
||||
}
|
||||
}
|
||||
|
||||
impl Done for TuiIn {
|
||||
impl Done for Tui {
|
||||
fn done (&self) { self.exited.store(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;
|
||||
#[inline] fn area (&self) -> XYWH<u16> { 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 }
|
||||
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_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);}) }
|
||||
|
|
@ -1044,7 +1104,7 @@ mod xywh {
|
|||
}
|
||||
}
|
||||
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.bg = bg;
|
||||
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>) {
|
||||
let text = text.as_ref();
|
||||
let buf = &mut self.buffer;
|
||||
let style = style.unwrap_or(Style::default());
|
||||
let mut buf = self.buffer.write().unwrap();
|
||||
if x < buf.area.width && y < buf.area.height {
|
||||
buf.set_string(x, y, text, style);
|
||||
}
|
||||
|
|
@ -1063,7 +1123,7 @@ mod xywh {
|
|||
/// TODO: do a paragraph (handle newlines)
|
||||
pub fn text (&mut self, text: &impl AsRef<str>, x0: u16, y: u16, max_width: u16) {
|
||||
let text = text.as_ref();
|
||||
let buf = &mut self.buffer;
|
||||
let mut buf = &mut self.buffer;
|
||||
let mut string_width: u16 = 0;
|
||||
for character in text.chars() {
|
||||
let x = x0 + string_width;
|
||||
|
|
@ -1072,7 +1132,7 @@ mod xywh {
|
|||
if string_width > max_width {
|
||||
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);
|
||||
} else {
|
||||
break
|
||||
|
|
@ -1098,9 +1158,7 @@ mod xywh {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn tui_setup <W: Write> (
|
||||
backend: &mut CrosstermBackend<W>
|
||||
) -> Usually<()> {
|
||||
pub fn tui_setup <W: Write> (backend: &mut CrosstermBackend<W>) -> 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();
|
||||
|
|
@ -1159,82 +1217,66 @@ mod xywh {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
impl TuiThread {
|
||||
/// Spawn a TUI thread that runs `callt least one, then repeats until `exit`.
|
||||
pub fn new <F> (exit: Arc<AtomicBool>, call: F) -> Result<Self, std::io::Error>
|
||||
where F: Fn(&PerfModel)->() + Send + Sync + 'static
|
||||
{
|
||||
let perf = Arc::new(PerfModel::default());
|
||||
Ok(Self {
|
||||
exit: exit.clone(),
|
||||
perf: perf.clone(),
|
||||
join: std::thread::Builder::new().name("tengri tui output".into()).spawn(move || {
|
||||
while !exit.fetch_and(true, Relaxed) {
|
||||
let _ = perf.cycle(&call);
|
||||
}
|
||||
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);
|
||||
})?.into()
|
||||
})
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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); })
|
||||
}
|
||||
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}")
|
||||
|
||||
/// 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()
|
||||
}
|
||||
}
|
||||
|
||||
impl PerfModel {
|
||||
fn cycle <F: Fn(&Self)->T, T> (&self, call: &F) -> T {
|
||||
let t0 = self.get_t0();
|
||||
let result = call(self);
|
||||
let _t1 = self.get_t1(t0).unwrap();
|
||||
result
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
impl<T> Phat<T> {
|
||||
pub const LO: &'static str = "▄";
|
||||
pub const HI: &'static str = "▀";
|
||||
/// 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)))
|
||||
}
|
||||
/// 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)))
|
||||
}
|
||||
}
|
||||
|
|
@ -1244,17 +1286,17 @@ mod xywh {
|
|||
const ICON_DEC_H: &[char] = &[' ', '🞀', ' '];
|
||||
const ICON_INC_H: &[char] = &[' ', '🞂', ' '];
|
||||
}
|
||||
impl<S: BorderStyle, W: Content<TuiOut>> HasContent<TuiOut> for Bordered<S, W> {
|
||||
fn content (&self) -> impl Content<TuiOut> {
|
||||
impl<S: BorderStyle, W: Content<Tui>> HasContent<Tui> for Bordered<S, W> {
|
||||
fn content (&self) -> impl Content<Tui> {
|
||||
Fill::XY(lay!( When::new(self.0, Border(self.0, self.1)), Pad::XY(1, 1, &self.2) ))
|
||||
}
|
||||
}
|
||||
impl<
|
||||
A: Content<TuiOut>,
|
||||
B: Content<TuiOut>,
|
||||
C: Content<TuiOut>,
|
||||
> HasContent<TuiOut> for Tryptich<A, B, C> {
|
||||
fn content (&self) -> impl Content<TuiOut> {
|
||||
A: Content<Tui>,
|
||||
B: Content<Tui>,
|
||||
C: Content<Tui>,
|
||||
> HasContent<Tui> for Tryptich<A, B, C> {
|
||||
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;
|
||||
Fixed::Y(h, if top {
|
||||
Bsp::a(
|
||||
|
|
@ -1275,8 +1317,8 @@ mod xywh {
|
|||
})
|
||||
}
|
||||
}
|
||||
impl<T: Content<TuiOut>> HasContent<TuiOut> for Phat<T> {
|
||||
fn content (&self) -> impl Content<TuiOut> {
|
||||
impl<T: Content<Tui>> HasContent<Tui> for Phat<T> {
|
||||
fn content (&self) -> impl Content<Tui> {
|
||||
let [fg, bg, hi, lo] = self.colors;
|
||||
let top = Fixed::Y(1, Self::lo(bg, hi));
|
||||
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))))
|
||||
}
|
||||
}
|
||||
impl<T: Content<TuiOut>> Layout<TuiOut> for Modify<T> {}
|
||||
impl<T: Content<TuiOut>> Layout<TuiOut> for Styled<T> {}
|
||||
impl Layout<TuiOut> for Repeat<'_> {}
|
||||
impl Layout<TuiOut> for &str {
|
||||
impl<T: Content<Tui>> Layout<Tui> for Modify<T> {}
|
||||
impl<T: Content<Tui>> Layout<Tui> for Styled<T> {}
|
||||
impl Layout<Tui> for Repeat<'_> {}
|
||||
impl Layout<Tui> for &str {
|
||||
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
|
||||
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> {
|
||||
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> {
|
||||
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> {
|
||||
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> {
|
||||
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> {
|
||||
fn draw (&self, to: &mut TuiOut) {
|
||||
match self.1.as_ref() {
|
||||
impl<T: Draw<Tui>> Draw<Tui> for ErrorBoundary<Tui, T> {
|
||||
fn draw (&self, to: &mut Tui) {
|
||||
match self.0.as_ref() {
|
||||
Ok(Some(content)) => content.draw(to),
|
||||
Ok(None) => to.blit(&"empty?", 0, 0, Some(Style::default().yellow())),
|
||||
Err(e) => {
|
||||
|
|
@ -1328,32 +1370,33 @@ mod xywh {
|
|||
}
|
||||
}
|
||||
|
||||
impl Draw<TuiOut> for u64 {
|
||||
fn draw (&self, _to: &mut TuiOut) {
|
||||
impl Draw<Tui> for u64 {
|
||||
fn draw (&self, _to: &mut Tui) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Draw<TuiOut> for f64 {
|
||||
fn draw (&self, _to: &mut TuiOut) {
|
||||
impl Draw<Tui> for f64 {
|
||||
fn draw (&self, _to: &mut Tui) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Draw<TuiOut> for Repeat<'_> {
|
||||
fn draw (&self, to: &mut TuiOut) {
|
||||
impl Draw<Tui> for Repeat<'_> {
|
||||
fn draw (&self, to: &mut Tui) {
|
||||
let XYWH(x, y, w, h) = to.area();
|
||||
let mut buf = to.buffer.write().unwrap();
|
||||
match self {
|
||||
Self::X(c) => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
},
|
||||
Self::Y(c) => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1362,7 +1405,7 @@ mod xywh {
|
|||
let a = c.len();
|
||||
for (_v, y) in (y..y+h).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;
|
||||
cell.set_symbol(&c[u..u+1]);
|
||||
}
|
||||
|
|
@ -1373,14 +1416,15 @@ mod xywh {
|
|||
}
|
||||
}
|
||||
|
||||
impl Draw<TuiOut> for Scrollbar {
|
||||
fn draw (&self, to: &mut TuiOut) {
|
||||
impl Draw<Tui> for Scrollbar {
|
||||
fn draw (&self, to: &mut Tui) {
|
||||
let XYWH(x1, y1, w, h) = to.area();
|
||||
let mut buf = to.buffer.write().unwrap();
|
||||
match self {
|
||||
Self::X { .. } => {
|
||||
let x2 = x1 + w;
|
||||
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()) {
|
||||
cell.set_fg(Rgb(255, 255, 255));
|
||||
cell.set_bg(Rgb(0, 0, 0));
|
||||
|
|
@ -1404,7 +1448,7 @@ mod xywh {
|
|||
Self::Y { .. } => {
|
||||
let y2 = y1 + h;
|
||||
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()) {
|
||||
cell.set_fg(Rgb(255, 255, 255));
|
||||
cell.set_bg(Rgb(0, 0, 0));
|
||||
|
|
@ -1429,55 +1473,55 @@ mod xywh {
|
|||
}
|
||||
}
|
||||
|
||||
impl Draw<TuiOut> for &str {
|
||||
fn draw (&self, to: &mut TuiOut) {
|
||||
impl Draw<Tui> for &str {
|
||||
fn draw (&self, to: &mut Tui) {
|
||||
let XYWH(x, y, w, ..) = self.layout(to.area());
|
||||
to.text(&self, x, y, w)
|
||||
}
|
||||
}
|
||||
|
||||
impl Draw<TuiOut> for String {
|
||||
fn draw (&self, to: &mut TuiOut) {
|
||||
impl Draw<Tui> for String {
|
||||
fn draw (&self, to: &mut Tui) {
|
||||
self.as_str().draw(to)
|
||||
}
|
||||
}
|
||||
|
||||
impl Draw<TuiOut> for Arc<str> {
|
||||
fn draw (&self, to: &mut TuiOut) { self.as_ref().draw(to) }
|
||||
impl Draw<Tui> for Arc<str> {
|
||||
fn draw (&self, to: &mut Tui) { self.as_ref().draw(to) }
|
||||
}
|
||||
|
||||
impl<T: Content<TuiOut>> Draw<TuiOut> for Foreground<Color, T> {
|
||||
fn draw (&self, to: &mut TuiOut) {
|
||||
impl<T: Content<Tui>> Draw<Tui> for Foreground<Color, T> {
|
||||
fn draw (&self, to: &mut Tui) {
|
||||
let area = self.layout(to.area());
|
||||
to.fill_fg(area, self.0);
|
||||
to.place_at(area, &self.1);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Content<TuiOut>> Draw<TuiOut> for Background<Color, T> {
|
||||
fn draw (&self, to: &mut TuiOut) {
|
||||
impl<T: Content<Tui>> Draw<Tui> for Background<Color, T> {
|
||||
fn draw (&self, to: &mut Tui) {
|
||||
let area = self.layout(to.area());
|
||||
to.fill_bg(area, self.0);
|
||||
to.place_at(area, &self.1);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Content<TuiOut>> Draw<TuiOut> for Modify<T> {
|
||||
fn draw (&self, to: &mut TuiOut) {
|
||||
impl<T: Content<Tui>> Draw<Tui> for Modify<T> {
|
||||
fn draw (&self, to: &mut Tui) {
|
||||
to.fill_mod(to.area(), self.0, self.1);
|
||||
self.2.draw(to)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Content<TuiOut>> Draw<TuiOut> for Styled<T> {
|
||||
fn draw (&self, to: &mut TuiOut) {
|
||||
impl<T: Content<Tui>> Draw<Tui> for Styled<T> {
|
||||
fn draw (&self, to: &mut Tui) {
|
||||
to.place(&self.1);
|
||||
// TODO write style over area
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: BorderStyle> Draw<TuiOut> for Border<S> {
|
||||
fn draw (&self, to: &mut TuiOut) {
|
||||
impl<S: BorderStyle> Draw<Tui> for Border<S> {
|
||||
fn draw (&self, to: &mut Tui) {
|
||||
let Border(enabled, style) = self;
|
||||
if *enabled {
|
||||
let area = to.area();
|
||||
|
|
@ -1499,20 +1543,21 @@ mod xywh {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AsRef<str>> Draw<TuiOut> for TrimString<T> {
|
||||
fn draw (&self, to: &mut TuiOut) { Draw::draw(&self.as_ref(), to) }
|
||||
impl<'a, T: AsRef<str>> Draw<Tui> for TrimString<T> {
|
||||
fn draw (&self, to: &mut Tui) { Draw::draw(&self.as_ref(), to) }
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> Draw<TuiOut> for TrimStringRef<'_, T> {
|
||||
fn draw (&self, target: &mut TuiOut) {
|
||||
impl<T: AsRef<str>> Draw<Tui> for TrimStringRef<'_, T> {
|
||||
fn draw (&self, target: &mut Tui) {
|
||||
let area = target.area();
|
||||
let mut buf = target.buffer.write().unwrap();
|
||||
let mut width: u16 = 1;
|
||||
let mut chars = self.1.as_ref().chars();
|
||||
while let Some(c) = chars.next() {
|
||||
if width > self.0 || width > area.w() {
|
||||
break
|
||||
}
|
||||
if let Some(cell) = target.buffer.cell_mut(Position {
|
||||
if let Some(cell) = buf.cell_mut(Position {
|
||||
x: area.x() + width - 1,
|
||||
y: area.y()
|
||||
}) {
|
||||
|
|
@ -1625,16 +1670,16 @@ mod xywh {
|
|||
/// Should be impl something or other...
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::{Namespace, Understand, TuiOut, ratatui::prelude::Color};
|
||||
/// use tengri::{Namespace, Understand, Tui, ratatui::prelude::Color};
|
||||
///
|
||||
/// struct State;
|
||||
/// impl<'b> Namespace<'b, bool> for State {}
|
||||
/// impl<'b> Namespace<'b, u16> for State {}
|
||||
/// impl<'b> Namespace<'b, Color> for State {}
|
||||
/// impl Understand<TuiOut, ()> for State {}
|
||||
/// impl Understand<Tui, ()> for State {}
|
||||
/// # fn main () -> tengri::Usually<()> {
|
||||
/// 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, "text Hello world!")?;
|
||||
/// tengri::evaluate_output_expression_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?;
|
||||
|
|
@ -1643,9 +1688,9 @@ mod xywh {
|
|||
/// # Ok(()) }
|
||||
/// ```
|
||||
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
|
||||
S: Understand<TuiOut, ()>
|
||||
S: Understand<Tui, ()>
|
||||
+ for<'b>Namespace<'b, bool>
|
||||
+ for<'b>Namespace<'b, u16>
|
||||
+ for<'b>Namespace<'b, Color>
|
||||
|
|
@ -1668,7 +1713,7 @@ mod xywh {
|
|||
Some("fg") => {
|
||||
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 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))
|
||||
},
|
||||
|
||||
|
|
@ -1678,7 +1723,7 @@ mod xywh {
|
|||
//panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}");
|
||||
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 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))
|
||||
},
|
||||
|
||||
|
|
@ -1692,7 +1737,7 @@ mod xywh {
|
|||
/// let _ = tengri::button_2("", "", true);
|
||||
/// 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::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))))
|
||||
|
|
@ -1703,8 +1748,8 @@ mod xywh {
|
|||
/// let _ = tengri::button_3("", "", "", false);
|
||||
/// ```
|
||||
pub fn button_3 <'a> (
|
||||
key: impl Content<TuiOut>, label: impl Content<TuiOut>, value: impl Content<TuiOut>, editing: bool,
|
||||
) -> impl Content<TuiOut> {
|
||||
key: impl Content<Tui>, label: impl Content<Tui>, value: impl Content<Tui>, editing: bool,
|
||||
) -> impl Content<Tui> {
|
||||
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(if editing { Tui::g(128) } else { Tui::g(96) }, "▐")))),
|
||||
|
|
@ -1731,9 +1776,9 @@ mod xywh {
|
|||
fn enabled (&self) -> bool { self.0 }
|
||||
}
|
||||
#[derive(Copy, Clone)] pub struct $T(pub bool, pub Style);
|
||||
impl Layout<TuiOut> for $T {}
|
||||
impl Draw<TuiOut> for $T {
|
||||
fn draw (&self, to: &mut TuiOut) {
|
||||
impl Layout<Tui> for $T {}
|
||||
impl Draw<Tui> for $T {
|
||||
fn draw (&self, to: &mut Tui) {
|
||||
if self.enabled() { let _ = BorderStyle::draw(self, to); }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,56 +2,51 @@ pub use self::logical::*; mod logical {
|
|||
use crate::*;
|
||||
|
||||
/// 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 std::marker::PhantomData<O>,
|
||||
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 Perhaps<T>,
|
||||
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> {
|
||||
pub value: T,
|
||||
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 {
|
||||
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.
|
||||
///
|
||||
/// ```
|
||||
/// 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>(
|
||||
/// Direction of split
|
||||
pub(crate) Direction,
|
||||
|
|
@ -118,7 +113,7 @@ pub use self::spatial::*; mod spatial {
|
|||
/// 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> {
|
||||
pub __: PhantomData<O>,
|
||||
|
|
@ -129,7 +124,7 @@ pub use self::spatial::*; mod spatial {
|
|||
/// 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")
|
||||
/// }
|
||||
/// ```
|
||||
|
|
@ -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.
|
||||
///
|
||||
/// ```
|
||||
/// fn test () -> impl tengri::Draw<tengri::TuiOut> {
|
||||
/// fn test () -> impl tengri::Draw<tengri::Tui> {
|
||||
/// tengri::either(true, "Yes", "No")
|
||||
/// }
|
||||
/// ```
|
||||
|
|
@ -212,7 +207,7 @@ pub use self::spatial::*; mod spatial {
|
|||
/// ```
|
||||
/// use ::tengri::*;
|
||||
/// 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!(Draw::layout(item, area), expected);
|
||||
/// };
|
||||
|
|
@ -242,13 +237,19 @@ pub use self::spatial::*; mod spatial {
|
|||
// TODO DOCUMENTME
|
||||
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
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::{Bounded, XYWH};
|
||||
/// let area = XYWH(0, 0, 0, 0);
|
||||
/// 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);
|
||||
|
||||
|
|
@ -314,24 +315,26 @@ pub use self::spatial::*; mod spatial {
|
|||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub struct Tui {
|
||||
pub exited: Arc<AtomicBool>,
|
||||
pub backend: CrosstermBackend<Box<dyn Write + Send + Sync>>,
|
||||
pub buffer: Buffer,
|
||||
pub area: [u16;4],
|
||||
pub perf: PerfModel,
|
||||
}
|
||||
|
||||
/// The TUI input event source.
|
||||
///
|
||||
/// ```
|
||||
/// use ::{tengri::{TuiIn, TuiEvent}, std::sync::Arc};
|
||||
/// 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>,
|
||||
/// Error message
|
||||
pub error: Option<Arc<str>>,
|
||||
/// Performance counter
|
||||
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>,
|
||||
}
|
||||
|
||||
#[derive(Debug)] pub struct TuiThread {
|
||||
pub exit: Arc<AtomicBool>,
|
||||
pub perf: Arc<PerfModel>,
|
||||
pub join: JoinHandle<()>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent(
|
||||
|
|
@ -343,16 +346,6 @@ pub use self::spatial::*; mod spatial {
|
|||
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`.
|
||||
#[derive(Default)] pub struct BigBuffer {
|
||||
pub width: usize,
|
||||
|
|
@ -499,3 +492,19 @@ mod textual {
|
|||
pub type BoxedJackEventHandler<'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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,20 +235,20 @@ pub use self::output::*; mod output {
|
|||
#[cfg(feature = "tui")] mod tui {
|
||||
use crate::*;
|
||||
use ratatui::prelude::*;
|
||||
pub trait TuiDraw = Draw<TuiOut>;
|
||||
pub trait TuiLayout = crate::Layout<TuiOut>;
|
||||
pub trait TuiContent = Content<TuiOut>;
|
||||
pub trait TuiHandle = Handle<TuiIn>;
|
||||
pub trait TuiDraw = Draw<Tui>;
|
||||
pub trait TuiLayout = crate::Layout<Tui>;
|
||||
pub trait TuiContent = Content<Tui>;
|
||||
pub trait TuiHandle = Handle<Tui>;
|
||||
pub trait TuiWidget = TuiDraw + TuiHandle;
|
||||
pub trait BorderStyle: Content<TuiOut> + Copy {
|
||||
pub trait BorderStyle: Content<Tui> + Copy {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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),
|
||||
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_se (&self) -> &str { Self::SE }
|
||||
#[inline] fn draw <'a> (
|
||||
&self, to: &mut TuiOut
|
||||
&self, to: &mut Tui
|
||||
) -> Usually<()> {
|
||||
if self.enabled() {
|
||||
self.draw_horizontal(to, None)?;
|
||||
|
|
@ -285,7 +285,7 @@ pub use self::output::*; mod output {
|
|||
Ok(())
|
||||
}
|
||||
#[inline] fn draw_horizontal (
|
||||
&self, to: &mut TuiOut, style: Option<Style>
|
||||
&self, to: &mut Tui, style: Option<Style>
|
||||
) -> Usually<XYWH<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_horizontal());
|
||||
|
|
@ -297,7 +297,7 @@ pub use self::output::*; mod output {
|
|||
Ok(area)
|
||||
}
|
||||
#[inline] fn draw_vertical (
|
||||
&self, to: &mut TuiOut, style: Option<Style>
|
||||
&self, to: &mut Tui, style: Option<Style>
|
||||
) -> Usually<XYWH<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_vertical());
|
||||
|
|
@ -315,7 +315,7 @@ pub use self::output::*; mod output {
|
|||
Ok(area)
|
||||
}
|
||||
#[inline] fn draw_corners (
|
||||
&self, to: &mut TuiOut, style: Option<Style>
|
||||
&self, to: &mut Tui, style: Option<Style>
|
||||
) -> Usually<XYWH<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_corners());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue