mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-02-21 10:39:03 +01:00
wip: refactor(tui): 1 error left
how do i flip buffer
This commit is contained in:
parent
3bdef228e8
commit
8ca90b9aa0
5 changed files with 86 additions and 58 deletions
|
|
@ -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 <T>(state: &Arc<RwLock<T>>) -> Usually<Arc<RwLock<Tui>>> {
|
||||
pub fn tui_run <T: Send + Sync + Draw<TuiOut> + Handle<TuiIn> + 'static> (
|
||||
state: &Arc<RwLock<T>>
|
||||
) -> Usually<Arc<RwLock<Tui>>> {
|
||||
let backend = CrosstermBackend::new(stdout());
|
||||
let Size { width, height } = backend.size()?;
|
||||
let tui = Arc::new(RwLock::new(Tui {
|
||||
|
|
@ -96,9 +97,9 @@ pub fn tui_run <T>(state: &Arc<RwLock<T>>) -> Usually<Arc<RwLock<Tui>>> {
|
|||
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 <T>(state: &Arc<RwLock<T>>) -> Usually<Arc<RwLock<Tui>>> {
|
|||
Ok(tui)
|
||||
}
|
||||
|
||||
pub fn tui_setup (backend: &mut CrosstermBackend) -> 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();
|
||||
|
|
@ -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 <W: Write> (backend: &mut CrosstermBackend<W>) -> 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 <W: Write> (
|
||||
backend: &mut CrosstermBackend<W>, 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<W>,
|
||||
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<u16>, callback: &impl Fn(&mut Cell, u16, u16)) {
|
||||
pub fn tui_update (
|
||||
buf: &mut Buffer, area: XYWH<u16>, callback: &impl Fn(&mut Cell, u16, u16)
|
||||
) {
|
||||
for row in 0..area.h() {
|
||||
let y = area.y() + row;
|
||||
for col in 0..area.w() {
|
||||
|
|
@ -167,7 +174,7 @@ pub fn tui_update (buf: &mut Buffer, area: XY<u16>, callback: &impl Fn(&mut Cell
|
|||
|
||||
/// Spawn the output thread.
|
||||
pub fn tui_output <T: Draw<TuiOut> + Send + Sync + 'static> (
|
||||
engine: &Arc<RwLock<Tui>>, state: &Arc<RwLock<T>>, timer: Duration
|
||||
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();
|
||||
|
|
@ -202,7 +209,7 @@ pub fn tui_output <T: Draw<TuiOut> + Send + Sync + 'static> (
|
|||
|
||||
/// Spawn the input thread.
|
||||
pub fn tui_input <T: Handle<TuiIn> + Send + Sync + 'static> (
|
||||
engine: &Arc<RwLock<Tui>>, state: &Arc<RwLock<T>>, timer: Duration
|
||||
engine: Arc<RwLock<Tui>>, state: &Arc<RwLock<T>>, timer: Duration
|
||||
) -> JoinHandle<()> {
|
||||
let exited = engine.read().unwrap().exited.clone();
|
||||
let state = state.clone();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,11 @@ use rand::{thread_rng, distributions::uniform::UniformSampler};
|
|||
|
||||
impl Tui {
|
||||
/// Create and launch a terminal user interface.
|
||||
pub fn run <T> (state: &Arc<RwLock<T>>) -> Usually<Arc<RwLock<Self>>> { tui_run(state) }
|
||||
pub fn run <T: Send + Sync + Handle<TuiIn> + Draw<TuiOut> + 'static> (
|
||||
state: &Arc<RwLock<T>>
|
||||
) -> Usually<Arc<RwLock<Self>>> {
|
||||
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<u16>) -> &mut Self { self.area = area; self }
|
||||
pub fn update (&mut self, area: XY<u16>, callback: &impl Fn(&mut Cell, u16, u16)) { tui_update(&mut self.buffer, area, callback); }
|
||||
pub fn fill_char (&mut self, area: XY<u16>, c: char) { self.update(area, &|cell,_,_|{cell.set_char(c);}) }
|
||||
pub fn fill_bg (&mut self, area: XY<u16>, color: Color) { self.update(area, &|cell,_,_|{cell.set_bg(color);}) }
|
||||
pub fn fill_fg (&mut self, area: XY<u16>, color: Color) { self.update(area, &|cell,_,_|{cell.set_fg(color);}) }
|
||||
pub fn fill_mod (&mut self, area: XY<u16>, on: bool, modifier: Modifier) {
|
||||
#[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 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);}) }
|
||||
pub fn fill_mod (&mut self, area: XYWH<u16>, 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<u16>, on: bool) { self.fill_mod(area, on, Modifier::BOLD) }
|
||||
pub fn fill_reversed (&mut self, area: XY<u16>, on: bool) { self.fill_mod(area, on, Modifier::REVERSED) }
|
||||
pub fn fill_crossed_out (&mut self, area: XY<u16>, on: bool) { self.fill_mod(area, on, Modifier::CROSSED_OUT) }
|
||||
pub fn fill_ul (&mut self, area: XY<u16>, color: Option<Color>) {
|
||||
pub fn fill_bold (&mut self, area: XYWH<u16>, on: bool) { self.fill_mod(area, on, Modifier::BOLD) }
|
||||
pub fn fill_reversed (&mut self, area: XYWH<u16>, on: bool) { self.fill_mod(area, on, Modifier::REVERSED) }
|
||||
pub fn fill_crossed_out (&mut self, area: XYWH<u16>, on: bool) { self.fill_mod(area, on, Modifier::CROSSED_OUT) }
|
||||
pub fn fill_ul (&mut self, area: XYWH<u16>, color: Option<Color>) {
|
||||
if let Some(color) = color {
|
||||
self.update(area, &|cell,_,_|{
|
||||
cell.modifier.insert(ratatui::prelude::Modifier::UNDERLINED);
|
||||
|
|
@ -282,11 +286,11 @@ impl<T> Phat<T> {
|
|||
pub const HI: &'static str = "▀";
|
||||
/// A phat line
|
||||
pub fn lo (fg: Color, bg: Color) -> impl Content<TuiOut> {
|
||||
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<TuiOut> {
|
||||
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<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 { fn layout (&self, to: XY<u16>) -> XY<u16> { to.center_xy([width_chars_max(to.w(), self), 1]) } }
|
||||
impl Layout<TuiOut> for String { fn layout (&self, to: XY<u16>) -> XY<u16> { self.as_str().layout(to) } }
|
||||
impl Layout<TuiOut> for Arc<str> { fn layout (&self, to: XY<u16>) -> XY<u16> { self.as_ref().layout(to) } }
|
||||
impl Layout<TuiOut> 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 {
|
||||
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
|
||||
self.as_str().layout(to)
|
||||
}
|
||||
}
|
||||
impl Layout<TuiOut> 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> {
|
||||
fn layout (&self, to: XY<u16>) -> XY<u16> { Layout::layout(&self.as_ref(), to) }
|
||||
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> {
|
||||
fn layout (&self, to: XY<u16>) -> XY<u16> { [to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h()] }
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -394,9 +414,9 @@ mod draw {
|
|||
|
||||
impl Draw<TuiOut> 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<TuiOut> 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<TuiOut> 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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,10 @@ pub struct Tui {
|
|||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent(pub Event);
|
||||
|
||||
pub struct TuiKey(Option<KeyCode>, KeyModifiers);
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiKey(
|
||||
pub Option<KeyCode>,
|
||||
pub KeyModifiers
|
||||
);
|
||||
|
||||
#[derive(Default)] pub struct TuiOut {
|
||||
pub buffer: Buffer,
|
||||
|
|
@ -68,8 +71,8 @@ pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
|
|||
|
||||
/// Thunks can be natural error boundaries!
|
||||
pub struct ErrorBoundary<O: Out, T: Draw<O>>(
|
||||
std::marker::PhantomData<O>,
|
||||
Perhaps<T>
|
||||
pub std::marker::PhantomData<O>,
|
||||
pub Perhaps<T>
|
||||
);
|
||||
|
||||
/// Repeat a string, e.g. for background
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ pub trait BorderStyle: Content<TuiOut> + Copy {
|
|||
}
|
||||
#[inline] fn draw_horizontal (
|
||||
&self, to: &mut TuiOut, style: Option<Style>
|
||||
) -> Usually<[u16;4]> {
|
||||
) -> Usually<XYWH<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_horizontal());
|
||||
let [x, x2, y, y2] = area.lrtb();
|
||||
|
|
@ -70,7 +70,7 @@ pub trait BorderStyle: Content<TuiOut> + Copy {
|
|||
}
|
||||
#[inline] fn draw_vertical (
|
||||
&self, to: &mut TuiOut, style: Option<Style>
|
||||
) -> Usually<[u16;4]> {
|
||||
) -> Usually<XYWH<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_vertical());
|
||||
let [x, x2, y, y2] = area.lrtb();
|
||||
|
|
@ -88,10 +88,10 @@ pub trait BorderStyle: Content<TuiOut> + Copy {
|
|||
}
|
||||
#[inline] fn draw_corners (
|
||||
&self, to: &mut TuiOut, style: Option<Style>
|
||||
) -> Usually<[u16;4]> {
|
||||
) -> Usually<XYWH<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_corners());
|
||||
let [x, y, width, height] = area.xywh();
|
||||
let XYWH(x, y, width, height) = area;
|
||||
if width > 1 && height > 1 {
|
||||
to.blit(&Self::NW, x, y, style);
|
||||
to.blit(&Self::NE, x + width - 1, y, style);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue