mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-02-21 10:39:03 +01:00
refactor(output,tui): fix errors, now there's more...
Some checks failed
/ build (push) Has been cancelled
Some checks failed
/ build (push) Has been cancelled
This commit is contained in:
parent
4fa5d74fa2
commit
2dc74657d5
9 changed files with 713 additions and 754 deletions
|
|
@ -9,8 +9,10 @@ bumpalo = [ "dep:bumpalo" ]
|
|||
dsl = []
|
||||
|
||||
[dependencies]
|
||||
dizzle = { path = "../../dizzle" }
|
||||
bumpalo = { optional = true, workspace = true }
|
||||
atomic_float = { workspace = true }
|
||||
bumpalo = { workspace = true, optional = true }
|
||||
dizzle = { path = "../../dizzle" }
|
||||
quanta = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tengri = { path = "../tengri", features = [ "dsl", "tui" ] }
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ pub(crate) use std::ops::{Add, Sub, Mul, Div};
|
|||
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, Ordering::Relaxed}};
|
||||
pub(crate) use std::marker::PhantomData;
|
||||
pub(crate) use dizzle::*;
|
||||
pub(crate) use quanta::Clock;
|
||||
pub(crate) use atomic_float::AtomicF64;
|
||||
|
||||
// Define macros first, so that private macros are available in private modules:
|
||||
|
||||
|
|
|
|||
|
|
@ -41,68 +41,68 @@ impl<O: Out> HasWH<O::Unit> for O {
|
|||
}
|
||||
|
||||
impl<N: Coord> WH<N> {
|
||||
fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] }
|
||||
fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] }
|
||||
fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
|
||||
pub fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] }
|
||||
pub fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] }
|
||||
pub fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
|
||||
if self.w() < w || self.h() < h { return Err(format!("min {w}x{h}").into()) }
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
impl<N: Coord> XYWH<N> {
|
||||
fn zero () -> Self {
|
||||
pub fn zero () -> Self {
|
||||
Self(0.into(), 0.into(), 0.into(), 0.into())
|
||||
}
|
||||
fn x2 (&self) -> N {
|
||||
pub fn x2 (&self) -> N {
|
||||
self.x().plus(self.w())
|
||||
}
|
||||
fn y2 (&self) -> N {
|
||||
pub fn y2 (&self) -> N {
|
||||
self.y().plus(self.h())
|
||||
}
|
||||
fn with_w (&self, w: N) -> XYWH<N> {
|
||||
pub fn with_w (&self, w: N) -> XYWH<N> {
|
||||
Self(self.x(), self.y(), w, self.h())
|
||||
}
|
||||
fn with_h (&self, h: N) -> XYWH<N> {
|
||||
pub fn with_h (&self, h: N) -> XYWH<N> {
|
||||
Self(self.x(), self.y(), self.w(), h)
|
||||
}
|
||||
fn lrtb (&self) -> XYWH<N> {
|
||||
pub fn lrtb (&self) -> XYWH<N> {
|
||||
Self(self.x(), self.x2(), self.y(), self.y2())
|
||||
}
|
||||
fn clipped_w (&self, w: N) -> XYWH<N> {
|
||||
pub fn clipped_w (&self, w: N) -> XYWH<N> {
|
||||
Self(self.x(), self.y(), self.w().min(w), self.h())
|
||||
}
|
||||
fn clipped_h (&self, h: N) -> XYWH<N> {
|
||||
pub fn clipped_h (&self, h: N) -> XYWH<N> {
|
||||
Self(self.x(), self.y(), self.w(), self.h().min(h))
|
||||
}
|
||||
fn clipped (&self, wh: WH<N>) -> XYWH<N> {
|
||||
pub fn clipped (&self, wh: WH<N>) -> XYWH<N> {
|
||||
Self(self.x(), self.y(), wh.w(), wh.h())
|
||||
}
|
||||
/// Iterate over every covered X coordinate.
|
||||
fn iter_x (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
|
||||
pub fn iter_x (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
|
||||
let Self(x, _, w, _) = *self;
|
||||
x..(x+w)
|
||||
}
|
||||
/// Iterate over every covered Y coordinate.
|
||||
fn iter_y (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
|
||||
pub fn iter_y (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
|
||||
let Self(_, y, _, h) = *self;
|
||||
y..(y+h)
|
||||
}
|
||||
fn center (&self) -> XY<N> {
|
||||
pub fn center (&self) -> XY<N> {
|
||||
let Self(x, y, w, h) = self;
|
||||
XY(self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into()))
|
||||
}
|
||||
fn centered (&self) -> XY<N> {
|
||||
pub fn centered (&self) -> XY<N> {
|
||||
let Self(x, y, w, h) = *self;
|
||||
XY(x.minus(w/2.into()), y.minus(h/2.into()))
|
||||
}
|
||||
fn centered_x (&self, n: N) -> XYWH<N> {
|
||||
pub fn centered_x (&self, n: N) -> XYWH<N> {
|
||||
let Self(x, y, w, h) = *self;
|
||||
XYWH((x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into())
|
||||
}
|
||||
fn centered_y (&self, n: N) -> XYWH<N> {
|
||||
pub fn centered_y (&self, n: N) -> XYWH<N> {
|
||||
let Self(x, y, w, h) = *self;
|
||||
XYWH(x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n)
|
||||
}
|
||||
fn centered_xy (&self, [n, m]: [N;2]) -> XYWH<N> {
|
||||
pub fn centered_xy (&self, [n, m]: [N;2]) -> XYWH<N> {
|
||||
let Self(x, y, w, h) = *self;
|
||||
XYWH((x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m)
|
||||
}
|
||||
|
|
@ -847,3 +847,51 @@ impl<C, T, U> Field<C, T, U> {
|
|||
Field::<C, T, V> { value, value_fg: fg, value_bg: bg, value_align: align, ..self }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PerfModel {
|
||||
fn default () -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
clock: quanta::Clock::new(),
|
||||
used: Default::default(),
|
||||
window: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PerfModel {
|
||||
pub fn get_t0 (&self) -> Option<u64> {
|
||||
if self.enabled {
|
||||
Some(self.clock.raw())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn get_t1 (&self, t0: Option<u64>) -> Option<std::time::Duration> {
|
||||
if let Some(t0) = t0 {
|
||||
if self.enabled {
|
||||
Some(self.clock.delta(t0, self.clock.raw()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn update (&self, t0: Option<u64>, microseconds: f64) {
|
||||
if let Some(t0) = t0 {
|
||||
let t1 = self.clock.raw();
|
||||
self.used.store(self.clock.delta_as_nanos(t0, t1) as f64, Relaxed);
|
||||
self.window.store(microseconds, Relaxed,);
|
||||
}
|
||||
}
|
||||
pub fn percentage (&self) -> Option<f64> {
|
||||
let window = self.window.load(Relaxed) * 1000.0;
|
||||
if window > 0.0 {
|
||||
let used = self.used.load(Relaxed);
|
||||
Some(100.0 * used / window)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,3 +256,15 @@ pub struct Field<C, T, U> {
|
|||
pub value_bg: Option<C>,
|
||||
pub value_align: Option<Direction>,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,3 +155,7 @@ pub trait Measured<O: Out> {
|
|||
fn measure_width (&self) -> O::Unit { self.measure().w() }
|
||||
fn measure_height (&self) -> O::Unit { self.measure().h() }
|
||||
}
|
||||
|
||||
pub trait HasPerf {
|
||||
fn perf (&self) -> &PerfModel;
|
||||
}
|
||||
|
|
|
|||
196
tui/src/lib.rs
196
tui/src/lib.rs
|
|
@ -23,8 +23,8 @@ pub(crate) use ::{
|
|||
better_panic::{Settings, Verbosity},
|
||||
palette::{*, convert::*, okhsl::*},
|
||||
ratatui::{
|
||||
prelude::{Color, Style, Buffer},
|
||||
style::Modifier,
|
||||
prelude::{Color, Style, Buffer, Position},
|
||||
style::{Stylize, Modifier, Color::*},
|
||||
backend::{Backend, CrosstermBackend, ClearType},
|
||||
layout::{Size, Rect},
|
||||
buffer::Cell
|
||||
|
|
@ -85,8 +85,155 @@ mod tui_structs; pub use self::tui_structs::*;
|
|||
mod tui_traits; pub use self::tui_traits::*;
|
||||
mod tui_impls; pub use self::tui_impls::*;
|
||||
|
||||
#[cfg(feature = "dsl")]
|
||||
pub fn evaluate_output_expression_tui <'a, S> (
|
||||
/// Run an app in the main loop.
|
||||
pub fn tui_run <T>(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 {
|
||||
exited: Arc::new(AtomicBool::new(false)),
|
||||
buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }),
|
||||
area: [0, 0, width, height],
|
||||
perf: Default::default(),
|
||||
backend,
|
||||
}));
|
||||
let _input_thread = tui_input(tui, state, Duration::from_millis(100));
|
||||
tui.write().unwrap().setup()?;
|
||||
let render_thread = tui_output(tui, state, Duration::from_millis(10))?;
|
||||
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")
|
||||
},
|
||||
}
|
||||
Ok(tui)
|
||||
}
|
||||
|
||||
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();
|
||||
CrosstermBackend::new(stdout()).show_cursor().unwrap();
|
||||
disable_raw_mode().unwrap();
|
||||
better_panic_handler(info);
|
||||
}));
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
backend.hide_cursor()?;
|
||||
enable_raw_mode().map_err(Into::into)
|
||||
}
|
||||
|
||||
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
|
||||
) {
|
||||
if buffer.area != size {
|
||||
backend.clear_region(ClearType::All).unwrap();
|
||||
buffer.resize(size);
|
||||
buffer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tui_redrawn (
|
||||
backend: &mut CrosstermBackend, buffer: &mut Buffer, new_buffer: &mut Buffer
|
||||
) {
|
||||
let updates = buffer.diff(&new_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();
|
||||
}
|
||||
|
||||
pub fn tui_update (buf: &mut Buffer, area: XY<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() {
|
||||
let x = area.x() + col;
|
||||
if x < buf.area.width && y < buf.area.height {
|
||||
if let Some(cell) = buf.cell_mut(ratatui::prelude::Position { x, y }) {
|
||||
callback(cell, col, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
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);
|
||||
})
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "dsl")] pub fn evaluate_output_expression_tui <'a, S> (
|
||||
state: &S, output: &mut TuiOut, expr: impl Expression + 'a
|
||||
) -> Usually<bool> where
|
||||
S: View<TuiOut, ()>
|
||||
|
|
@ -296,6 +443,47 @@ border! {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn okhsl_to_rgb (color: Okhsl<f32>) -> Color {
|
||||
let Srgb { red, green, blue, .. }: Srgb<f32> = Srgb::from_color_unclamped(color);
|
||||
Color::Rgb((red * 255.0) as u8, (green * 255.0) as u8, (blue * 255.0) as u8,)
|
||||
}
|
||||
|
||||
pub fn rgb_to_okhsl (color: Color) -> Okhsl<f32> {
|
||||
if let Color::Rgb(r, g, b) = color {
|
||||
Okhsl::from_color(Srgb::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0))
|
||||
} else {
|
||||
unreachable!("only Color::Rgb is supported")
|
||||
}
|
||||
}
|
||||
|
||||
/// Trim string with [unicode_width].
|
||||
pub fn trim_string (max_width: usize, input: impl AsRef<str>) -> String {
|
||||
let input = input.as_ref();
|
||||
let mut output = Vec::with_capacity(input.len());
|
||||
let mut width: usize = 1;
|
||||
let mut chars = input.chars();
|
||||
while let Some(c) = chars.next() {
|
||||
if width > max_width {
|
||||
break
|
||||
}
|
||||
output.push(c);
|
||||
width += c.width().unwrap_or(0);
|
||||
}
|
||||
return output.into_iter().collect()
|
||||
}
|
||||
|
||||
pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
|
||||
let mut width: u16 = 0;
|
||||
let mut chars = text.as_ref().chars();
|
||||
while let Some(c) = chars.next() {
|
||||
width += c.width().unwrap_or(0) as u16;
|
||||
if width > max {
|
||||
break
|
||||
}
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
#[cfg(test)] mod tui_test {
|
||||
use crate::*;
|
||||
#[test] fn test_tui_engine () -> Usually<()> {
|
||||
|
|
|
|||
1051
tui/src/tui_impls.rs
1051
tui/src/tui_impls.rs
File diff suppressed because it is too large
Load diff
|
|
@ -13,34 +13,29 @@ pub struct Tui {
|
|||
pub perf: PerfModel,
|
||||
}
|
||||
|
||||
#[derive(Default)] pub struct BigBuffer {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub content: Vec<Cell>
|
||||
#[derive(Debug, Clone)] pub struct TuiIn {
|
||||
/// Input event
|
||||
pub event: TuiEvent,
|
||||
/// Exit flag
|
||||
pub exited: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent(pub Event);
|
||||
|
||||
pub struct TuiKey(Option<KeyCode>, KeyModifiers);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TuiIn {
|
||||
/// Exit flag
|
||||
pub exited: Arc<AtomicBool>,
|
||||
/// Input event
|
||||
pub event: TuiEvent,
|
||||
#[derive(Default)] pub struct TuiOut {
|
||||
pub buffer: Buffer,
|
||||
pub area: XYWH<u16>,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
/// TUI buffer sized by `usize` instead of `u16`.
|
||||
#[derive(Default)] pub struct BigBuffer {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub content: Vec<Cell>
|
||||
}
|
||||
|
||||
/// A color in OKHSL and RGB representations.
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor {
|
||||
pub okhsl: Okhsl<f32>,
|
||||
|
|
@ -57,29 +52,10 @@ pub struct PerfModel {
|
|||
pub darkest: ItemColor,
|
||||
}
|
||||
|
||||
/// A cell that takes up 3 rows on its own,
|
||||
/// but stacks, giving (N+1)*2 rows per N cells.
|
||||
pub struct Phat<T> {
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
pub content: T,
|
||||
pub colors: [Color;4],
|
||||
}
|
||||
pub struct Repeat<'a>(pub &'a str);
|
||||
pub struct RepeatV<'a>(pub &'a str);
|
||||
pub struct RepeatH<'a>(pub &'a str);
|
||||
pub struct Modify<T>(pub bool, pub Modifier, pub T);
|
||||
|
||||
pub struct ScrollbarV {
|
||||
pub offset: usize,
|
||||
pub length: usize,
|
||||
pub total: usize,
|
||||
}
|
||||
pub struct Styled<T>(pub Option<Style>, pub T);
|
||||
|
||||
pub struct ScrollbarH {
|
||||
pub offset: usize,
|
||||
pub length: usize,
|
||||
pub total: usize,
|
||||
}
|
||||
/// Displays an owned [str]-like with fixed maximum width.
|
||||
///
|
||||
/// Width is computed using [unicode_width].
|
||||
|
|
@ -90,35 +66,32 @@ pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
|
|||
/// Width is computed using [unicode_width].
|
||||
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
|
||||
|
||||
// Thunks can be natural error boundaries!
|
||||
/// Thunks can be natural error boundaries!
|
||||
pub struct ErrorBoundary<O: Out, T: Draw<O>>(
|
||||
std::marker::PhantomData<O>, Perhaps<T>
|
||||
std::marker::PhantomData<O>,
|
||||
Perhaps<T>
|
||||
);
|
||||
|
||||
pub struct Modify<T>(pub bool, pub Modifier, pub T);
|
||||
|
||||
pub struct Styled<T>(pub Option<Style>, pub T);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TuiOut {
|
||||
pub buffer: Buffer,
|
||||
pub area: XYWH<u16>,
|
||||
/// Repeat a string, e.g. for background
|
||||
pub enum Repeat<'a> {
|
||||
X(&'a str),
|
||||
Y(&'a str),
|
||||
XY(&'a str)
|
||||
}
|
||||
|
||||
impl Out for TuiOut {
|
||||
type Unit = u16;
|
||||
#[inline] fn area (&self) -> XYWH<u16> {
|
||||
self.area
|
||||
}
|
||||
#[inline] fn area_mut (&mut self) -> &mut XYWH<u16> {
|
||||
&mut self.area
|
||||
}
|
||||
#[inline] fn place_at <'t, T: Draw<Self> + ?Sized> (
|
||||
&mut self, area: XYWH<u16>, content: &'t T
|
||||
) {
|
||||
let last = self.area();
|
||||
*self.area_mut() = area;
|
||||
content.draw(self);
|
||||
*self.area_mut() = last;
|
||||
}
|
||||
/// Scroll indicator
|
||||
pub enum Scrollbar {
|
||||
/// Horizontal scrollbar
|
||||
X { offset: usize, length: usize, total: usize, },
|
||||
/// Vertical scrollbar
|
||||
Y { offset: usize, length: usize, total: usize, }
|
||||
}
|
||||
|
||||
/// A cell that takes up 3 rows on its own,
|
||||
/// but stacks, giving (N+1)*2 rows per N cells.
|
||||
pub struct Phat<T> {
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
pub content: T,
|
||||
pub colors: [Color;4],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,6 @@ pub trait TuiHandle = Handle<TuiIn>;
|
|||
|
||||
pub trait TuiWidget = TuiDraw + TuiHandle;
|
||||
|
||||
pub trait TuiRun<T: TuiWidget + 'static> {
|
||||
/// Run an app in the main loop.
|
||||
fn run (&self, state: &Arc<RwLock<T>>) -> Usually<()>;
|
||||
}
|
||||
|
||||
pub trait HasColor { fn color (&self) -> ItemColor; }
|
||||
|
||||
pub trait BorderStyle: Content<TuiOut> + Copy {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue