mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-04-03 21:40:44 +02:00
This commit is contained in:
parent
4e8d58d793
commit
b0fbe3c173
18 changed files with 1724 additions and 1808 deletions
493
src/tui.rs
493
src/tui.rs
|
|
@ -1,99 +1,114 @@
|
|||
use crate::*;
|
||||
use crate::{*, lang::*, play::*, draw::*, color::*};
|
||||
use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
|
||||
use rand::distributions::uniform::UniformSampler;
|
||||
use ::{
|
||||
std::{
|
||||
io::{stdout, Write},
|
||||
time::Duration
|
||||
},
|
||||
better_panic::{Settings, Verbosity},
|
||||
ratatui::{
|
||||
prelude::{Color, Style, Buffer, Position, Backend},
|
||||
style::{Modifier, Color::*},
|
||||
backend::{CrosstermBackend, ClearType},
|
||||
layout::{Size, Rect},
|
||||
buffer::Cell
|
||||
},
|
||||
crossterm::{
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
|
||||
event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "tui")] pub use self::tui_fns::*;
|
||||
|
||||
#[cfg(feature = "tui")] mod tui_fns {
|
||||
use crate::*;
|
||||
/// One trait to bind 'em.
|
||||
pub trait Tui: Draw<Buffer> + Do<TuiEvent, Perhaps<TuiEvent>> {
|
||||
fn handle (&mut self, e: TuiEvent) -> Perhaps<TuiEvent> { Ok(None) }
|
||||
fn render (&self, to: &mut Buffer) {}
|
||||
}
|
||||
|
||||
/// Interpret TUI-specific layout operation.
|
||||
///
|
||||
/// ```
|
||||
/// 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<Tui, ()> for State {}
|
||||
/// # fn main () -> tengri::Usually<()> {
|
||||
/// let state = State;
|
||||
/// let mut out = TuiOut::default();
|
||||
/// tengri::eval_view_tui(&state, &mut out, "")?;
|
||||
/// tengri::eval_view_tui(&state, &mut out, "text Hello world!")?;
|
||||
/// tengri::eval_view_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?;
|
||||
/// tengri::eval_view_tui(&state, &mut out, "bg (g 2) (text Hello world!)")?;
|
||||
/// tengri::eval_view_tui(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn eval_view_tui <'a, S> (
|
||||
state: &S, output: &mut Buffer, expr: impl Expression + 'a
|
||||
) -> Usually<bool> where
|
||||
S: Understand<TuiOut, ()>
|
||||
+ for<'b>Namespace<'b, bool>
|
||||
+ for<'b>Namespace<'b, u16>
|
||||
+ for<'b>Namespace<'b, Color>
|
||||
{
|
||||
// See `tengri::eval_view`
|
||||
let head = expr.head()?;
|
||||
let mut frags = head.src()?.unwrap_or_default().split("/");
|
||||
let args = expr.tail();
|
||||
let arg0 = args.head();
|
||||
let tail0 = args.tail();
|
||||
let arg1 = tail0.head();
|
||||
let tail1 = tail0.tail();
|
||||
let arg2 = tail1.head();
|
||||
match frags.next() {
|
||||
|
||||
Some("text") => {
|
||||
if let Some(src) = args?.src()? { output.place(&src) }
|
||||
},
|
||||
|
||||
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 Buffer|state.understand(output, &arg1).unwrap());
|
||||
output.place(&TuiOut::fg(color, thunk))
|
||||
},
|
||||
|
||||
Some("bg") => {
|
||||
//panic!("expr: {expr:?}\nhead: {head:?}\nfrags: {frags:?}\nargs: {args:?}\narg0: {arg0:?}\ntail0: {tail0:?}\narg1: {arg1:?}\ntail1: {tail1:?}\narg2: {arg2:?}");
|
||||
//panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}");;
|
||||
//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 Buffer|state.understand(output, &arg1).unwrap());
|
||||
output.place(&TuiOut::bg(color, thunk))
|
||||
},
|
||||
|
||||
_ => return Ok(false)
|
||||
|
||||
};
|
||||
Ok(true)
|
||||
/// Rendering is delegated to [Tui] impl.
|
||||
impl<T: Tui> Draw<TuiOut> for T {
|
||||
fn draw (&self, to: &mut Buffer) {
|
||||
Tui::render(self, to)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tui")] pub fn tui_setup <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()> {
|
||||
/// Event handling is delegated to [Tui] impl.
|
||||
impl<T: Tui> Do<TuiEvent, Perhaps<TuiEvent>> for T {
|
||||
fn apply (&mut self, event: TuiEvent) -> Perhaps<TuiEvent> {
|
||||
Tui::handle(self, event)
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn the TUI output thread which writes colored characters to the terminal.
|
||||
pub fn tui_output <W: Write, T: Draw<Buffer> + Send + Sync + 'static> (
|
||||
output: W,
|
||||
exited: &Arc<AtomicBool>,
|
||||
state: &Arc<RwLock<T>>,
|
||||
sleep: Duration
|
||||
) -> Result<Thread, std::io::Error> {
|
||||
let state = state.clone();
|
||||
tui_setup(&mut output)?;
|
||||
let mut backend = CrosstermBackend::new(output);
|
||||
let WH(width, height) = tui_wh(&mut backend);
|
||||
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 });
|
||||
Thread::new_sleep(exited.clone(), sleep, move |perf| {
|
||||
let size = tui_wh(&mut backend);
|
||||
if let Ok(state) = state.try_read() {
|
||||
tui_resize(&mut backend, &mut buffer_a, size);
|
||||
buffer_a = tui_redraw(&mut backend, &mut buffer_a, &mut buffer_b);
|
||||
}
|
||||
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
|
||||
buffer_a.set_string(0, 0, &timer, Style::default());
|
||||
})
|
||||
}
|
||||
|
||||
pub fn tui_setup (output: &mut impl Write) -> 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();
|
||||
output.execute(LeaveAlternateScreen).unwrap();
|
||||
CrosstermBackend::new(output).show_cursor().unwrap();
|
||||
disable_raw_mode().unwrap();
|
||||
better_panic_handler(info);
|
||||
}));
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
backend.hide_cursor()?;
|
||||
output.execute(EnterAlternateScreen)?;
|
||||
CrosstermBackend::new(output).hide_cursor()?;
|
||||
enable_raw_mode().map_err(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(feature = "tui")] pub fn tui_teardown <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()> {
|
||||
pub fn tui_resize <W: Write> (
|
||||
backend: &mut CrosstermBackend<W>,
|
||||
buffer: &mut Buffer,
|
||||
size: WH<u16>
|
||||
) {
|
||||
if buffer.area != size {
|
||||
backend.clear_region(ClearType::All).unwrap();
|
||||
buffer.resize(size);
|
||||
buffer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tui_redraw <'b, W: Write> (
|
||||
backend: &mut CrosstermBackend<W>,
|
||||
mut prev_buffer: &'b mut Buffer,
|
||||
mut next_buffer: &'b mut Buffer
|
||||
) {
|
||||
let updates = prev_buffer.diff(&next_buffer);
|
||||
backend.draw(updates.into_iter()).expect("failed to render");
|
||||
Backend::flush(backend).expect("failed to flush output new_buffer");
|
||||
std::mem::swap(&mut prev_buffer, &mut next_buffer);
|
||||
next_buffer.reset();
|
||||
next_buffer
|
||||
}
|
||||
|
||||
pub fn tui_teardown <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()> {
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
backend.show_cursor()?;
|
||||
disable_raw_mode().map_err(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(feature = "tui")] pub fn tui_update (
|
||||
pub fn tui_update (
|
||||
buf: &mut Buffer, area: XYWH<u16>, callback: &impl Fn(&mut Cell, u16, u16)
|
||||
) {
|
||||
for row in 0..area.h() {
|
||||
|
|
@ -109,15 +124,71 @@ pub fn eval_view_tui <'a, S> (
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tui")] pub(crate) fn tui_wh <W: Write> (
|
||||
backend: &mut CrosstermBackend<W>
|
||||
) -> WH<u16> {
|
||||
pub(crate) fn tui_wh <W: Write> (backend: &mut CrosstermBackend<W>) -> WH<u16> {
|
||||
let Size { width, height } = backend.size().expect("get size failed");
|
||||
WH(width, height)
|
||||
}
|
||||
|
||||
/// Draw contents with foreground color applied.
|
||||
pub fn fg (enabled: bool, color: Color, item: impl Tui) -> impl Tui {
|
||||
|to: &mut Buffer|item.draw(to.with_fg(enabled, color))
|
||||
}
|
||||
|
||||
/// Draw contents with background color applied.
|
||||
pub fn bg (enabled: bool, color: Color, item: impl Tui) -> impl Tui {
|
||||
|to: &mut Buffer|item.draw(to.with_bg(enabled, color))
|
||||
}
|
||||
|
||||
/// Draw contents with modifier applied.
|
||||
pub fn modify (enabled: bool, modifier: Modifier, item: impl Tui) -> impl Tui {
|
||||
|to: &mut Buffer|item.draw(to.with_modifier(enabled, modifier, modifier))
|
||||
}
|
||||
|
||||
/// Draw contents with style applied.
|
||||
pub fn styled (enabled: bool, style: Style, item: impl Tui) -> impl Tui {
|
||||
|to: &mut Buffer|item.draw(to.with_style(enabled, style, modifier))
|
||||
}
|
||||
|
||||
/// Draw border around shrinked item.
|
||||
///
|
||||
/// ```
|
||||
/// /// TODO
|
||||
/// ```
|
||||
pub fn border <T, S: BorderStyle> (on: bool, style: S, draw: impl Tui) -> impl Tui {
|
||||
fill_wh(bsp_a(when(on, |output|{/*TODO*/}), pad(Some(1), Some(1), draw)))
|
||||
}
|
||||
|
||||
/// Draw TUI content or its error message.
|
||||
///
|
||||
/// ```
|
||||
/// let _ = tengri::tui::catcher(Ok(Some("hello")));
|
||||
/// let _ = tengri::tui::catcher(Ok(None));
|
||||
/// let _ = tengri::tui::catcher(Err("draw fail".into()));
|
||||
/// ```
|
||||
pub fn catcher <T, E> (error: Perhaps<E>, draw: impl Tui) -> impl Tui {
|
||||
move|to: &mut Buffer|match error.as_ref() {
|
||||
Ok(Some(content)) => draw(to),
|
||||
Ok(None) => to.blit(&"<empty>", 0, 0, Some(Style::default().yellow())),
|
||||
Err(e) => {
|
||||
let err_fg = rgb(255,224,244);
|
||||
let err_bg = rgb(96, 24, 24);
|
||||
let title = bsp_e(bold(true, "upsi daisy. "), "rendering failed.");
|
||||
let error = bsp_e("\"why?\" ", bold(true, format!("{e}")));
|
||||
&fg(err_fg, bg(err_bg, bsp_s(title, error)))(to)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TUI buffer sized by `usize` instead of `u16`.
|
||||
#[derive(Default)]
|
||||
pub struct BigBuffer {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub content: Vec<Cell>
|
||||
}
|
||||
|
||||
/// Spawn the TUI input thread which reads keys from the terminal.
|
||||
#[cfg(feature = "tui")] pub fn tui_input <T: Act<TuiEvent, T> + Send + Sync + 'static> (
|
||||
pub fn tui_input <T: Act<TuiEvent, T> + Send + Sync + 'static> (
|
||||
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
|
||||
) -> Result<Thread, std::io::Error> {
|
||||
let exited = exited.clone();
|
||||
|
|
@ -145,61 +216,6 @@ pub fn eval_view_tui <'a, S> (
|
|||
})
|
||||
}
|
||||
|
||||
/// Spawn the TUI output thread which writes colored characters to the terminal.
|
||||
#[cfg(feature = "tui")] pub fn tui_output <T: Draw<Buffer> + Send + Sync + 'static> (
|
||||
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, sleep: Duration
|
||||
) -> Result<Thread, std::io::Error> {
|
||||
let state = state.clone();
|
||||
let mut backend = CrosstermBackend::new(stdout());
|
||||
let WH(width, height) = tui_wh(&mut backend);
|
||||
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 });
|
||||
Thread::new_sleep(exited.clone(), sleep, move |perf| {
|
||||
let size = tui_wh(&mut backend);
|
||||
if let Ok(state) = state.try_read() {
|
||||
tui_resize(&mut backend, &mut buffer_a, size);
|
||||
buffer_a = tui_redraw(&mut backend, &mut buffer_a, &mut buffer_b);
|
||||
}
|
||||
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
|
||||
buffer_a.set_string(0, 0, &timer, Style::default());
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "tui")] pub fn tui_resize <W: Write> (
|
||||
backend: &mut CrosstermBackend<W>,
|
||||
buffer: &mut Buffer,
|
||||
size: WH<u16>
|
||||
) {
|
||||
if buffer.area != size {
|
||||
backend.clear_region(ClearType::All).unwrap();
|
||||
buffer.resize(size);
|
||||
buffer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tui")] pub fn tui_redraw <'b, W: Write> (
|
||||
backend: &mut CrosstermBackend<W>,
|
||||
mut prev_buffer: &'b mut Buffer,
|
||||
mut next_buffer: &'b mut Buffer
|
||||
) {
|
||||
let updates = prev_buffer.diff(&next_buffer);
|
||||
backend.draw(updates.into_iter()).expect("failed to render");
|
||||
Backend::flush(backend).expect("failed to flush output new_buffer");
|
||||
std::mem::swap(&mut prev_buffer, &mut next_buffer);
|
||||
next_buffer.reset();
|
||||
next_buffer
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! tui_main {
|
||||
($expr:expr) => {
|
||||
fn main () -> Usually<()> {
|
||||
let engine = ::tengri::Tui::new(Box::new(stdout()))?;
|
||||
let state = ::std::sync::Arc::new(std::sync::RwLock::new($expr));
|
||||
engine.run(true, &state)?;
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl Coord for u16 { fn plus (self, other: Self) -> Self { self.saturating_add(other) } }
|
||||
impl_from!(BigBuffer: |size:(usize, usize)| Self::new(size.0, size.1));
|
||||
|
|
@ -350,16 +366,6 @@ impl BigBuffer {
|
|||
y * self.width + x
|
||||
}
|
||||
}
|
||||
|
||||
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 = "▀";
|
||||
|
|
@ -522,13 +528,6 @@ impl Draw<Buffer> for Arc<str> {
|
|||
fn draw (&self, to: &mut Buffer) { self.as_ref().draw(to) }
|
||||
}
|
||||
|
||||
impl<T: Draw<Buffer>> Draw<Buffer> for Foreground<Color, T> {
|
||||
fn draw (&self, to: &mut Buffer) {
|
||||
let area = self.layout(to.area());
|
||||
to.fill_fg(area, self.0);
|
||||
to.show(area, &self.1);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Draw<Buffer>> Draw<Buffer> for Background<Color, T> {
|
||||
fn draw (&self, to: &mut Buffer) {
|
||||
|
|
@ -674,9 +673,6 @@ pub fn named_key (token: &str) -> Option<KeyCode> {
|
|||
_ => return None,
|
||||
})
|
||||
}
|
||||
impl<O: Screen, Color, Item: Layout<O>> Layout<O> for Foreground<Color, Item> {
|
||||
fn layout (&self, to: XYWH<O::Unit>) -> XYWH<O::Unit> { self.1.layout(to) }
|
||||
}
|
||||
impl<O: Screen, Color, Item: Layout<O>> Layout<O> for Background<Color, Item> {
|
||||
fn layout (&self, to: XYWH<O::Unit>) -> XYWH<O::Unit> { self.1.layout(to) }
|
||||
}
|
||||
|
|
@ -969,3 +965,190 @@ impl ItemTheme {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TuiOut: Screen {
|
||||
fn tui_out (&mut self) -> &mut Buffer;
|
||||
fn update (&mut self, area: impl Area<u16>, callback: &impl Fn(&mut Cell, u16, u16)) {
|
||||
tui_update(self.buffer(), area, callback);
|
||||
}
|
||||
fn fill_char (&mut self, area: impl Area<u16>, c: char) {
|
||||
self.update(area, &|cell,_,_|{cell.set_char(c);})
|
||||
}
|
||||
fn fill_bg (&mut self, area: impl Area<u16>, color: Color) {
|
||||
self.update(area, &|cell,_,_|{cell.set_bg(color);})
|
||||
}
|
||||
fn fill_fg (&mut self, area: impl Area<u16>, color: Color) {
|
||||
self.update(area, &|cell,_,_|{cell.set_fg(color);})
|
||||
}
|
||||
fn fill_mod (&mut self, area: impl Area<u16>, on: bool, modifier: Modifier) {
|
||||
if on {
|
||||
self.update(area, &|cell,_,_|cell.modifier.insert(modifier))
|
||||
} else {
|
||||
self.update(area, &|cell,_,_|cell.modifier.remove(modifier))
|
||||
}
|
||||
}
|
||||
fn fill_bold (&mut self, area: impl Area<u16>, on: bool) {
|
||||
self.fill_mod(area, on, Modifier::BOLD)
|
||||
}
|
||||
fn fill_reversed (&mut self, area: impl Area<u16>, on: bool) {
|
||||
self.fill_mod(area, on, Modifier::REVERSED)
|
||||
}
|
||||
fn fill_crossed_out (&mut self, area: impl Area<u16>, on: bool) {
|
||||
self.fill_mod(area, on, Modifier::CROSSED_OUT)
|
||||
}
|
||||
fn fill_ul (&mut self, area: impl Area<u16>, color: Option<Color>) {
|
||||
if let Some(color) = color {
|
||||
self.update(area, &|cell,_,_|{
|
||||
cell.modifier.insert(ratatui::prelude::Modifier::UNDERLINED);
|
||||
cell.underline_color = color;
|
||||
})
|
||||
} else {
|
||||
self.update(area, &|cell,_,_|{
|
||||
cell.modifier.remove(ratatui::prelude::Modifier::UNDERLINED);
|
||||
})
|
||||
}
|
||||
}
|
||||
fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) {
|
||||
for cell in self.buffer().content.iter_mut() {
|
||||
cell.fg = fg;
|
||||
cell.bg = bg;
|
||||
cell.modifier = modifier;
|
||||
}
|
||||
}
|
||||
fn blit (&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>) {
|
||||
let text = text.as_ref();
|
||||
let style = style.unwrap_or(Style::default());
|
||||
let buf = self.buffer();
|
||||
if x < buf.area.width && y < buf.area.height {
|
||||
buf.set_string(x, y, text, style);
|
||||
}
|
||||
}
|
||||
/// Write a line of text
|
||||
///
|
||||
/// TODO: do a paragraph (handle newlines)
|
||||
fn text (&mut self, text: &impl AsRef<str>, x0: u16, y: u16, max_width: u16) {
|
||||
let text = text.as_ref();
|
||||
let buf = self.buffer();
|
||||
let mut string_width: u16 = 0;
|
||||
for character in text.chars() {
|
||||
let x = x0 + string_width;
|
||||
let character_width = character.width().unwrap_or(0) as u16;
|
||||
string_width += character_width;
|
||||
if string_width > max_width {
|
||||
break
|
||||
}
|
||||
if let Some(cell) = buf.write().unwrap().cell_mut(ratatui::prelude::Position { x, y }) {
|
||||
cell.set_char(character);
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BorderStyle: Draw<Buffer> + Copy {
|
||||
fn enabled (&self) -> bool;
|
||||
fn border_n (&self) -> &str { Self::N }
|
||||
fn border_s (&self) -> &str { Self::S }
|
||||
fn border_e (&self) -> &str { Self::E }
|
||||
fn border_w (&self) -> &str { Self::W }
|
||||
fn border_nw (&self) -> &str { Self::NW }
|
||||
fn border_ne (&self) -> &str { Self::NE }
|
||||
fn border_sw (&self) -> &str { Self::SW }
|
||||
fn border_se (&self) -> &str { Self::SE }
|
||||
|
||||
fn enclose (self, w: impl Draw<Buffer>) -> impl Draw<Buffer> {
|
||||
Bsp::b(Fill::XY(Border(self.enabled(), self)), w)
|
||||
}
|
||||
fn enclose2 (self, w: impl Draw<Buffer>) -> impl Draw<Buffer> {
|
||||
Bsp::b(Pad::XY(1, 1, Fill::XY(Border(self.enabled(), self))), w)
|
||||
}
|
||||
fn enclose_bg (self, w: impl Draw<Buffer>) -> impl Draw<Buffer> {
|
||||
TuiOut::bg(self.style().unwrap().bg.unwrap_or(Color::Reset),
|
||||
Bsp::b(Fill::XY(Border(self.enabled(), self)), w))
|
||||
}
|
||||
#[inline] fn draw <'a> (&self, to: &mut impl TuiOut) -> Usually<()> {
|
||||
if self.enabled() {
|
||||
self.draw_h(to, None)?;
|
||||
self.draw_v(to, None)?;
|
||||
self.draw_c(to, None)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[inline] fn draw_h (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl Area<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_horizontal());
|
||||
let [x, x2, y, y2] = area.lrtb();
|
||||
for x in x..x2.saturating_sub(1) {
|
||||
to.blit(&Self::N, x, y, style);
|
||||
to.blit(&Self::S, x, y2.saturating_sub(1), style)
|
||||
}
|
||||
Ok(area)
|
||||
}
|
||||
#[inline] fn draw_v (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl Area<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_vertical());
|
||||
let [x, x2, y, y2] = area.lrtb();
|
||||
let h = y2 - y;
|
||||
if h > 1 {
|
||||
for y in y..y2.saturating_sub(1) {
|
||||
to.blit(&Self::W, x, y, style);
|
||||
to.blit(&Self::E, x2.saturating_sub(1), y, style);
|
||||
}
|
||||
} else if h > 0 {
|
||||
to.blit(&Self::W0, x, y, style);
|
||||
to.blit(&Self::E0, x2.saturating_sub(1), y, style);
|
||||
}
|
||||
Ok(area)
|
||||
}
|
||||
#[inline] fn draw_c (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl Area<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_corners());
|
||||
let XYWH(x, y, w, h) = area;
|
||||
if w > 1 && h > 1 {
|
||||
to.blit(&Self::NW, x, y, style);
|
||||
to.blit(&Self::NE, x + w - 1, y, style);
|
||||
to.blit(&Self::SW, x, y + h- 1, style);
|
||||
to.blit(&Self::SE, x + w - 1, y + h - 1, style);
|
||||
}
|
||||
Ok(area)
|
||||
}
|
||||
#[inline] fn style (&self) -> Option<Style> { None }
|
||||
#[inline] fn style_horizontal (&self) -> Option<Style> { self.style() }
|
||||
#[inline] fn style_vertical (&self) -> Option<Style> { self.style() }
|
||||
#[inline] fn style_corners (&self) -> Option<Style> { self.style() }
|
||||
|
||||
const NW: &'static str = "";
|
||||
const N: &'static str = "";
|
||||
const NE: &'static str = "";
|
||||
const E: &'static str = "";
|
||||
const SE: &'static str = "";
|
||||
const S: &'static str = "";
|
||||
const SW: &'static str = "";
|
||||
const W: &'static str = "";
|
||||
|
||||
const N0: &'static str = "";
|
||||
const S0: &'static str = "";
|
||||
const W0: &'static str = "";
|
||||
const E0: &'static str = "";
|
||||
}
|
||||
|
||||
/// Stackably padded.
|
||||
///
|
||||
/// ```
|
||||
/// /// TODO
|
||||
/// ```
|
||||
pub fn phat <T, N: Coord> (w: N, h: N, [fg, bg, hi, lo]: [Color;4], draw: impl Tui) -> impl Tui {
|
||||
let top = exact(Some(1), None, Self::lo(bg, hi));
|
||||
let low = exact(Some(1), None, Self::hi(bg, lo));
|
||||
let draw = Tui::fg_bg(fg, bg, draw);
|
||||
min_wh(w, h, bsp_s(top, bsp_n(low, fill_wh(draw))))
|
||||
}
|
||||
|
||||
/// TUI input loop event.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
||||
pub struct TuiEvent(pub Event);
|
||||
|
||||
/// TUI key spec.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
||||
pub struct TuiKey(pub Option<KeyCode>, pub KeyModifiers);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue