diff --git a/output/src/out_impls.rs b/output/src/out_impls.rs index 5b160ca..2799859 100644 --- a/output/src/out_impls.rs +++ b/output/src/out_impls.rs @@ -64,8 +64,8 @@ impl XYWH { pub fn with_h (&self, h: N) -> XYWH { Self(self.x(), self.y(), self.w(), h) } - pub fn lrtb (&self) -> XYWH { - Self(self.x(), self.x2(), self.y(), self.y2()) + pub fn lrtb (&self) -> [N;4] { + [self.x(), self.x2(), self.y(), self.y2()] } pub fn clipped_w (&self, w: N) -> XYWH { Self(self.x(), self.y(), self.w().min(w), self.h()) diff --git a/tui/src/lib.rs b/tui/src/lib.rs index ee26633..16fd3d7 100644 --- a/tui/src/lib.rs +++ b/tui/src/lib.rs @@ -1,7 +1,6 @@ #![feature(type_changing_struct_update, trait_alias)] -use std::{time::Duration, thread::{spawn, JoinHandle}}; - +use std::{time::Duration, thread::{spawn, JoinHandle}, io::Write}; use unicode_width::*; pub use ::{ @@ -86,7 +85,9 @@ mod tui_traits; pub use self::tui_traits::*; mod tui_impls; pub use self::tui_impls::*; /// Run an app in the main loop. -pub fn tui_run (state: &Arc>) -> Usually>> { +pub fn tui_run + Handle + 'static> ( + state: &Arc> +) -> Usually>> { let backend = CrosstermBackend::new(stdout()); let Size { width, height } = backend.size()?; let tui = Arc::new(RwLock::new(Tui { @@ -96,9 +97,9 @@ pub fn tui_run (state: &Arc>) -> Usually>> { perf: Default::default(), backend, })); - let _input_thread = tui_input(tui, state, Duration::from_millis(100)); + let _input_thread = tui_input(tui.clone(), state, Duration::from_millis(100)); tui.write().unwrap().setup()?; - let render_thread = tui_output(tui, state, Duration::from_millis(10))?; + let render_thread = tui_output(tui.clone(), state, Duration::from_millis(10))?; match render_thread.join() { Ok(result) => { tui.write().unwrap().teardown()?; @@ -112,7 +113,9 @@ pub fn tui_run (state: &Arc>) -> Usually>> { Ok(tui) } -pub fn tui_setup (backend: &mut CrosstermBackend) -> Usually<()> { +pub fn tui_setup ( + backend: &mut CrosstermBackend +) -> Usually<()> { let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler(); std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{ stdout().execute(LeaveAlternateScreen).unwrap(); @@ -125,14 +128,14 @@ pub fn tui_setup (backend: &mut CrosstermBackend) -> Usually<()> { enable_raw_mode().map_err(Into::into) } -pub fn tui_teardown (backend: &mut CrosstermBackend) -> Usually<()> { +pub fn tui_teardown (backend: &mut CrosstermBackend) -> Usually<()> { stdout().execute(LeaveAlternateScreen)?; backend.show_cursor()?; disable_raw_mode().map_err(Into::into) } -pub fn tui_resized ( - backend: &mut CrosstermBackend, buffer: &mut Buffer, size: ratatui::prelude::Rect +pub fn tui_resized ( + backend: &mut CrosstermBackend, buffer: &mut Buffer, size: ratatui::prelude::Rect ) { if buffer.area != size { backend.clear_region(ClearType::All).unwrap(); @@ -141,17 +144,21 @@ pub fn tui_resized ( } } -pub fn tui_redrawn ( - backend: &mut CrosstermBackend, buffer: &mut Buffer, new_buffer: &mut Buffer +pub fn tui_redrawn <'b, W: Write> ( + backend: &mut CrosstermBackend, + mut prev_buffer: &'b mut Buffer, + mut next_buffer: &'b mut Buffer ) { - let updates = buffer.diff(&new_buffer); + let updates = prev_buffer.diff(&next_buffer); backend.draw(updates.into_iter()).expect("failed to render"); - backend.flush().expect("failed to flush output new_buffer"); - std::mem::swap(&mut buffer, &mut new_buffer); - new_buffer.reset(); + Backend::flush(backend).expect("failed to flush output new_buffer"); + std::mem::swap(&mut prev_buffer, &mut next_buffer); + next_buffer.reset(); } -pub fn tui_update (buf: &mut Buffer, area: XY, callback: &impl Fn(&mut Cell, u16, u16)) { +pub fn tui_update ( + buf: &mut Buffer, area: XYWH, callback: &impl Fn(&mut Cell, u16, u16) +) { for row in 0..area.h() { let y = area.y() + row; for col in 0..area.w() { @@ -167,7 +174,7 @@ pub fn tui_update (buf: &mut Buffer, area: XY, callback: &impl Fn(&mut Cell /// Spawn the output thread. pub fn tui_output + Send + Sync + 'static> ( - engine: &Arc>, state: &Arc>, timer: Duration + engine: Arc>, state: &Arc>, timer: Duration ) -> Result, std::io::Error> { let exited = engine.read().unwrap().exited.clone(); let engine = engine.clone(); @@ -202,7 +209,7 @@ pub fn tui_output + Send + Sync + 'static> ( /// Spawn the input thread. pub fn tui_input + Send + Sync + 'static> ( - engine: &Arc>, state: &Arc>, timer: Duration + engine: Arc>, state: &Arc>, timer: Duration ) -> JoinHandle<()> { let exited = engine.read().unwrap().exited.clone(); let state = state.clone(); diff --git a/tui/src/tui_impls.rs b/tui/src/tui_impls.rs index e29529a..c6c4afa 100644 --- a/tui/src/tui_impls.rs +++ b/tui/src/tui_impls.rs @@ -4,7 +4,11 @@ use rand::{thread_rng, distributions::uniform::UniformSampler}; impl Tui { /// Create and launch a terminal user interface. - pub fn run (state: &Arc>) -> Usually>> { tui_run(state) } + pub fn run + Draw + 'static> ( + state: &Arc> + ) -> Usually>> { + tui_run(state) + } /// True if done pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) } /// Prepare before run @@ -97,22 +101,22 @@ impl Out for TuiOut { } } impl TuiOut { - #[inline] pub fn with_rect (&mut self, area: XY) -> &mut Self { self.area = area; self } - pub fn update (&mut self, area: XY, callback: &impl Fn(&mut Cell, u16, u16)) { tui_update(&mut self.buffer, area, callback); } - pub fn fill_char (&mut self, area: XY, c: char) { self.update(area, &|cell,_,_|{cell.set_char(c);}) } - pub fn fill_bg (&mut self, area: XY, color: Color) { self.update(area, &|cell,_,_|{cell.set_bg(color);}) } - pub fn fill_fg (&mut self, area: XY, color: Color) { self.update(area, &|cell,_,_|{cell.set_fg(color);}) } - pub fn fill_mod (&mut self, area: XY, on: bool, modifier: Modifier) { + #[inline] pub fn with_rect (&mut self, area: XYWH) -> &mut Self { self.area = area; self } + pub fn update (&mut self, area: XYWH, callback: &impl Fn(&mut Cell, u16, u16)) { tui_update(&mut self.buffer, area, callback); } + pub fn fill_char (&mut self, area: XYWH, c: char) { self.update(area, &|cell,_,_|{cell.set_char(c);}) } + pub fn fill_bg (&mut self, area: XYWH, color: Color) { self.update(area, &|cell,_,_|{cell.set_bg(color);}) } + pub fn fill_fg (&mut self, area: XYWH, color: Color) { self.update(area, &|cell,_,_|{cell.set_fg(color);}) } + pub fn fill_mod (&mut self, area: XYWH, on: bool, modifier: Modifier) { if on { self.update(area, &|cell,_,_|cell.modifier.insert(modifier)) } else { self.update(area, &|cell,_,_|cell.modifier.remove(modifier)) } } - pub fn fill_bold (&mut self, area: XY, on: bool) { self.fill_mod(area, on, Modifier::BOLD) } - pub fn fill_reversed (&mut self, area: XY, on: bool) { self.fill_mod(area, on, Modifier::REVERSED) } - pub fn fill_crossed_out (&mut self, area: XY, on: bool) { self.fill_mod(area, on, Modifier::CROSSED_OUT) } - pub fn fill_ul (&mut self, area: XY, color: Option) { + pub fn fill_bold (&mut self, area: XYWH, on: bool) { self.fill_mod(area, on, Modifier::BOLD) } + pub fn fill_reversed (&mut self, area: XYWH, on: bool) { self.fill_mod(area, on, Modifier::REVERSED) } + pub fn fill_crossed_out (&mut self, area: XYWH, on: bool) { self.fill_mod(area, on, Modifier::CROSSED_OUT) } + pub fn fill_ul (&mut self, area: XYWH, color: Option) { if let Some(color) = color { self.update(area, &|cell,_,_|{ cell.modifier.insert(ratatui::prelude::Modifier::UNDERLINED); @@ -282,11 +286,11 @@ impl Phat { pub const HI: &'static str = "▀"; /// A phat line pub fn lo (fg: Color, bg: Color) -> impl Content { - Fixed::Y(1, Tui::fg_bg(fg, bg, RepeatH(Self::LO))) + Fixed::Y(1, Tui::fg_bg(fg, bg, Repeat::X(Self::LO))) } /// A phat line pub fn hi (fg: Color, bg: Color) -> impl Content { - Fixed::Y(1, Tui::fg_bg(fg, bg, RepeatH(Self::HI))) + Fixed::Y(1, Tui::fg_bg(fg, bg, Repeat::X(Self::HI))) } } impl Scrollbar { @@ -350,14 +354,30 @@ mod layout { impl> Layout for Modify {} impl> Layout for Styled {} impl Layout for Repeat<'_> {} - impl Layout for &str { fn layout (&self, to: XY) -> XY { to.center_xy([width_chars_max(to.w(), self), 1]) } } - impl Layout for String { fn layout (&self, to: XY) -> XY { self.as_str().layout(to) } } - impl Layout for Arc { fn layout (&self, to: XY) -> XY { self.as_ref().layout(to) } } + impl Layout for &str { + fn layout (&self, to: XYWH) -> XYWH { + to.centered_xy([width_chars_max(to.w(), self), 1]) + } + } + impl Layout for String { + fn layout (&self, to: XYWH) -> XYWH { + self.as_str().layout(to) + } + } + impl Layout for Arc { + fn layout (&self, to: XYWH) -> XYWH { + self.as_ref().layout(to) + } + } impl<'a, T: AsRef> Layout for TrimString { - fn layout (&self, to: XY) -> XY { Layout::layout(&self.as_ref(), to) } + fn layout (&self, to: XYWH) -> XYWH { + Layout::layout(&self.as_ref(), to) + } } impl<'a, T: AsRef> Layout for TrimStringRef<'a, T> { - fn layout (&self, to: XY) -> XY { [to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h()] } + fn layout (&self, to: XYWH) -> XYWH { + XYWH(to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h()) + } } } @@ -394,9 +414,9 @@ mod draw { impl Draw for Repeat<'_> { fn draw (&self, to: &mut TuiOut) { + let XYWH(x, y, w, h) = to.area(); match self { Self::X(c) => { - let [x, y, w, _h] = to.area().xywh(); for x in x..x+w { if let Some(cell) = to.buffer.cell_mut(Position::from((x, y))) { cell.set_symbol(&c); @@ -404,21 +424,19 @@ mod draw { } }, Self::Y(c) => { - let [x, y, _w, h] = to.area().xywh(); for y in y..y+h { if let Some(cell) = to.buffer.cell_mut(Position::from((x, y))) { cell.set_symbol(&c); } } }, - Self::XY => { - let [x, y, w, h] = to.area().xywh(); - let a = self.0.len(); + Self::XY(c) => { + 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))) { let u = u % a; - cell.set_symbol(&self.0[u..u+1]); + cell.set_symbol(&c[u..u+1]); } } } @@ -429,7 +447,7 @@ mod draw { impl Draw for Scrollbar { fn draw (&self, to: &mut TuiOut) { - let [x1, y1, w, h] = to.area().xywh(); + let XYWH(x1, y1, w, h) = to.area(); match self { Self::X { .. } => { let x2 = x1 + w; @@ -439,10 +457,10 @@ mod draw { cell.set_fg(Rgb(255, 255, 255)); cell.set_bg(Rgb(0, 0, 0)); cell.set_char(Self::ICON_DEC_H[i as usize]); - } else if i > (w as usize - Self::ICON_INC.len()) { + } else if i > (w as usize - Self::ICON_INC_H.len()) { cell.set_fg(Rgb(255, 255, 255)); cell.set_bg(Rgb(0, 0, 0)); - cell.set_char(Self::ICON_INC[w as usize - i]); + cell.set_char(Self::ICON_INC_H[w as usize - i]); } else if false { cell.set_fg(Rgb(255, 255, 255)); cell.set_bg(Reset); @@ -462,11 +480,11 @@ mod draw { if (i as usize) < (Self::ICON_DEC_V.len()) { cell.set_fg(Rgb(255, 255, 255)); cell.set_bg(Rgb(0, 0, 0)); - cell.set_char(Self::ICON_DEC_v[i as usize]); - } else if (i as usize) > (h as usize - Self::ICON_INC.len()) { + cell.set_char(Self::ICON_DEC_V[i as usize]); + } else if (i as usize) > (h as usize - Self::ICON_INC_V.len()) { cell.set_fg(Rgb(255, 255, 255)); cell.set_bg(Rgb(0, 0, 0)); - cell.set_char(Self::ICON_INC[h as usize - i]); + cell.set_char(Self::ICON_INC_V[h as usize - i]); } else if false { cell.set_fg(Rgb(255, 255, 255)); cell.set_bg(Reset); @@ -485,7 +503,7 @@ mod draw { impl Draw for &str { fn draw (&self, to: &mut TuiOut) { - let [x, y, w, ..] = self.layout(to.area()); + let XYWH(x, y, w, ..) = self.layout(to.area()); to.text(&self, x, y, w) } } diff --git a/tui/src/tui_structs.rs b/tui/src/tui_structs.rs index 57337b5..e0eebbc 100644 --- a/tui/src/tui_structs.rs +++ b/tui/src/tui_structs.rs @@ -22,7 +22,10 @@ pub struct Tui { #[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent(pub Event); -pub struct TuiKey(Option, KeyModifiers); +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiKey( + pub Option, + pub KeyModifiers +); #[derive(Default)] pub struct TuiOut { pub buffer: Buffer, @@ -68,8 +71,8 @@ pub struct TrimStringRef<'a, T: AsRef>(pub u16, pub &'a T); /// Thunks can be natural error boundaries! pub struct ErrorBoundary>( - std::marker::PhantomData, - Perhaps + pub std::marker::PhantomData, + pub Perhaps ); /// Repeat a string, e.g. for background diff --git a/tui/src/tui_traits.rs b/tui/src/tui_traits.rs index 0520edd..77276c7 100644 --- a/tui/src/tui_traits.rs +++ b/tui/src/tui_traits.rs @@ -58,7 +58,7 @@ pub trait BorderStyle: Content + Copy { } #[inline] fn draw_horizontal ( &self, to: &mut TuiOut, style: Option