mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-07-17 15:56:57 +02:00
well, one tui example works again
This commit is contained in:
parent
145047b7ff
commit
be14173126
8 changed files with 440 additions and 362 deletions
|
|
@ -10,7 +10,7 @@ tui_main!(State {
|
||||||
});
|
});
|
||||||
|
|
||||||
tui_keys!(|self: State, input| {
|
tui_keys!(|self: State, input| {
|
||||||
todo!()
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
tui_view!(|self: State| {
|
tui_view!(|self: State| {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use ::ratatui::style::Color;
|
|
||||||
use crate::lang::impl_from;
|
use crate::lang::impl_from;
|
||||||
|
use ::ratatui::style::Color;
|
||||||
|
use ::rand::distributions::uniform::UniformSampler;
|
||||||
pub(crate) use ::palette::{
|
pub(crate) use ::palette::{
|
||||||
Okhsl, Srgb, OklabHue, Mix, okhsl::UniformOkhsl,
|
Okhsl, Srgb, OklabHue, Mix, okhsl::UniformOkhsl,
|
||||||
convert::{FromColor, FromColorUnclamped}
|
convert::{FromColor, FromColorUnclamped}
|
||||||
};
|
};
|
||||||
use rand::distributions::uniform::UniformSampler;
|
|
||||||
|
|
||||||
pub fn rgb (r: u8, g: u8, b: u8) -> ItemColor {
|
pub fn rgb (r: u8, g: u8, b: u8) -> ItemColor {
|
||||||
let term = Color::Rgb(r, g, b);
|
let term = Color::Rgb(r, g, b);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
use crate::*;
|
|
||||||
pub use crate::space::*;
|
|
||||||
|
|
||||||
mod draw;
|
mod draw;
|
||||||
pub use self::draw::*;
|
pub use self::draw::*;
|
||||||
|
|
||||||
|
|
@ -13,6 +10,9 @@ pub use self::thunk::*;
|
||||||
mod screen;
|
mod screen;
|
||||||
pub use self::screen::*;
|
pub use self::screen::*;
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
pub use crate::space::*;
|
||||||
|
|
||||||
/// Only render when condition is true.
|
/// Only render when condition is true.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,6 @@ pub trait Screen: Space<Self::Unit> + Send + Sync + Sized {
|
||||||
fn place_at <'t, T: Draw<Self> + ?Sized> (
|
fn place_at <'t, T: Draw<Self> + ?Sized> (
|
||||||
&mut self, _area: XYWH<Self::Unit>, _content: &'t T
|
&mut self, _area: XYWH<Self::Unit>, _content: &'t T
|
||||||
) {
|
) {
|
||||||
unimplemented!()
|
unimplemented!("place_at")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,4 +14,3 @@ impl AsRef<Arc<AtomicBool>> for Exit {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
15
src/keys.rs
15
src/keys.rs
|
|
@ -7,8 +7,17 @@ use ::crossterm::event::{
|
||||||
read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState
|
read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Enable TUI keyboard input for main state struct.
|
||||||
|
#[macro_export] macro_rules! tui_keys {
|
||||||
|
(|$self:ident:$State:ty,$input:ident|$body:block) => {
|
||||||
|
impl Apply<TuiEvent, Usually<()>> for $State {
|
||||||
|
fn apply (&mut $self, $input: &TuiEvent) -> Usually<()> $body
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Spawn the TUI input thread which reads keys from the terminal.
|
/// Spawn the TUI input thread which reads keys from the terminal.
|
||||||
pub fn tui_input <T: Apply<TuiEvent, Usually<T>> + Send + Sync + 'static> (
|
pub fn tui_input <T: Apply<TuiEvent, Usually<()>> + Send + Sync + 'static> (
|
||||||
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
|
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
|
||||||
) -> Result<Task, std::io::Error> {
|
) -> Result<Task, std::io::Error> {
|
||||||
let exited = exited.clone();
|
let exited = exited.clone();
|
||||||
|
|
@ -39,8 +48,11 @@ pub fn tui_input <T: Apply<TuiEvent, Usually<T>> + Send + Sync + 'static> (
|
||||||
/// TUI input loop event.
|
/// TUI input loop event.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
||||||
pub struct TuiEvent(pub Event);
|
pub struct TuiEvent(pub Event);
|
||||||
|
|
||||||
impl_from!(TuiEvent: |e: Event| TuiEvent(e));
|
impl_from!(TuiEvent: |e: Event| TuiEvent(e));
|
||||||
|
|
||||||
impl_from!(TuiEvent: |c: char| TuiEvent(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE))));
|
impl_from!(TuiEvent: |c: char| TuiEvent(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE))));
|
||||||
|
|
||||||
impl Ord for TuiEvent {
|
impl Ord for TuiEvent {
|
||||||
fn cmp (&self, other: &Self) -> std::cmp::Ordering {
|
fn cmp (&self, other: &Self) -> std::cmp::Ordering {
|
||||||
self.partial_cmp(other)
|
self.partial_cmp(other)
|
||||||
|
|
@ -51,6 +63,7 @@ impl Ord for TuiEvent {
|
||||||
/// TUI key spec.
|
/// TUI key spec.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
||||||
pub struct TuiKey(pub Option<KeyCode>, pub KeyModifiers);
|
pub struct TuiKey(pub Option<KeyCode>, pub KeyModifiers);
|
||||||
|
|
||||||
impl TuiKey {
|
impl TuiKey {
|
||||||
const SPLIT: char = '/';
|
const SPLIT: char = '/';
|
||||||
pub fn from_crossterm (event: KeyEvent) -> Self {
|
pub fn from_crossterm (event: KeyEvent) -> Self {
|
||||||
|
|
|
||||||
447
src/term.rs
447
src/term.rs
|
|
@ -1,4 +1,7 @@
|
||||||
use crate::{*, lang::*, draw::*, space::{*, Split::*}, color::*, text::*, task::*};
|
mod border;
|
||||||
|
pub use self::border::*;
|
||||||
|
|
||||||
|
use crate::{*, lang::*, draw::*, task::*};
|
||||||
//use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
|
//use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
|
||||||
//use rand::distributions::uniform::UniformSampler;
|
//use rand::distributions::uniform::UniformSampler;
|
||||||
use ::{
|
use ::{
|
||||||
|
|
@ -7,51 +10,84 @@ use ::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
},
|
},
|
||||||
better_panic::{Settings, Verbosity},
|
|
||||||
ratatui::{
|
ratatui::{
|
||||||
prelude::{Style, Buffer as ScreenBuffer, Position, Backend, Color},
|
prelude::{Style, Position, Backend, Color},
|
||||||
style::{Modifier, Color::*},
|
style::{Modifier, Color::*},
|
||||||
backend::{CrosstermBackend, ClearType},
|
backend::{CrosstermBackend, ClearType},
|
||||||
layout::{Size, Rect},
|
layout::{Size, Rect},
|
||||||
buffer::{Buffer, Cell},
|
buffer::{Buffer, Cell},
|
||||||
},
|
|
||||||
crossterm::{
|
crossterm::{
|
||||||
ExecutableCommand,
|
ExecutableCommand,
|
||||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
|
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
|
||||||
//event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
|
//event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
|
||||||
}
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[macro_export] macro_rules! tui_main {
|
#[macro_export] macro_rules! tui_main {
|
||||||
($state:expr) => {
|
($state:expr) => {
|
||||||
pub fn main () -> Usually<()> {
|
pub fn main () -> Usually<()> {
|
||||||
tengri::exit::Exit::run(|exit|{
|
use ::{
|
||||||
|
std::sync::{
|
||||||
|
Arc,
|
||||||
|
RwLock
|
||||||
|
},
|
||||||
|
std::panic::{
|
||||||
|
set_hook,
|
||||||
|
PanicHookInfo,
|
||||||
|
},
|
||||||
|
std::time::{
|
||||||
|
Duration
|
||||||
|
},
|
||||||
|
better_panic::{
|
||||||
|
Settings,
|
||||||
|
Verbosity
|
||||||
|
},
|
||||||
|
ratatui::{
|
||||||
|
backend::{
|
||||||
|
Backend,
|
||||||
|
CrosstermBackend
|
||||||
|
},
|
||||||
|
crossterm::{
|
||||||
|
ExecutableCommand,
|
||||||
|
terminal::{
|
||||||
|
disable_raw_mode,
|
||||||
|
LeaveAlternateScreen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tengri::{
|
||||||
|
exit::Exit,
|
||||||
|
keys::tui_input,
|
||||||
|
term::tui_output,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let panic = Settings::auto()
|
||||||
|
.verbosity(Verbosity::Full)
|
||||||
|
.create_panic_handler();
|
||||||
|
set_hook(Box::new(move |info: &PanicHookInfo|{
|
||||||
|
stdout().execute(LeaveAlternateScreen).unwrap();
|
||||||
|
CrosstermBackend::new(stdout()).show_cursor().unwrap();
|
||||||
|
disable_raw_mode().unwrap();
|
||||||
|
panic(info);
|
||||||
|
}));
|
||||||
|
Exit::run(|exit|{
|
||||||
let state = Arc::new(RwLock::new($state));
|
let state = Arc::new(RwLock::new($state));
|
||||||
let input = ::tengri::keys::tui_input(
|
let scan = Duration::from_millis(100);
|
||||||
exit.as_ref(),
|
let input = tui_input(exit.as_ref(), &state, scan)?;
|
||||||
&state,
|
let frame = Duration::from_millis(10);
|
||||||
::std::time::Duration::from_millis(100)
|
let output = tui_output(stdout(), exit.as_ref(), &state, frame)?;
|
||||||
)?;
|
output.join();
|
||||||
let output = ::tengri::term::tui_output(
|
stdout().execute(LeaveAlternateScreen)?;
|
||||||
stdout(),
|
CrosstermBackend::new(stdout()).show_cursor()?;
|
||||||
exit.as_ref(),
|
disable_raw_mode()?;
|
||||||
&state,
|
|
||||||
::std::time::Duration::from_millis(10)
|
|
||||||
)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export] macro_rules! tui_keys {
|
/// Enable TUI output for state struct.
|
||||||
(|$self:ident:$State:ty,$input:ident|$body:block) => {
|
|
||||||
impl Apply<TuiEvent, Usually<Self>> for $State {
|
|
||||||
fn apply (&mut $self, $input: &TuiEvent) -> Usually<Self> $body
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! tui_view {
|
#[macro_export] macro_rules! tui_view {
|
||||||
(|$self:ident: $State:ty|$body:block) => {
|
(|$self:ident: $State:ty|$body:block) => {
|
||||||
impl View<Tui> for $State {
|
impl View<Tui> for $State {
|
||||||
|
|
@ -61,42 +97,94 @@ use ::{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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 + 'static, T: View<Tui> + Send + Sync + 'static> (
|
pub fn tui_output <
|
||||||
|
W: Write + Send + Sync + 'static, T: View<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
|
||||||
) -> Usually<Task> {
|
) -> Usually<Task> {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
tui_setup()?;
|
stdout().execute(EnterAlternateScreen)?;
|
||||||
|
CrosstermBackend::new(stdout()).hide_cursor()?;
|
||||||
|
enable_raw_mode()?;
|
||||||
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 prev = Tui::new(width, height);
|
||||||
let mut buffer_b = Buffer::empty(Rect { x: 0, y: 0, width, height });
|
let mut next = Tui::new(width, height);
|
||||||
Ok(Task::new_sleep(exited.clone(), sleep, move |perf| {
|
Ok(Task::new_sleep(exited.clone(), sleep, move |perf| {
|
||||||
let Size { width, height } = 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, Rect { x: 0, y: 0, width, height });
|
prev.resize(&mut backend, width, height);
|
||||||
tui_redraw(&mut backend, &mut buffer_a, &mut buffer_b);
|
state.view().draw(&mut next).expect("draw failed"); // TODO draw error
|
||||||
|
prev.redraw(&mut backend, &mut next);
|
||||||
|
//tui_redraw(&mut backend, &mut prev, &mut next);
|
||||||
}
|
}
|
||||||
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());
|
prev.set_string(0, 0, &timer, Style::default());
|
||||||
})?)
|
})?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Tui(pub Buffer, pub XYWH<u16>);
|
pub fn tui_redraw <'b, W: Write> (
|
||||||
|
back: &mut CrosstermBackend<W>,
|
||||||
|
mut prev: &'b mut Buffer,
|
||||||
|
mut next: &'b mut Buffer
|
||||||
|
) {
|
||||||
|
let updates = prev.diff(&next);
|
||||||
|
back.draw(updates.into_iter()).expect("failed to render");
|
||||||
|
Backend::flush(back).expect("failed to flush output new");
|
||||||
|
std::mem::swap(&mut prev, &mut next);
|
||||||
|
next.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Terminal output.
|
||||||
|
pub struct Tui(
|
||||||
|
/// Ratatui buffer; area is screen size
|
||||||
|
pub Buffer,
|
||||||
|
/// Current draw area
|
||||||
|
pub XYWH<u16>
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Tui {
|
||||||
|
fn new (width: u16, height: u16) -> Self {
|
||||||
|
Self(Buffer::empty(Rect { x: 0, y: 0, width, height }), XYWH(0, 0, width, height))
|
||||||
|
}
|
||||||
|
fn resize <W: Write> (&mut self, back: &mut CrosstermBackend<W>, width: u16, height: u16) {
|
||||||
|
let size = Rect { x: 0, y: 0, width, height };
|
||||||
|
if self.0.area != size {
|
||||||
|
back.clear_region(ClearType::All).unwrap();
|
||||||
|
self.0.resize(size);
|
||||||
|
self.0.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn redraw <'b, W: Write> (&'b mut self, back: &mut CrosstermBackend<W>, mut next: &'b mut Self) {
|
||||||
|
let updates = self.0.diff(&next.0);
|
||||||
|
back.draw(updates.into_iter()).expect("failed to render");
|
||||||
|
Backend::flush(back).expect("failed to flush output new");
|
||||||
|
std::mem::swap(self, &mut next);
|
||||||
|
next.0.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Screen for Tui { type Unit = u16; }
|
impl Screen for Tui { type Unit = u16; }
|
||||||
|
|
||||||
impl Deref for Tui { type Target = Buffer; fn deref (&self) -> &Buffer { &self.0 } }
|
impl Deref for Tui { type Target = Buffer; fn deref (&self) -> &Buffer { &self.0 } }
|
||||||
|
|
||||||
impl DerefMut for Tui { fn deref_mut (&mut self) -> &mut Buffer { &mut self.0 } }
|
impl DerefMut for Tui { fn deref_mut (&mut self) -> &mut Buffer { &mut self.0 } }
|
||||||
|
|
||||||
impl HasOrigin for Tui { fn origin (&self) -> Origin { Origin::NW } }
|
impl HasOrigin for Tui { fn origin (&self) -> Origin { Origin::NW } }
|
||||||
|
|
||||||
impl X<u16> for Tui {
|
impl X<u16> for Tui {
|
||||||
fn x (&self) -> u16 { self.1.0 }
|
fn x (&self) -> u16 { self.1.0 }
|
||||||
fn w (&self) -> u16 { self.1.2 }
|
fn w (&self) -> u16 { self.1.2 }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Y<u16> for Tui {
|
impl Y<u16> for Tui {
|
||||||
fn y (&self) -> u16 { self.1.1 }
|
fn y (&self) -> u16 { self.1.1 }
|
||||||
fn h (&self) -> u16 { self.1.3 }
|
fn h (&self) -> u16 { self.1.3 }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tui {
|
impl Tui {
|
||||||
pub fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> XYWH<u16> {
|
pub fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> XYWH<u16> {
|
||||||
for row in 0..self.h() {
|
for row in 0..self.h() {
|
||||||
|
|
@ -127,6 +215,7 @@ impl Tui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply foreground color.
|
/// Apply foreground color.
|
||||||
pub const fn fg (fg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
pub const fn fg (fg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
thunk(move|to: &mut Tui|{
|
thunk(move|to: &mut Tui|{
|
||||||
|
|
@ -134,6 +223,7 @@ pub const fn fg (fg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
draw.draw(to)
|
draw.draw(to)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply background color.
|
/// Apply background color.
|
||||||
pub const fn bg (bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
pub const fn bg (bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
thunk(move|to: &mut Tui|{
|
thunk(move|to: &mut Tui|{
|
||||||
|
|
@ -141,17 +231,20 @@ pub const fn bg (bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
draw.draw(to)
|
draw.draw(to)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn fg_bg (fg: Color, bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
pub const fn fg_bg (fg: Color, bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
thunk(move|to: &mut Tui|{
|
thunk(move|to: &mut Tui|{
|
||||||
to.update(&|cell,_,_|{ cell.set_fg(fg); cell.set_bg(bg); });
|
to.update(&|cell,_,_|{ cell.set_fg(fg); cell.set_bg(bg); });
|
||||||
draw.draw(to)
|
draw.draw(to)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn fill_char (c: char) -> impl Draw<Tui> {
|
pub const fn fill_char (c: char) -> impl Draw<Tui> {
|
||||||
thunk(move|to: &mut Tui|Ok(to.update(&|cell,_,_|{
|
thunk(move|to: &mut Tui|Ok(to.update(&|cell,_,_|{
|
||||||
cell.set_char(c);
|
cell.set_char(c);
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw contents with modifier applied.
|
/// Draw contents with modifier applied.
|
||||||
pub const fn modify (on: bool, modifier: Modifier, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
pub const fn modify (on: bool, modifier: Modifier, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
thunk(move|to: &mut Tui|{
|
thunk(move|to: &mut Tui|{
|
||||||
|
|
@ -159,6 +252,7 @@ pub const fn modify (on: bool, modifier: Modifier, draw: impl Draw<Tui>) -> impl
|
||||||
draw.draw(to)
|
draw.draw(to)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn fill_mod (on: bool, modifier: Modifier) -> impl Draw<Tui> {
|
pub const fn fill_mod (on: bool, modifier: Modifier) -> impl Draw<Tui> {
|
||||||
thunk(move|to: &mut Tui|Ok({
|
thunk(move|to: &mut Tui|Ok({
|
||||||
if on {
|
if on {
|
||||||
|
|
@ -168,10 +262,12 @@ pub const fn fill_mod (on: bool, modifier: Modifier) -> impl Draw<Tui> {
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw contents with bold modifier applied.
|
/// Draw contents with bold modifier applied.
|
||||||
pub const fn bold (on: bool, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
pub const fn bold (on: bool, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
modify(on, Modifier::BOLD, draw)
|
modify(on, Modifier::BOLD, draw)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn fill_ul (color: Option<Color>) -> impl Draw<Tui> {
|
pub const fn fill_ul (color: Option<Color>) -> impl Draw<Tui> {
|
||||||
thunk(move|to: &mut Tui|Ok(if let Some(color) = color {
|
thunk(move|to: &mut Tui|Ok(if let Some(color) = color {
|
||||||
to.update(&|cell,_,_|{
|
to.update(&|cell,_,_|{
|
||||||
|
|
@ -196,6 +292,7 @@ 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!() }
|
||||||
}
|
}
|
||||||
|
|
@ -288,216 +385,6 @@ pub const fn button_3 <'a> (
|
||||||
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 {
|
|
||||||
($($T:ident {
|
|
||||||
$nw:literal $n:literal $ne:literal $w:literal $e:literal $sw:literal $s:literal $se:literal
|
|
||||||
$($x:tt)*
|
|
||||||
}),+) => {$(
|
|
||||||
impl BorderStyle for $T {
|
|
||||||
const NW: &'static str = $nw;
|
|
||||||
const N: &'static str = $n;
|
|
||||||
const NE: &'static str = $ne;
|
|
||||||
const W: &'static str = $w;
|
|
||||||
const E: &'static str = $e;
|
|
||||||
const SW: &'static str = $sw;
|
|
||||||
const S: &'static str = $s;
|
|
||||||
const SE: &'static str = $se;
|
|
||||||
$($x)*
|
|
||||||
fn enabled (&self) -> bool { self.0 }
|
|
||||||
}
|
|
||||||
#[derive(Copy, Clone)] pub struct $T(pub bool, pub Style);
|
|
||||||
//impl Layout<Tui> for $T {}
|
|
||||||
impl Draw<Tui> for $T {
|
|
||||||
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
|
|
||||||
when(self.enabled(), thunk(|to: &mut Tui|BorderStyle::draw(self, to))).draw(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+}
|
|
||||||
}
|
|
||||||
|
|
||||||
border! {
|
|
||||||
Square {
|
|
||||||
"┌" "─" "┐"
|
|
||||||
"│" "│"
|
|
||||||
"└" "─" "┘" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
SquareBold {
|
|
||||||
"┏" "━" "┓"
|
|
||||||
"┃" "┃"
|
|
||||||
"┗" "━" "┛" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
TabLike {
|
|
||||||
"╭" "─" "╮"
|
|
||||||
"│" "│"
|
|
||||||
"│" " " "│" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Lozenge {
|
|
||||||
"╭" "─" "╮"
|
|
||||||
"│" "│"
|
|
||||||
"╰" "─" "╯" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Brace {
|
|
||||||
"╭" "" "╮"
|
|
||||||
"│" "│"
|
|
||||||
"╰" "" "╯" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
LozengeDotted {
|
|
||||||
"╭" "┅" "╮"
|
|
||||||
"┇" "┇"
|
|
||||||
"╰" "┅" "╯" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Quarter {
|
|
||||||
"▎" "▔" "🮇"
|
|
||||||
"▎" "🮇"
|
|
||||||
"▎" "▁" "🮇" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
QuarterV {
|
|
||||||
"▎" "" "🮇"
|
|
||||||
"▎" "🮇"
|
|
||||||
"▎" "" "🮇" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Chamfer {
|
|
||||||
"🭂" "▔" "🭍"
|
|
||||||
"▎" "🮇"
|
|
||||||
"🭓" "▁" "🭞" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Corners {
|
|
||||||
"🬆" "" "🬊" // 🬴 🬸
|
|
||||||
"" ""
|
|
||||||
"🬱" "" "🬵" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
CornersTall {
|
|
||||||
"🭽" "" "🭾"
|
|
||||||
"" ""
|
|
||||||
"🭼" "" "🭿" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Outer {
|
|
||||||
"🭽" "▔" "🭾"
|
|
||||||
"▏" "▕"
|
|
||||||
"🭼" "▁" "🭿"
|
|
||||||
const W0: &'static str = "[";
|
|
||||||
const E0: &'static str = "]";
|
|
||||||
const N0: &'static str = "⎴";
|
|
||||||
const S0: &'static str = "⎵";
|
|
||||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Thick {
|
|
||||||
"▄" "▄" "▄"
|
|
||||||
"█" "█"
|
|
||||||
"▀" "▀" "▀"
|
|
||||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Rugged {
|
|
||||||
"▄" "▂" "▄"
|
|
||||||
"▐" "▌"
|
|
||||||
"▀" "🮂" "▀"
|
|
||||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Skinny {
|
|
||||||
"▗" "▄" "▖"
|
|
||||||
"▐" "▌"
|
|
||||||
"▝" "▀" "▘"
|
|
||||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Brackets {
|
|
||||||
"⎡" "" "⎤"
|
|
||||||
"" ""
|
|
||||||
"⎣" "" "⎦"
|
|
||||||
const W0: &'static str = "[";
|
|
||||||
const E0: &'static str = "]";
|
|
||||||
const N0: &'static str = "⎴";
|
|
||||||
const S0: &'static str = "⎵";
|
|
||||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Reticle {
|
|
||||||
"⎡" "" "⎤"
|
|
||||||
"" ""
|
|
||||||
"⎣" "" "⎦"
|
|
||||||
const W0: &'static str = "╟";
|
|
||||||
const E0: &'static str = "╢";
|
|
||||||
const N0: &'static str = "┯";
|
|
||||||
const S0: &'static str = "┷";
|
|
||||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait BorderStyle: Draw<Tui> + 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 }
|
|
||||||
|
|
||||||
#[inline] fn draw <'a> (self, to: &mut Tui) -> Usually<XYWH<u16>> {
|
|
||||||
if self.enabled() {
|
|
||||||
self.draw_h(to, None)?;
|
|
||||||
self.draw_v(to, None)?;
|
|
||||||
self.draw_c(to, None)?;
|
|
||||||
}
|
|
||||||
Ok(to.1)
|
|
||||||
}
|
|
||||||
#[inline] fn draw_h (self, to: &mut Tui, style: Option<Style>) -> Usually<XYWH<u16>> {
|
|
||||||
let style = style.or_else(||self.style_horizontal());
|
|
||||||
let y1 = to.y_north();
|
|
||||||
let y2 = to.y_south().saturating_sub(1);
|
|
||||||
for x in to.x_west()..to.x_east().saturating_sub(1) {
|
|
||||||
to.blit(&Self::N, x, y1, style);
|
|
||||||
to.blit(&Self::S, x, y2, style)
|
|
||||||
}
|
|
||||||
Ok(to.xywh())
|
|
||||||
}
|
|
||||||
#[inline] fn draw_v (self, to: &mut Tui, style: Option<Style>) -> Usually<XYWH<u16>> {
|
|
||||||
let area = to.1;
|
|
||||||
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 Tui, style: Option<Style>) -> Usually<XYWH<u16>> {
|
|
||||||
let area = to.1;
|
|
||||||
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.
|
/// Stackably padded.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -566,82 +453,20 @@ fn y_scroll () -> impl Draw<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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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<()> {
|
||||||
|
use ::ratatui::backend::Backend;
|
||||||
stdout().execute(LeaveAlternateScreen)?;
|
stdout().execute(LeaveAlternateScreen)?;
|
||||||
backend.show_cursor()?;
|
backend.show_cursor()?;
|
||||||
disable_raw_mode().map_err(Into::into)
|
disable_raw_mode().map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tui_update (
|
pub fn tui_update (
|
||||||
Tui(buf, ..): &mut Tui, area: XYWH<u16>, callback: &impl Fn(&mut Cell, u16, u16)
|
Tui(buf, ..): &mut Tui,
|
||||||
|
area: XYWH<u16>,
|
||||||
|
callback: &impl Fn(&mut Cell, u16, u16)
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw border around shrinked item.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// /// TODO
|
|
||||||
/// ```
|
|
||||||
pub const fn border <T, S: BorderStyle> (on: bool, style: S, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
|
||||||
let content = wh_pad(1, 1, draw);
|
|
||||||
let outline = when(on, thunk(move|to: &mut Tui|{
|
|
||||||
let XYWH(x, y, w, h) = to.1;
|
|
||||||
if w > 0 && h > 0 {
|
|
||||||
to.blit(&style.border_nw(), x, y, style.style());
|
|
||||||
to.blit(&style.border_ne(), x + w - 1, y, style.style());
|
|
||||||
to.blit(&style.border_sw(), x, y + h - 1, style.style());
|
|
||||||
to.blit(&style.border_se(), x + w - 1, y + h - 1, style.style());
|
|
||||||
for x in x+1..x+w-1 {
|
|
||||||
to.blit(&style.border_n(), x, y, style.style());
|
|
||||||
to.blit(&style.border_s(), x, y + h - 1, style.style());
|
|
||||||
}
|
|
||||||
for y in y+1..y+h-1 {
|
|
||||||
to.blit(&style.border_w(), x, y, style.style());
|
|
||||||
to.blit(&style.border_e(), x + w - 1, y, style.style());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(XYWH(x, y, w, h))
|
|
||||||
}));
|
|
||||||
above(outline, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw TUI content or its error message.
|
/// Draw TUI content or its error message.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -663,14 +488,16 @@ pub fn catcher <T: Draw<Tui>> (result: Usually<T>) -> impl Draw<Tui> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TUI buffer sized by `usize` instead of `u16`.
|
/// TUI buffer sized by `usize` instead of `u16`.
|
||||||
#[derive(Default)]
|
#[derive(Default)] pub struct BigBuffer {
|
||||||
pub struct BigBuffer {
|
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
pub content: Vec<Cell>
|
pub content: Vec<Cell>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_from!(BigBuffer: |size:(usize, usize)| Self::new(size.0, size.1));
|
impl_from!(BigBuffer: |size:(usize, usize)| Self::new(size.0, size.1));
|
||||||
|
|
||||||
impl_debug!(BigBuffer |self, f| { write!(f, "[BB {}x{} ({})]", self.width, self.height, self.content.len()) });
|
impl_debug!(BigBuffer |self, f| { write!(f, "[BB {}x{} ({})]", self.width, self.height, self.content.len()) });
|
||||||
|
|
||||||
impl BigBuffer {
|
impl BigBuffer {
|
||||||
pub fn new (width: usize, height: usize) -> Self {
|
pub fn new (width: usize, height: usize) -> Self {
|
||||||
Self { width, height, content: vec![Cell::default(); width*height] }
|
Self { width, height, content: vec![Cell::default(); width*height] }
|
||||||
|
|
|
||||||
239
src/term/border.rs
Normal file
239
src/term/border.rs
Normal file
|
|
@ -0,0 +1,239 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Draw border around item shrunk by 1 on each side.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// /// TODO
|
||||||
|
/// ```
|
||||||
|
pub const fn border <T, S: BorderStyle> (on: bool, style: S, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
|
let content = wh_pad(1, 1, draw);
|
||||||
|
let outline = when(on, thunk(move|to: &mut Tui|{
|
||||||
|
let XYWH(x, y, w, h) = to.1;
|
||||||
|
if w > 0 && h > 0 {
|
||||||
|
to.blit(&style.border_nw(), x, y, style.style());
|
||||||
|
to.blit(&style.border_ne(), x + w - 1, y, style.style());
|
||||||
|
to.blit(&style.border_sw(), x, y + h - 1, style.style());
|
||||||
|
to.blit(&style.border_se(), x + w - 1, y + h - 1, style.style());
|
||||||
|
for x in x+1..x+w-1 {
|
||||||
|
to.blit(&style.border_n(), x, y, style.style());
|
||||||
|
to.blit(&style.border_s(), x, y + h - 1, style.style());
|
||||||
|
}
|
||||||
|
for y in y+1..y+h-1 {
|
||||||
|
to.blit(&style.border_w(), x, y, style.style());
|
||||||
|
to.blit(&style.border_e(), x + w - 1, y, style.style());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(XYWH(x, y, w, h))
|
||||||
|
}));
|
||||||
|
above(outline, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! border {
|
||||||
|
($($T:ident {
|
||||||
|
$nw:literal $n:literal $ne:literal $w:literal $e:literal $sw:literal $s:literal $se:literal
|
||||||
|
$($x:tt)*
|
||||||
|
}),+) => {$(
|
||||||
|
impl BorderStyle for $T {
|
||||||
|
const NW: &'static str = $nw;
|
||||||
|
const N: &'static str = $n;
|
||||||
|
const NE: &'static str = $ne;
|
||||||
|
const W: &'static str = $w;
|
||||||
|
const E: &'static str = $e;
|
||||||
|
const SW: &'static str = $sw;
|
||||||
|
const S: &'static str = $s;
|
||||||
|
const SE: &'static str = $se;
|
||||||
|
$($x)*
|
||||||
|
fn enabled (&self) -> bool { self.0 }
|
||||||
|
}
|
||||||
|
#[derive(Copy, Clone)] pub struct $T(pub bool, pub Style);
|
||||||
|
//impl Layout<Tui> for $T {}
|
||||||
|
impl Draw<Tui> for $T {
|
||||||
|
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
|
||||||
|
when(self.enabled(), thunk(|to: &mut Tui|BorderStyle::draw(self, to))).draw(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+}
|
||||||
|
}
|
||||||
|
|
||||||
|
border! {
|
||||||
|
Square {
|
||||||
|
"┌" "─" "┐"
|
||||||
|
"│" "│"
|
||||||
|
"└" "─" "┘" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
},
|
||||||
|
SquareBold {
|
||||||
|
"┏" "━" "┓"
|
||||||
|
"┃" "┃"
|
||||||
|
"┗" "━" "┛" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
},
|
||||||
|
TabLike {
|
||||||
|
"╭" "─" "╮"
|
||||||
|
"│" "│"
|
||||||
|
"│" " " "│" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
},
|
||||||
|
Lozenge {
|
||||||
|
"╭" "─" "╮"
|
||||||
|
"│" "│"
|
||||||
|
"╰" "─" "╯" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
},
|
||||||
|
Brace {
|
||||||
|
"╭" "" "╮"
|
||||||
|
"│" "│"
|
||||||
|
"╰" "" "╯" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
},
|
||||||
|
LozengeDotted {
|
||||||
|
"╭" "┅" "╮"
|
||||||
|
"┇" "┇"
|
||||||
|
"╰" "┅" "╯" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
},
|
||||||
|
Quarter {
|
||||||
|
"▎" "▔" "🮇"
|
||||||
|
"▎" "🮇"
|
||||||
|
"▎" "▁" "🮇" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
},
|
||||||
|
QuarterV {
|
||||||
|
"▎" "" "🮇"
|
||||||
|
"▎" "🮇"
|
||||||
|
"▎" "" "🮇" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
},
|
||||||
|
Chamfer {
|
||||||
|
"🭂" "▔" "🭍"
|
||||||
|
"▎" "🮇"
|
||||||
|
"🭓" "▁" "🭞" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
},
|
||||||
|
Corners {
|
||||||
|
"🬆" "" "🬊" // 🬴 🬸
|
||||||
|
"" ""
|
||||||
|
"🬱" "" "🬵" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
},
|
||||||
|
CornersTall {
|
||||||
|
"🭽" "" "🭾"
|
||||||
|
"" ""
|
||||||
|
"🭼" "" "🭿" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
},
|
||||||
|
Outer {
|
||||||
|
"🭽" "▔" "🭾"
|
||||||
|
"▏" "▕"
|
||||||
|
"🭼" "▁" "🭿"
|
||||||
|
const W0: &'static str = "[";
|
||||||
|
const E0: &'static str = "]";
|
||||||
|
const N0: &'static str = "⎴";
|
||||||
|
const S0: &'static str = "⎵";
|
||||||
|
fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
},
|
||||||
|
Thick {
|
||||||
|
"▄" "▄" "▄"
|
||||||
|
"█" "█"
|
||||||
|
"▀" "▀" "▀"
|
||||||
|
fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
},
|
||||||
|
Rugged {
|
||||||
|
"▄" "▂" "▄"
|
||||||
|
"▐" "▌"
|
||||||
|
"▀" "🮂" "▀"
|
||||||
|
fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
},
|
||||||
|
Skinny {
|
||||||
|
"▗" "▄" "▖"
|
||||||
|
"▐" "▌"
|
||||||
|
"▝" "▀" "▘"
|
||||||
|
fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
},
|
||||||
|
Brackets {
|
||||||
|
"⎡" "" "⎤"
|
||||||
|
"" ""
|
||||||
|
"⎣" "" "⎦"
|
||||||
|
const W0: &'static str = "[";
|
||||||
|
const E0: &'static str = "]";
|
||||||
|
const N0: &'static str = "⎴";
|
||||||
|
const S0: &'static str = "⎵";
|
||||||
|
fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
},
|
||||||
|
Reticle {
|
||||||
|
"⎡" "" "⎤"
|
||||||
|
"" ""
|
||||||
|
"⎣" "" "⎦"
|
||||||
|
const W0: &'static str = "╟";
|
||||||
|
const E0: &'static str = "╢";
|
||||||
|
const N0: &'static str = "┯";
|
||||||
|
const S0: &'static str = "┷";
|
||||||
|
fn style (&self) -> Option<Style> { Some(self.1) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait BorderStyle: Draw<Tui> + 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 }
|
||||||
|
|
||||||
|
#[inline] fn draw <'a> (self, to: &mut Tui) -> Usually<XYWH<u16>> {
|
||||||
|
if self.enabled() {
|
||||||
|
self.draw_h(to, None)?;
|
||||||
|
self.draw_v(to, None)?;
|
||||||
|
self.draw_c(to, None)?;
|
||||||
|
}
|
||||||
|
Ok(to.1)
|
||||||
|
}
|
||||||
|
#[inline] fn draw_h (self, to: &mut Tui, style: Option<Style>) -> Usually<XYWH<u16>> {
|
||||||
|
let style = style.or_else(||self.style_horizontal());
|
||||||
|
let y1 = to.y_north();
|
||||||
|
let y2 = to.y_south().saturating_sub(1);
|
||||||
|
for x in to.x_west()..to.x_east().saturating_sub(1) {
|
||||||
|
to.blit(&Self::N, x, y1, style);
|
||||||
|
to.blit(&Self::S, x, y2, style)
|
||||||
|
}
|
||||||
|
Ok(to.xywh())
|
||||||
|
}
|
||||||
|
#[inline] fn draw_v (self, to: &mut Tui, style: Option<Style>) -> Usually<XYWH<u16>> {
|
||||||
|
let area = to.1;
|
||||||
|
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 Tui, style: Option<Style>) -> Usually<XYWH<u16>> {
|
||||||
|
let area = to.1;
|
||||||
|
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 = "";
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue