compiles, once again.

now 616 errors downstream...
This commit is contained in:
same mf who else 2026-03-21 20:12:51 +02:00
parent eb899906f9
commit cdc513060d
8 changed files with 196 additions and 201 deletions

View file

@ -1,7 +1,7 @@
use crate::{*, lang::*, color::*, space::*}; use crate::{*, lang::*, color::*, space::*};
/// Drawable that supports dynamic dispatch. /// Drawable that supports dynamic dispatch.
/// ///
/// ``` /// ```
/// use tengri::draw::*; /// use tengri::draw::*;
/// struct TestScreen(bool); /// struct TestScreen(bool);
@ -18,31 +18,16 @@ use crate::{*, lang::*, color::*, space::*};
/// TestWidget(false).draw(&mut screen); /// TestWidget(false).draw(&mut screen);
/// ``` /// ```
pub trait Draw<T: Screen> { pub trait Draw<T: Screen> {
fn draw (&self, to: &mut T) -> Usually<XYWH<T::Unit>>; fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>>;
} }
impl<T: Screen> Draw<T> for () { impl<T: Screen> Draw<T> for () {
fn draw (&self, __: &mut T) -> Usually<XYWH<T::Unit>> { fn draw (self, __: &mut T) -> Usually<XYWH<T::Unit>> {
Ok(Default::default()) Ok(Default::default())
} }
} }
impl<T: Screen, D: Draw<T>> Draw<T> for &D {
fn draw (&self, to: &mut T) -> Usually<XYWH<T::Unit>> {
(*self).draw(to)
}
}
impl<T: Screen, D: Draw<T>> Draw<T> for Arc<D> {
fn draw (&self, to: &mut T) -> Usually<XYWH<T::Unit>> {
(**self).draw(to)
}
}
impl<T: Screen, D: Draw<T>> Draw<T> for RwLock<D> {
fn draw (&self, to: &mut T) -> Usually<XYWH<T::Unit>> {
self.read().unwrap().draw(to)
}
}
impl<T: Screen, D: Draw<T>> Draw<T> for Option<D> { impl<T: Screen, D: Draw<T>> Draw<T> for Option<D> {
fn draw (&self, to: &mut T) -> Usually<XYWH<T::Unit>> { fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
Ok(self.as_ref().map(|draw|draw.draw(to)).transpose()?.unwrap_or_default()) Ok(self.map(|draw|draw.draw(to)).transpose()?.unwrap_or_default())
} }
} }
@ -55,8 +40,8 @@ pub const fn thunk <T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> (draw:
Thunk(draw, std::marker::PhantomData) Thunk(draw, std::marker::PhantomData)
} }
impl<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> Draw<T> for Thunk<T, F> { impl<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> Draw<T> for Thunk<T, F> {
fn draw (&self, to: &mut T) -> Usually<XYWH<T::Unit>> { fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
(&self.0)(to) (self.0)(to)
} }
} }

110
src/keys.rs Normal file
View file

@ -0,0 +1,110 @@
use crate::play::Thread;
use ::std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}};
use ::std::time::Duration;
use ::dizzle::{Usually, Do, impl_from};
use ::crossterm::event::{
read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState
};
/// Spawn the TUI input thread which reads keys from the terminal.
pub fn tui_input <T: Do<TuiEvent, Usually<T>> + Send + Sync + 'static> (
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
) -> Result<Thread, std::io::Error> {
let exited = exited.clone();
let state = state.clone();
Thread::new_poll(exited.clone(), poll, move |_| {
let event = read().unwrap();
match event {
// Hardcoded exit.
Event::Key(KeyEvent {
modifiers: KeyModifiers::CONTROL,
code: KeyCode::Char('c'),
kind: KeyEventKind::Press,
state: KeyEventState::NONE
}) => { exited.store(true, Relaxed); },
// Handle all other events by the state:
event => {
if let Err(e) = state.write().unwrap().apply(&TuiEvent(event)) {
panic!("{e}")
}
},
}
})
}
/// TUI input loop event.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
pub struct TuiEvent(pub Event);
impl_from!(TuiEvent: |e: Event| TuiEvent(e));
impl_from!(TuiEvent: |c: char| TuiEvent(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE))));
impl Ord for TuiEvent {
fn cmp (&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other)
.unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf
}
}
/// TUI key spec.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
pub struct TuiKey(pub Option<KeyCode>, pub KeyModifiers);
impl TuiKey {
const SPLIT: char = '/';
pub fn from_crossterm (event: KeyEvent) -> Self {
Self(Some(event.code), event.modifiers)
}
pub fn to_crossterm (&self) -> Option<Event> {
self.0.map(|code|Event::Key(KeyEvent {
code,
modifiers: self.1,
kind: KeyEventKind::Press,
state: KeyEventState::NONE,
}))
}
pub fn named (token: &str) -> Option<KeyCode> {
use KeyCode::*;
Some(match token {
"up" => Up,
"down" => Down,
"left" => Left,
"right" => Right,
"esc" | "escape" => Esc,
"enter" | "return" => Enter,
"delete" | "del" => Delete,
"backspace" => Backspace,
"tab" => Tab,
"space" => Char(' '),
"comma" => Char(','),
"period" => Char('.'),
"plus" => Char('+'),
"minus" | "dash" => Char('-'),
"equal" | "equals" => Char('='),
"underscore" => Char('_'),
"backtick" => Char('`'),
"lt" => Char('<'),
"gt" => Char('>'),
"cbopen" | "openbrace" => Char('{'),
"cbclose" | "closebrace" => Char('}'),
"bropen" | "openbracket" => Char('['),
"brclose" | "closebracket" => Char(']'),
"pgup" | "pageup" => PageUp,
"pgdn" | "pagedown" => PageDown,
"f1" => F(1),
"f2" => F(2),
"f3" => F(3),
"f4" => F(4),
"f5" => F(5),
"f6" => F(6),
"f7" => F(7),
"f8" => F(8),
"f9" => F(9),
"f10" => F(10),
"f11" => F(11),
"f12" => F(12),
_ => return None,
})
}
}

View file

@ -37,6 +37,7 @@ pub(crate) use ::{
#[cfg(feature = "draw")] pub mod color; #[cfg(feature = "draw")] pub mod color;
#[cfg(feature = "text")] pub mod text; #[cfg(feature = "text")] pub mod text;
#[cfg(feature = "term")] pub mod term; #[cfg(feature = "term")] pub mod term;
#[cfg(feature = "term")] pub mod keys;
#[cfg(feature = "term")] pub extern crate ratatui; #[cfg(feature = "term")] pub extern crate ratatui;
#[cfg(feature = "term")] pub extern crate crossterm; #[cfg(feature = "term")] pub extern crate crossterm;

View file

@ -21,8 +21,8 @@ impl Exit {
impl Thread { impl Thread {
/// Spawn a TUI thread that runs `callt least one, then repeats until `exit`. /// 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> pub fn new <F> (exit: Arc<AtomicBool>, mut call: F) -> Result<Self, std::io::Error>
where F: Fn(&PerfModel)->() + Send + Sync + 'static where F: FnMut(&PerfModel)->() + Send + Sync + 'static
{ {
let perf = Arc::new(PerfModel::default()); let perf = Arc::new(PerfModel::default());
Ok(Self { Ok(Self {
@ -30,7 +30,7 @@ impl Thread {
perf: perf.clone(), perf: perf.clone(),
join: std::thread::Builder::new().name("tengri tui output".into()).spawn(move || { join: std::thread::Builder::new().name("tengri tui output".into()).spawn(move || {
while !exit.fetch_and(true, Relaxed) { while !exit.fetch_and(true, Relaxed) {
let _ = perf.cycle(&call); let _ = perf.cycle(&mut call);
} }
})?.into() })?.into()
}) })
@ -39,19 +39,19 @@ impl Thread {
/// Spawn a thread that runs `call` least one, then repeats /// Spawn a thread that runs `call` least one, then repeats
/// until `exit`, sleeping for `time` msec after every iteration. /// until `exit`, sleeping for `time` msec after every iteration.
pub fn new_sleep <F> ( pub fn new_sleep <F> (
exit: Arc<AtomicBool>, time: Duration, call: F exit: Arc<AtomicBool>, time: Duration, mut call: F
) -> Result<Self, std::io::Error> ) -> Result<Self, std::io::Error>
where F: Fn(&PerfModel)->() + Send + Sync + 'static where F: FnMut(&PerfModel)->() + Send + Sync + 'static
{ {
Self::new(exit, move |perf| { let _ = call(perf); std::thread::sleep(time); }) Self::new(exit, move |perf| { let _ = call(perf); std::thread::sleep(time); })
} }
/// Spawn a thread that uses [crossterm::event::poll] /// Spawn a thread that uses [crossterm::event::poll]
/// to run `call` every `time` msec. /// to run `call` every `time` msec.
#[cfg(feature = "term")]pub fn new_poll <F> ( #[cfg(feature = "term")] pub fn new_poll <F> (
exit: Arc<AtomicBool>, time: Duration, call: F exit: Arc<AtomicBool>, time: Duration, mut call: F
) -> Result<Self, std::io::Error> ) -> Result<Self, std::io::Error>
where F: Fn(&PerfModel)->() + Send + Sync + 'static where F: FnMut(&PerfModel)->() + Send + Sync + 'static
{ {
Self::new(exit, move |perf| { if poll(time).is_ok() { let _ = call(perf); } }) Self::new(exit, move |perf| { if poll(time).is_ok() { let _ = call(perf); } })
} }

View file

@ -158,17 +158,19 @@ impl Split {
/// let _ = West.bsp((), ()); /// let _ = West.bsp((), ());
/// ``` /// ```
pub const fn half <T: Screen> (&self, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> { pub const fn half <T: Screen> (&self, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|{ thunk(move|to: &mut T|{
let (area_a, area_b) = to.xywh().split_half(self); let (area_a, area_b) = to.xywh().split_half(self);
let (origin_a, origin_b) = self.origins(); let (origin_a, origin_b) = self.origins();
let a = origin_a.align(a);
let b = origin_b.align(b);
match self { match self {
Self::Below => { Self::Below => {
to.place(&origin_b.align(b), Some(area_b)); to.place(&b, Some(area_b));
to.place(&origin_a.align(a), Some(area_b)); to.place(&a, Some(area_b));
}, },
_ => { _ => {
to.place(&origin_a.align(a), Some(area_a)); to.place(&a, Some(area_a));
to.place(&origin_b.align(b), Some(area_a)); to.place(&b, Some(area_a));
} }
} }
Ok(to.xywh()) // FIXME: compute and return actually used area Ok(to.xywh()) // FIXME: compute and return actually used area

View file

@ -131,26 +131,10 @@ impl Coord for u16 {
} }
impl Draw<Tui> for u64 { impl Draw<Tui> for u64 {
fn draw (&self, _to: &mut Tui) -> Usually<XYWH<u16>> { todo!() } fn draw (self, _to: &mut Tui) -> Usually<XYWH<u16>> { todo!() }
} }
impl Draw<Tui> for f64 { impl Draw<Tui> for f64 {
fn draw (&self, _to: &mut Tui) -> Usually<XYWH<u16>> { todo!() } fn draw (self, _to: &mut Tui) -> Usually<XYWH<u16>> { todo!() }
}
impl Draw<Tui> for &str {
fn draw (&self, to: &mut Tui) -> Usually<XYWH<u16>> {
let XYWH(x, y, w, ..) = to.1.centered_xy([width_chars_max(to.w(), self), 1]);
to.text(&self, x, y, w)
}
}
impl Draw<Tui> for String {
fn draw (&self, to: &mut Tui) -> Usually<XYWH<u16>> {
self.as_str().draw(to)
}
}
impl Draw<Tui> for Arc<str> {
fn draw (&self, to: &mut Tui) -> Usually<XYWH<u16>> {
self.as_ref().draw(to)
}
} }
mod phat { mod phat {
@ -223,7 +207,7 @@ pub const fn button_2 <'a> (key: impl Draw<Tui>, label: impl Draw<Tui>, hide: bo
let c2 = tui_g(0); let c2 = tui_g(0);
let c3 = tui_g(96); let c3 = tui_g(96);
let c4 = tui_g(255); let c4 = tui_g(255);
bold(true, fg_bg(c1, c2, east(fg(c2, east(key, fg(c3, &""))), when(!hide, fg_bg(c4, c3, label))))) bold(true, fg_bg(c1, c2, east(fg(c2, east(key, fg(c3, ""))), when(!hide, fg_bg(c4, c3, label)))))
} }
/// ``` /// ```
@ -235,10 +219,10 @@ pub const fn button_3 <'a> (
) -> impl Draw<Tui> { ) -> impl Draw<Tui> {
bold(true, east( bold(true, east(
fg_bg(tui_orange(), tui_g(0), fg_bg(tui_orange(), tui_g(0),
east(fg(tui_g(0), &""), east(key, fg(if editing { tui_g(128) } else { tui_g(96) }, "")))), east(fg(tui_g(0), ""), east(key, fg(if editing { tui_g(128) } else { tui_g(96) }, "")))),
east( east(
when(!editing, east(fg_bg(tui_g(255), tui_g(96), label), fg_bg(tui_g(128), tui_g(96), &""),)), when(!editing, east(fg_bg(tui_g(255), tui_g(96), label), fg_bg(tui_g(128), tui_g(96), ""),)),
east(fg_bg(tui_g(224), tui_g(128), value), fg_bg(tui_g(128), Reset, &""), )))) east(fg_bg(tui_g(224), tui_g(128), value), fg_bg(tui_g(128), Reset, ""), ))))
} }
macro_rules! border { macro_rules! border {
@ -261,7 +245,7 @@ macro_rules! border {
#[derive(Copy, Clone)] pub struct $T(pub bool, pub Style); #[derive(Copy, Clone)] pub struct $T(pub bool, pub Style);
//impl Layout<Tui> for $T {} //impl Layout<Tui> for $T {}
impl Draw<Tui> for $T { impl Draw<Tui> for $T {
fn draw (&self, to: &mut Tui) -> Usually<XYWH<u16>> { fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
when(self.enabled(), thunk(|to: &mut Tui|BorderStyle::draw(self, to))).draw(to) when(self.enabled(), thunk(|to: &mut Tui|BorderStyle::draw(self, to))).draw(to)
} }
} }
@ -385,7 +369,7 @@ pub trait BorderStyle: Draw<Tui> + Copy {
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> (&self, to: &mut Tui) -> Usually<XYWH<u16>> { #[inline] fn draw <'a> (self, to: &mut Tui) -> Usually<XYWH<u16>> {
if self.enabled() { if self.enabled() {
self.draw_h(to, None)?; self.draw_h(to, None)?;
self.draw_v(to, None)?; self.draw_v(to, None)?;
@ -393,7 +377,7 @@ pub trait BorderStyle: Draw<Tui> + Copy {
} }
Ok(to.1) Ok(to.1)
} }
#[inline] fn draw_h (&self, to: &mut Tui, style: Option<Style>) -> Usually<XYWH<u16>> { #[inline] fn draw_h (self, to: &mut Tui, style: Option<Style>) -> Usually<XYWH<u16>> {
let style = style.or_else(||self.style_horizontal()); let style = style.or_else(||self.style_horizontal());
let y1 = to.y_north(); let y1 = to.y_north();
let y2 = to.y_south().saturating_sub(1); let y2 = to.y_south().saturating_sub(1);
@ -403,7 +387,7 @@ pub trait BorderStyle: Draw<Tui> + Copy {
} }
Ok(to.xywh()) Ok(to.xywh())
} }
#[inline] fn draw_v (&self, to: &mut Tui, style: Option<Style>) -> Usually<XYWH<u16>> { #[inline] fn draw_v (self, to: &mut Tui, style: Option<Style>) -> Usually<XYWH<u16>> {
let area = to.1; let area = to.1;
let style = style.or_else(||self.style_vertical()); let style = style.or_else(||self.style_vertical());
let [x, x2, y, y2] = area.lrtb(); let [x, x2, y, y2] = area.lrtb();
@ -419,7 +403,7 @@ pub trait BorderStyle: Draw<Tui> + Copy {
} }
Ok(area) Ok(area)
} }
#[inline] fn draw_c (&self, to: &mut Tui, style: Option<Style>) -> Usually<XYWH<u16>> { #[inline] fn draw_c (self, to: &mut Tui, style: Option<Style>) -> Usually<XYWH<u16>> {
let area = to.1; let area = to.1;
let style = style.or_else(||self.style_corners()); let style = style.or_else(||self.style_corners());
let XYWH(x, y, w, h) = area; let XYWH(x, y, w, h) = area;
@ -520,52 +504,27 @@ fn y_scroll () -> impl Draw<Tui> {
} }
/// Spawn the TUI output thread which writes colored characters to the terminal. /// Spawn the TUI output thread which writes colored characters to the terminal.
pub fn tui_output <W: Write + Send + Sync, T: Draw<Tui> + Send + Sync + 'static> ( pub fn tui_output <W: Write + Send + Sync + 'static, T: Draw<Tui> + Send + Sync + 'static> (
output: W, output: W,
exited: &Arc<AtomicBool>, exited: &Arc<AtomicBool>,
state: &Arc<RwLock<T>>, state: &Arc<RwLock<T>>,
sleep: Duration sleep: Duration
) -> Result<Thread, std::io::Error> { ) -> Usually<Thread> {
let state = state.clone(); let state = state.clone();
tui_setup(&mut output)?; tui_setup()?;
let mut backend = CrosstermBackend::new(output); let mut backend = CrosstermBackend::new(output);
let Size { width, height } = backend.size().expect("get size failed"); let Size { width, height } = backend.size().expect("get size failed");
let mut buffer_a = Buffer::empty(Rect { x: 0, y: 0, width, height }); let mut buffer_a = Buffer::empty(Rect { x: 0, y: 0, width, height });
let mut buffer_b = Buffer::empty(Rect { x: 0, y: 0, width, height }); let mut buffer_b = Buffer::empty(Rect { x: 0, y: 0, width, height });
Thread::new_sleep(exited.clone(), sleep, move |perf| { Ok(Thread::new_sleep(exited.clone(), sleep, move |perf| {
let size = backend.size().expect("get size failed"); let Size { width, height } = backend.size().expect("get size failed");
if let Ok(state) = state.try_read() { if let Ok(state) = state.try_read() {
tui_resize(&mut backend, &mut buffer_a, size); tui_resize(&mut backend, &mut buffer_a, Rect { x: 0, y: 0, width, height });
buffer_a = tui_redraw(&mut backend, &mut buffer_a, &mut buffer_b); tui_redraw(&mut backend, &mut buffer_a, &mut buffer_b);
} }
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed)); let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
buffer_a.set_string(0, 0, &timer, Style::default()); buffer_a.set_string(0, 0, &timer, Style::default());
}) })?)
}
pub fn tui_setup <T: Write + Send + Sync> (output: &mut T) -> Usually<()> {
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{
output.execute(LeaveAlternateScreen).unwrap();
CrosstermBackend::new(output).show_cursor().unwrap();
disable_raw_mode().unwrap();
better_panic_handler(info);
}));
output.execute(EnterAlternateScreen)?;
CrosstermBackend::new(output).hide_cursor()?;
enable_raw_mode().map_err(Into::into)
}
pub fn tui_resize <W: Write> (
backend: &mut CrosstermBackend<W>,
buffer: &mut Buffer,
size: XYWH<u16>
) {
if buffer.1 != size {
backend.clear_region(ClearType::All).unwrap();
buffer.resize(size);
buffer.reset();
}
} }
pub fn tui_redraw <'b, W: Write> ( pub fn tui_redraw <'b, W: Write> (
@ -578,7 +537,31 @@ pub fn tui_redraw <'b, W: Write> (
Backend::flush(backend).expect("failed to flush output new_buffer"); Backend::flush(backend).expect("failed to flush output new_buffer");
std::mem::swap(&mut prev_buffer, &mut next_buffer); std::mem::swap(&mut prev_buffer, &mut next_buffer);
next_buffer.reset(); next_buffer.reset();
next_buffer }
pub fn tui_setup () -> 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)?;
CrosstermBackend::new(stdout()).hide_cursor()?;
enable_raw_mode().map_err(Into::into)
}
pub fn tui_resize <W: Write> (
backend: &mut CrosstermBackend<W>,
buffer: &mut Buffer,
size: Rect
) {
if buffer.area != size {
backend.clear_region(ClearType::All).unwrap();
buffer.resize(size);
buffer.reset();
}
} }
pub fn tui_teardown <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()> { pub fn tui_teardown <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()> {
@ -628,7 +611,7 @@ pub const fn border <T, S: BorderStyle> (on: bool, style: S, draw: impl Draw<Tui
/// let _ = tengri::tui::catcher(Err("draw fail".into())); /// let _ = tengri::tui::catcher(Err("draw fail".into()));
/// ``` /// ```
pub fn catcher <T: Draw<Tui>> (result: Usually<T>) -> impl Draw<Tui> { pub fn catcher <T: Draw<Tui>> (result: Usually<T>) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|match result.as_ref() { thunk(move|to: &mut Tui|match result {
Ok(content) => content.draw(to), Ok(content) => content.draw(to),
Err(e) => { Err(e) => {
let err_fg = Color::Rgb(255,224,244); let err_fg = Color::Rgb(255,224,244);
@ -690,102 +673,3 @@ use self::colors::*; mod colors {
pub const fn tui_title_fg (f: bool) -> Color { if f { tui_ti1() } else { tui_ti2() } } pub const fn tui_title_fg (f: bool) -> Color { if f { tui_ti1() } else { tui_ti2() } }
pub const fn tui_yellow () -> Color { Color::Rgb(255,255,0) } pub const fn tui_yellow () -> Color { Color::Rgb(255,255,0) }
} }
/// Spawn the TUI input thread which reads keys from the terminal.
pub fn tui_input <T: Do<TuiEvent, T> + Send + Sync + 'static> (
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
) -> Result<Thread, std::io::Error> {
let exited = exited.clone();
let state = state.clone();
Thread::new_poll(exited.clone(), poll, move |_| {
let event = read().unwrap();
match event {
// Hardcoded exit.
Event::Key(KeyEvent {
modifiers: KeyModifiers::CONTROL,
code: KeyCode::Char('c'),
kind: KeyEventKind::Press,
state: KeyEventState::NONE
}) => { exited.store(true, Relaxed); },
// Handle all other events by the state:
_ => {
let event = TuiEvent(TuiKey::from_crossterm(event));
if let Err(e) = state.write().unwrap().apply(&event) {
panic!("{e}")
}
}
}
})
}
/// TUI input loop event.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
pub struct TuiEvent(pub Event);
impl_from!(TuiEvent: |e: Event| TuiEvent(e));
impl_from!(TuiEvent: |c: char| TuiEvent(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE))));
impl Ord for TuiEvent {
fn cmp (&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other)
.unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf
}
}
/// TUI key spec.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
pub struct TuiKey(pub Option<KeyCode>, pub KeyModifiers);
impl TuiKey {
const SPLIT: char = '/';
pub fn to_crossterm (&self) -> Option<Event> {
self.0.map(|code|Event::Key(KeyEvent {
code,
modifiers: self.1,
kind: KeyEventKind::Press,
state: KeyEventState::NONE,
}))
}
pub fn named (token: &str) -> Option<KeyCode> {
use KeyCode::*;
Some(match token {
"up" => Up,
"down" => Down,
"left" => Left,
"right" => Right,
"esc" | "escape" => Esc,
"enter" | "return" => Enter,
"delete" | "del" => Delete,
"backspace" => Backspace,
"tab" => Tab,
"space" => Char(' '),
"comma" => Char(','),
"period" => Char('.'),
"plus" => Char('+'),
"minus" | "dash" => Char('-'),
"equal" | "equals" => Char('='),
"underscore" => Char('_'),
"backtick" => Char('`'),
"lt" => Char('<'),
"gt" => Char('>'),
"cbopen" | "openbrace" => Char('{'),
"cbclose" | "closebrace" => Char('}'),
"bropen" | "openbracket" => Char('['),
"brclose" | "closebracket" => Char(']'),
"pgup" | "pageup" => PageUp,
"pgdn" | "pagedown" => PageDown,
"f1" => F(1),
"f2" => F(2),
"f3" => F(3),
"f4" => F(4),
"f5" => F(5),
"f6" => F(6),
"f7" => F(7),
"f8" => F(8),
"f9" => F(9),
"f10" => F(10),
"f11" => F(11),
"f12" => F(12),
_ => return None,
})
}
}

View file

@ -6,16 +6,29 @@ pub(crate) use ::unicode_width::*;
use crate::term::Tui; use crate::term::Tui;
use ratatui::prelude::Position; use ratatui::prelude::Position;
impl Draw<Tui> for str { impl Draw<Tui> for &str {
fn draw (&self, to: &mut Tui) -> Usually<XYWH<u16>> { todo!() } fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
let XYWH(x, y, w, ..) = to.1.centered_xy([width_chars_max(to.w(), self), 1]);
to.text(&self, x, y, w)
}
}
impl Draw<Tui> for String {
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
self.as_str().draw(to)
}
}
impl Draw<Tui> for std::sync::Arc<str> {
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
self.as_ref().draw(to)
}
} }
impl<T: AsRef<str>> Draw<Tui> for TrimString<T> { impl<T: AsRef<str>> Draw<Tui> for TrimString<T> {
fn draw (&self, to: &mut Tui) -> Usually<XYWH<u16>> { self.as_ref().draw(to) } fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> { self.as_ref().draw(to) }
} }
impl<T: AsRef<str>> Draw<Tui> for TrimStringRef<'_, T> { impl<T: AsRef<str>> Draw<Tui> for TrimStringRef<'_, T> {
fn draw (&self, to: &mut Tui) -> Usually<XYWH<u16>> { fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
let XYWH(x, y, w, ..) = to.1; let XYWH(x, y, w, ..) = to.1;
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();

View file

@ -54,7 +54,7 @@ impl PerfModel {
None None
} }
} }
pub fn cycle <F: Fn(&Self)->T, T> (&self, call: &F) -> T { pub fn cycle <F: FnMut(&Self)->T, T> (&self, call: &mut F) -> T {
let t0 = self.get_t0(); let t0 = self.get_t0();
let result = call(self); let result = call(self);
let _t1 = self.get_t1(t0).unwrap(); let _t1 = self.get_t1(t0).unwrap();