mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-04-03 21:40:44 +02:00
all possible insanities simultaneously.
but at least deleting more than i'm writing (hopefully!)
This commit is contained in:
parent
9dbf4fcab5
commit
5d627f7669
5 changed files with 868 additions and 815 deletions
|
|
@ -1439,25 +1439,4 @@ impl<E: Engine, S, C: Command<S>> MenuItem<E, S, C> {
|
|||
|
||||
///// TUI helper defs.
|
||||
//impl Tui {
|
||||
////pub const fn null () -> Color { Color::Reset }
|
||||
////pub const fn red () -> Color { Color::Rgb(255,0, 0) }
|
||||
////pub const fn orange () -> Color { Color::Rgb(255,128,0) }
|
||||
////pub const fn yellow () -> Color { Color::Rgb(255,255,0) }
|
||||
////pub const fn brown () -> Color { Color::Rgb(128,255,0) }
|
||||
////pub const fn green () -> Color { Color::Rgb(0,255,0) }
|
||||
////pub const fn electric () -> Color { Color::Rgb(0,255,128) }
|
||||
////pub const fn g (g: u8) -> Color { Color::Rgb(g, g, g) }
|
||||
////fn bg0 () -> Color { Color::Rgb(20, 20, 20) }
|
||||
////fn bg () -> Color { Color::Rgb(28, 35, 25) }
|
||||
////fn border_bg () -> Color { Color::Rgb(40, 50, 30) }
|
||||
////fn border_fg (f: bool) -> Color { if f { Self::bo1() } else { Self::bo2() } }
|
||||
////fn title_fg (f: bool) -> Color { if f { Self::ti1() } else { Self::ti2() } }
|
||||
////fn separator_fg (_: bool) -> Color { Color::Rgb(0, 0, 0) }
|
||||
////fn mode_bg () -> Color { Color::Rgb(150, 160, 90) }
|
||||
////fn mode_fg () -> Color { Color::Rgb(255, 255, 255) }
|
||||
////fn status_bar_bg () -> Color { Color::Rgb(28, 35, 25) }
|
||||
////fn bo1 () -> Color { Color::Rgb(100, 110, 40) }
|
||||
////fn bo2 () -> Color { Color::Rgb(70, 80, 50) }
|
||||
////fn ti1 () -> Color { Color::Rgb(150, 160, 90) }
|
||||
////fn ti2 () -> Color { Color::Rgb(120, 130, 100) }
|
||||
//}
|
||||
|
|
|
|||
10
src/color.rs
10
src/color.rs
|
|
@ -55,7 +55,15 @@ impl ItemColor {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ItemTheme {}
|
||||
pub struct ItemTheme {
|
||||
pub base: ItemColor,
|
||||
pub light: ItemColor,
|
||||
pub lighter: ItemColor,
|
||||
pub lightest: ItemColor,
|
||||
pub dark: ItemColor,
|
||||
pub darker: ItemColor,
|
||||
pub darkest: ItemColor,
|
||||
}
|
||||
impl_from!(ItemTheme: |base: ItemColor| Self::from_item_color(base));
|
||||
impl_from!(ItemTheme: |base: Color| Self::from_tui_color(base));
|
||||
impl ItemTheme {
|
||||
|
|
|
|||
969
src/draw.rs
969
src/draw.rs
File diff suppressed because it is too large
Load diff
606
src/term.rs
606
src/term.rs
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{*, lang::*, play::*, draw::*, color::*, text::*};
|
||||
use crate::{*, lang::*, play::*, draw::{*, Split::*}, color::*, text::*};
|
||||
use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
|
||||
use rand::distributions::uniform::UniformSampler;
|
||||
use ::{
|
||||
|
|
@ -8,149 +8,140 @@ use ::{
|
|||
},
|
||||
better_panic::{Settings, Verbosity},
|
||||
ratatui::{
|
||||
prelude::{Color, Style, Buffer, Position, Backend},
|
||||
prelude::{Style, Buffer as ScreenBuffer, Position, Backend, Color},
|
||||
style::{Modifier, Color::*},
|
||||
backend::{CrosstermBackend, ClearType},
|
||||
layout::{Size, Rect},
|
||||
buffer::Cell
|
||||
buffer::{Buffer, Cell},
|
||||
},
|
||||
crossterm::{
|
||||
ExecutableCommand,
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
|
||||
event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
|
||||
}
|
||||
};
|
||||
|
||||
/// Marker trait for structs that may be root of TUI app.
|
||||
pub trait Tui: Draw<Buffer> + Do<TuiEvent, Perhaps<TuiEvent>> {}
|
||||
|
||||
/// `Tui` is automatically implemented.
|
||||
impl<T: Draw<Buffer> + Do<TuiEvent, Perhaps<TuiEvent>>> Tui for T {}
|
||||
|
||||
/// Spawn the TUI input thread which reads keys from the terminal.
|
||||
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();
|
||||
let state = state.clone();
|
||||
Thread::new_poll(exited.clone(), poll, move |_| {
|
||||
let event = read().unwrap();
|
||||
match event {
|
||||
|
||||
// Hardcoded exit.
|
||||
Event::Key(KeyEvent {
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
code: KeyCode::Char('c'),
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE
|
||||
}) => { exited.store(true, Relaxed); },
|
||||
|
||||
// Handle all other events by the state:
|
||||
_ => {
|
||||
let event = TuiEvent::from_crossterm(event);
|
||||
if let Err(e) = state.write().unwrap().handle(&event) {
|
||||
panic!("{e}")
|
||||
pub struct Tui(pub Buffer, pub XYWH<u16>);
|
||||
impl Screen for Tui { type Unit = u16; }
|
||||
impl HasX<u16> for Tui { fn x (&self) -> X<u16> { self.1.x() } }
|
||||
impl HasY<u16> for Tui { fn y (&self) -> Y<u16> { self.1.y() } }
|
||||
impl HasW<u16> for Tui { fn w (&self) -> W<u16> { self.1.w() } }
|
||||
impl HasH<u16> for Tui { fn h (&self) -> H<u16> { self.1.h() } }
|
||||
impl HasOrigin for Tui { fn origin (&self) -> Origin { Origin::NW } }
|
||||
impl AsRef<Buffer> for Tui { fn as_ref (&self) -> &Buffer { &self.0 } }
|
||||
impl AsMut<Buffer> for Tui { fn as_mut (&mut self) -> &mut Buffer { &mut self.0 } }
|
||||
impl AsRef<XYWH<u16>> for Tui { fn as_ref (&self) -> &XYWH<u16> { &self.0 } }
|
||||
impl AsMut<XYWH<u16>> for Tui { fn as_mut (&mut self) -> &mut XYWH<u16> { &mut self.0 } }
|
||||
impl Tui {
|
||||
fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> WH<u16> {
|
||||
tui_update(self.0, self.1, callback);
|
||||
self.1.wh()
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Apply foreground color.
|
||||
pub const fn fg (fg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|{
|
||||
to.update(&|cell,_,_|{ cell.set_fg(fg); });
|
||||
draw.draw(to)
|
||||
})
|
||||
}
|
||||
/// Apply background color.
|
||||
pub const fn bg (bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|{
|
||||
to.update(&|cell,_,_|{ cell.set_bg(bg); });
|
||||
draw.draw(to)
|
||||
})
|
||||
}
|
||||
pub const fn fg_bg (fg: Color, bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|{
|
||||
to.update(&|cell,_,_|{ cell.set_fg(fg); cell.set_bg(bg); });
|
||||
draw.draw(to)
|
||||
})
|
||||
}
|
||||
pub const fn fill_char (c: char) {
|
||||
thunk(move|to: &mut Tui|Ok(to.update(&|cell,_,_|{
|
||||
cell.set_char(c);
|
||||
})))
|
||||
}
|
||||
/// Draw contents with modifier applied.
|
||||
pub const fn modify (on: bool, modifier: Modifier, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|{
|
||||
fill_mod(on, modifier).draw(to)?;
|
||||
draw.draw(to)
|
||||
})
|
||||
}
|
||||
pub const fn fill_mod (on: bool, modifier: Modifier) {
|
||||
thunk(move|to: &mut Tui|{
|
||||
if on {
|
||||
to.update(&|cell,_,_|cell.modifier.insert(modifier))
|
||||
} else {
|
||||
to.update(&|cell,_,_|cell.modifier.remove(modifier))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// TUI input loop event.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
||||
pub struct TuiEvent(pub Event);
|
||||
impl_from!(TuiEvent: |e: Event| TuiEvent(e));
|
||||
impl_from!(TuiEvent: |c: char| TuiEvent(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE))));
|
||||
impl TuiEvent {
|
||||
#[cfg(feature = "dsl")] pub fn from_dsl (dsl: impl Language) -> Perhaps<Self> {
|
||||
Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self))
|
||||
/// Draw contents with bold modifier applied.
|
||||
pub const fn bold (on: bool, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
modify(on, Modifier::BOLD, draw)
|
||||
}
|
||||
}
|
||||
impl Ord for TuiEvent {
|
||||
fn cmp (&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.partial_cmp(other)
|
||||
.unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf
|
||||
}
|
||||
}
|
||||
|
||||
/// TUI key spec.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
||||
pub struct TuiKey(pub Option<KeyCode>, pub KeyModifiers);
|
||||
|
||||
impl TuiKey {
|
||||
const SPLIT: char = '/';
|
||||
pub fn to_crossterm (&self) -> Option<Event> {
|
||||
self.0.map(|code|Event::Key(KeyEvent {
|
||||
code,
|
||||
modifiers: self.1,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}))
|
||||
}
|
||||
pub fn named (token: &str) -> Option<KeyCode> {
|
||||
use KeyCode::*;
|
||||
Some(match token {
|
||||
"up" => Up,
|
||||
"down" => Down,
|
||||
"left" => Left,
|
||||
"right" => Right,
|
||||
"esc" | "escape" => Esc,
|
||||
"enter" | "return" => Enter,
|
||||
"delete" | "del" => Delete,
|
||||
"backspace" => Backspace,
|
||||
"tab" => Tab,
|
||||
"space" => Char(' '),
|
||||
"comma" => Char(','),
|
||||
"period" => Char('.'),
|
||||
"plus" => Char('+'),
|
||||
"minus" | "dash" => Char('-'),
|
||||
"equal" | "equals" => Char('='),
|
||||
"underscore" => Char('_'),
|
||||
"backtick" => Char('`'),
|
||||
"lt" => Char('<'),
|
||||
"gt" => Char('>'),
|
||||
"cbopen" | "openbrace" => Char('{'),
|
||||
"cbclose" | "closebrace" => Char('}'),
|
||||
"bropen" | "openbracket" => Char('['),
|
||||
"brclose" | "closebracket" => Char(']'),
|
||||
"pgup" | "pageup" => PageUp,
|
||||
"pgdn" | "pagedown" => PageDown,
|
||||
"f1" => F(1),
|
||||
"f2" => F(2),
|
||||
"f3" => F(3),
|
||||
"f4" => F(4),
|
||||
"f5" => F(5),
|
||||
"f6" => F(6),
|
||||
"f7" => F(7),
|
||||
"f8" => F(8),
|
||||
"f9" => F(9),
|
||||
"f10" => F(10),
|
||||
"f11" => F(11),
|
||||
"f12" => F(12),
|
||||
_ => return None,
|
||||
pub const fn fill_ul (on: bool, color: Option<Color>) {
|
||||
thunk(move|to: &mut Tui|{
|
||||
if on {
|
||||
to.update(&|cell,_,_|{
|
||||
cell.modifier.insert(Modifier::UNDERLINED);
|
||||
cell.underline_color = color;
|
||||
})
|
||||
} else {
|
||||
to.update(&|cell,_,_|&|cell,_,_|{
|
||||
cell.modifier.remove(Modifier::UNDERLINED);
|
||||
cell.underline_color = Reset;
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// TUI works in u16 coordinates.
|
||||
impl Coord for u16 { fn plus (self, other: Self) -> Self { self.saturating_add(other) } }
|
||||
impl Coord for u16 {
|
||||
fn plus (self, other: Self) -> Self {
|
||||
self.saturating_add(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Draw<Buffer> for u64 {
|
||||
fn draw (&self, _to: &mut Buffer) { todo!() }
|
||||
impl Draw<Tui> for u64 {
|
||||
fn draw (&self, _to: &mut Tui) -> Usually<WH<u16>> { todo!() }
|
||||
}
|
||||
impl Draw<Buffer> for f64 {
|
||||
fn draw (&self, _to: &mut Buffer) { todo!() }
|
||||
impl Draw<Tui> for f64 {
|
||||
fn draw (&self, _to: &mut Tui) -> Usually<WH<u16>> { todo!() }
|
||||
}
|
||||
impl Draw<Buffer> for &str {
|
||||
fn draw (&self, to: &mut Buffer) {
|
||||
impl Draw<Tui> for &str {
|
||||
fn draw (&self, to: &mut Tui) -> Usually<WH<u16>> {
|
||||
let XYWH(x, y, w, ..) = to.centered_xy([width_chars_max(to.w(), self), 1]);
|
||||
to.text(&self, x, y, w)
|
||||
}
|
||||
}
|
||||
impl Draw<Buffer> for String {
|
||||
fn draw (&self, to: &mut Buffer) { self.as_str().draw(to) }
|
||||
impl Draw<Tui> for String {
|
||||
fn draw (&self, to: &mut Tui) -> Usually<WH<u16>> {
|
||||
self.as_str().draw(to)
|
||||
}
|
||||
}
|
||||
impl Draw<Tui> for Arc<str> {
|
||||
fn draw (&self, to: &mut Tui) -> Usually<WH<u16>> {
|
||||
self.as_ref().draw(to)
|
||||
}
|
||||
impl Draw<Buffer> for Arc<str> {
|
||||
fn draw (&self, to: &mut Buffer) { self.as_ref().draw(to) }
|
||||
}
|
||||
|
||||
mod phat {
|
||||
|
|
@ -158,12 +149,12 @@ mod phat {
|
|||
pub const LO: &'static str = "▄";
|
||||
pub const HI: &'static str = "▀";
|
||||
/// A phat line
|
||||
pub fn lo (fg: Color, bg: Color) -> impl Draw<Buffer> {
|
||||
H::fixed(1, Tui::fg_bg(fg, bg, X::repeat(self::phat::LO)))
|
||||
pub fn lo (fg: Color, bg: Color) -> impl Draw<Tui> {
|
||||
H::exact(1, fg_bg(fg, bg, x_repeat(self::phat::LO)))
|
||||
}
|
||||
/// A phat line
|
||||
pub fn hi (fg: Color, bg: Color) -> impl Draw<Buffer> {
|
||||
H::fixed(1, Tui::fg_bg(fg, bg, X::repeat(self::phat::HI)))
|
||||
pub fn hi (fg: Color, bg: Color) -> impl Draw<Tui> {
|
||||
H::exact(1, fg_bg(fg, bg, x_repeat(self::phat::HI)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,66 +165,71 @@ mod scroll {
|
|||
pub const ICON_INC_H: &[char] = &[' ', '🞂', ' '];
|
||||
}
|
||||
|
||||
fn x_repeat (c: char) {
|
||||
move|to: &mut Buffer|{
|
||||
let XYWH(x, y, w, h) = to.area();
|
||||
pub const fn x_repeat (c: &str) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|{
|
||||
let XYWH(x, y, w, h) = to.xywh();
|
||||
for x in x..x+w {
|
||||
if let Some(cell) = to.cell_mut(Position::from((x, y))) {
|
||||
if let Some(cell) = to.0.cell_mut(Position::from((x, y))) {
|
||||
cell.set_symbol(&c);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(WH(w, 1))
|
||||
})
|
||||
}
|
||||
|
||||
fn y_repeat (c: char) {
|
||||
move|to: &mut Buffer|{
|
||||
let XYWH(x, y, w, h) = to.area();
|
||||
pub const fn y_repeat (c: &str) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|{
|
||||
let XYWH(x, y, w, h) = to.xywh();
|
||||
for y in y..y+h {
|
||||
if let Some(cell) = to.cell_mut(Position::from((x, y))) {
|
||||
if let Some(cell) = to.0.cell_mut(Position::from((x, y))) {
|
||||
cell.set_symbol(&c);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(WH(1, h))
|
||||
})
|
||||
}
|
||||
|
||||
fn xy_repeat (c: char) {
|
||||
move|to: &mut Buffer|{
|
||||
let XYWH(x, y, w, h) = to.area();
|
||||
pub const fn xy_repeat (c: &str) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|{
|
||||
let XYWH(x, y, w, h) = to.xywh();
|
||||
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.cell_mut(Position::from((x, y))) {
|
||||
if let Some(cell) = to.0.cell_mut(Position::from((x, y))) {
|
||||
let u = u % a;
|
||||
cell.set_symbol(&c[u..u+1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(WH(w, h))
|
||||
})
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// let _ = tengri::button_2("", "", true);
|
||||
/// let _ = tengri::button_2("", "", false);
|
||||
/// ```
|
||||
pub fn button_2 <'a> (key: impl Draw<Buffer>, label: impl Draw<Buffer>, editing: bool) -> impl Draw<Buffer> {
|
||||
Tui::bold(true, bsp_e(
|
||||
Tui::fg_bg(Tui::orange(), Tui::g(0), bsp_e(Tui::fg(Tui::g(0), &"▐"), bsp_e(key, Tui::fg(Tui::g(96), &"▐")))),
|
||||
when(!editing, Tui::fg_bg(Tui::g(255), Tui::g(96), label))))
|
||||
pub const fn button_2 <'a> (key: impl Draw<Tui>, label: impl Draw<Tui>, hide: bool) -> impl Draw<Tui> {
|
||||
let c1 = tui_orange();
|
||||
let c2 = tui_g(0);
|
||||
let c3 = tui_g(96);
|
||||
let c4 = tui_g(255);
|
||||
bold(true, fg_bg(c1, c2, east(fg(c2, east(key, fg(c3, &"▐"))), when(!hide, fg_bg(c4, c3, label)))))
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// let _ = tengri::button_3("", "", "", true);
|
||||
/// let _ = tengri::button_3("", "", "", false);
|
||||
/// ```
|
||||
pub fn button_3 <'a> (
|
||||
key: impl Draw<Buffer>, label: impl Draw<Buffer>, value: impl Draw<Buffer>, editing: bool,
|
||||
) -> impl Draw<Buffer> {
|
||||
Tui::bold(true, bsp_e(
|
||||
Tui::fg_bg(Tui::orange(), Tui::g(0),
|
||||
bsp_e(Tui::fg(Tui::g(0), &"▐"), bsp_e(key, Tui::fg(if editing { Tui::g(128) } else { Tui::g(96) }, "▐")))),
|
||||
bsp_e(
|
||||
when(!editing, bsp_e(Tui::fg_bg(Tui::g(255), Tui::g(96), label), Tui::fg_bg(Tui::g(128), Tui::g(96), &"▐"),)),
|
||||
bsp_e(Tui::fg_bg(Tui::g(224), Tui::g(128), value), Tui::fg_bg(Tui::g(128), Reset, &"▌"), ))))
|
||||
pub const fn button_3 <'a> (
|
||||
key: impl Draw<Tui>, label: impl Draw<Tui>, value: impl Draw<Tui>, editing: bool,
|
||||
) -> impl Draw<Tui> {
|
||||
bold(true, east(
|
||||
fg_bg(tui_orange(), tui_g(0),
|
||||
east(fg(tui_g(0), &"▐"), east(key, fg(if editing { tui_g(128) } else { tui_g(96) }, "▐")))),
|
||||
east(
|
||||
when(!editing, east(fg_bg(tui_g(255), tui_g(96), label), fg_bg(tui_g(128), tui_g(96), &"▐"),)),
|
||||
east(fg_bg(tui_g(224), tui_g(128), value), fg_bg(tui_g(128), Reset, &"▌"), ))))
|
||||
}
|
||||
|
||||
macro_rules! border {
|
||||
|
|
@ -255,9 +251,9 @@ macro_rules! border {
|
|||
}
|
||||
#[derive(Copy, Clone)] pub struct $T(pub bool, pub Style);
|
||||
//impl Layout<Tui> for $T {}
|
||||
impl Draw<Buffer> for $T {
|
||||
fn draw (&self, to: &mut Buffer) {
|
||||
if self.enabled() { let _ = BorderStyle::draw(self, to); }
|
||||
impl Draw<Tui> for $T {
|
||||
fn draw (&self, to: &mut Tui) -> Usually<WH<u16>> {
|
||||
when(self.enabled(), |to: &mut Tui|BorderStyle::draw(self, to)).draw(to)
|
||||
}
|
||||
}
|
||||
)+}
|
||||
|
|
@ -369,87 +365,7 @@ border! {
|
|||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
pub trait BorderStyle: Draw<Tui> + Copy {
|
||||
fn enabled (&self) -> bool;
|
||||
fn border_n (&self) -> &str { Self::N }
|
||||
fn border_s (&self) -> &str { Self::S }
|
||||
|
|
@ -460,17 +376,17 @@ pub trait BorderStyle: Draw<Buffer> + Copy {
|
|||
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(XY::fill(border(self.enabled(), self)), w)
|
||||
fn enclose (self, w: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
below(WH::fill(border(self.enabled(), self)), w)
|
||||
}
|
||||
fn enclose2 (self, w: impl Draw<Buffer>) -> impl Draw<Buffer> {
|
||||
bsp_b(XY::pad(1, 1, XY::fill(border(self.enabled(), self))), w)
|
||||
fn enclose2 (self, w: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
below(WH::pad(1, 1, WH::fill(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(XY::fill(border(self.enabled(), self)), w))
|
||||
fn enclose_bg (self, w: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
Tui::bg(self.style().unwrap().bg.unwrap_or(Color::Reset),
|
||||
below(WH::fill(border(self.enabled(), self)), w))
|
||||
}
|
||||
#[inline] fn draw <'a> (&self, to: &mut impl TuiOut) -> Usually<()> {
|
||||
#[inline] fn draw <'a> (&self, to: &mut impl Draw<Tui>) -> Usually<()> {
|
||||
if self.enabled() {
|
||||
self.draw_h(to, None)?;
|
||||
self.draw_v(to, None)?;
|
||||
|
|
@ -478,7 +394,7 @@ pub trait BorderStyle: Draw<Buffer> + Copy {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
#[inline] fn draw_h (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl Area<u16>> {
|
||||
#[inline] fn draw_h (&self, to: &mut impl Draw<Tui>, style: Option<Style>) -> Usually<XYWH<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_horizontal());
|
||||
let [x, x2, y, y2] = area.lrtb();
|
||||
|
|
@ -488,7 +404,7 @@ pub trait BorderStyle: Draw<Buffer> + Copy {
|
|||
}
|
||||
Ok(area)
|
||||
}
|
||||
#[inline] fn draw_v (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl Area<u16>> {
|
||||
#[inline] fn draw_v (&self, to: &mut impl Draw<Tui>, style: Option<Style>) -> Usually<XYWH<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_vertical());
|
||||
let [x, x2, y, y2] = area.lrtb();
|
||||
|
|
@ -504,7 +420,7 @@ pub trait BorderStyle: Draw<Buffer> + Copy {
|
|||
}
|
||||
Ok(area)
|
||||
}
|
||||
#[inline] fn draw_c (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl Area<u16>> {
|
||||
#[inline] fn draw_c (&self, to: &mut impl Draw<Tui>, style: Option<Style>) -> Usually<XYWH<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_corners());
|
||||
let XYWH(x, y, w, h) = area;
|
||||
|
|
@ -541,15 +457,15 @@ pub trait BorderStyle: Draw<Buffer> + Copy {
|
|||
/// ```
|
||||
/// /// TODO
|
||||
/// ```
|
||||
pub fn phat <T, N: Coord> (w: N, h: N, [fg, bg, hi, lo]: [Color;4], draw: impl Tui) -> impl Tui {
|
||||
pub fn phat <T, N: Coord> (w: N, h: N, [fg, bg, hi, lo]: [Color;4], draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
let top = W::exact(1, self::phat::lo(bg, hi));
|
||||
let low = W::exact(1, self::phat::hi(bg, lo));
|
||||
let draw = Tui::fg_bg(fg, bg, draw);
|
||||
WH::min(w, h, bsp_s(top, bsp_n(low, WH::fill(draw))))
|
||||
let draw = fg_bg(fg, bg, draw);
|
||||
WH::min(w, h, south(top, north(low, WH::fill(draw))))
|
||||
}
|
||||
|
||||
fn x_scroll () {
|
||||
|to: &mut Buffer|{
|
||||
|to: &mut Tui|{
|
||||
let XYWH(x1, y1, w, h) = to.area();
|
||||
let mut buf = to.buffer.write().unwrap();
|
||||
let x2 = x1 + w;
|
||||
|
|
@ -578,7 +494,7 @@ fn x_scroll () {
|
|||
}
|
||||
|
||||
fn y_scroll () {
|
||||
|to: &mut Buffer|{
|
||||
|to: &mut Tui|{
|
||||
let XYWH(x1, y1, w, h) = to.area();
|
||||
let mut buf = to.buffer.write().unwrap();
|
||||
let y2 = y1 + h;
|
||||
|
|
@ -607,7 +523,7 @@ fn y_scroll () {
|
|||
}
|
||||
|
||||
/// Spawn the TUI output thread which writes colored characters to the terminal.
|
||||
pub fn tui_output <W: Write, T: Draw<Buffer> + Send + Sync + 'static> (
|
||||
pub fn tui_output <W: Write, T: Draw<Tui> + Send + Sync + 'static> (
|
||||
output: W,
|
||||
exited: &Arc<AtomicBool>,
|
||||
state: &Arc<RwLock<T>>,
|
||||
|
|
@ -645,7 +561,7 @@ pub fn tui_setup (output: &mut impl Write) -> Usually<()> {
|
|||
|
||||
pub fn tui_resize <W: Write> (
|
||||
backend: &mut CrosstermBackend<W>,
|
||||
buffer: &mut Buffer,
|
||||
buffer: &mut Tui,
|
||||
size: WH<u16>
|
||||
) {
|
||||
if buffer.area != size {
|
||||
|
|
@ -675,14 +591,14 @@ pub fn tui_teardown <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()
|
|||
}
|
||||
|
||||
pub fn tui_update (
|
||||
buf: &mut Buffer, area: XYWH<u16>, callback: &impl Fn(&mut Cell, u16, u16)
|
||||
buf: &mut Tui, 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() {
|
||||
for row in 0..area.h().0 {
|
||||
let y = area.y().0 + row;
|
||||
for col in 0..area.w().0 {
|
||||
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 }) {
|
||||
if let Some(cell) = buf.cell_mut(Position { x, y }) {
|
||||
callback(cell, col, row);
|
||||
}
|
||||
}
|
||||
|
|
@ -695,37 +611,14 @@ pub(crate) fn tui_wh <W: Write> (backend: &mut CrosstermBackend<W>) -> WH<u16> {
|
|||
WH(width, height)
|
||||
}
|
||||
|
||||
/// Draw contents with foreground color applied.
|
||||
pub const 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 const 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 const fn modify (enabled: bool, modifier: Modifier, item: impl Tui) -> impl Tui {
|
||||
|to: &mut Buffer|item.draw(to.with_modifier(enabled, modifier, item))
|
||||
}
|
||||
|
||||
pub const fn bold (enabled: bool, item: impl Tui) -> impl Tui {
|
||||
modify(enabled, Modifier::BOLD, item)
|
||||
}
|
||||
|
||||
/// Draw contents with style applied.
|
||||
pub const fn styled (enabled: bool, style: Style, item: impl Tui) -> impl Tui {
|
||||
|to: &mut Buffer|item.draw(to.with_style(enabled, style, item))
|
||||
}
|
||||
|
||||
/// Draw border around shrinked item.
|
||||
///
|
||||
/// ```
|
||||
/// /// TODO
|
||||
/// ```
|
||||
pub const fn border <T, S: BorderStyle> (on: bool, style: S, draw: impl Tui) -> impl Tui {
|
||||
WH::fill(bsp_a(when(on, |to: &mut Area|{
|
||||
pub const fn border <T, S: BorderStyle> (on: bool, style: S, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
let content = pad(Some(1), Some(1), draw);
|
||||
let outline = when(on, |to: &mut Tui|{
|
||||
let area = to.area();
|
||||
if area.w() > 0 && area.y() > 0 {
|
||||
to.blit(&style.border_nw(), area.x(), area.y(), style.style());
|
||||
|
|
@ -741,7 +634,8 @@ pub const fn border <T, S: BorderStyle> (on: bool, style: S, draw: impl Tui) ->
|
|||
to.blit(&style.border_e(), area.x() + area.w() - 1, y, style.style());
|
||||
}
|
||||
}
|
||||
}), pad(Some(1), Some(1), draw)))
|
||||
});
|
||||
Above.split(outline, content)
|
||||
}
|
||||
|
||||
/// Draw TUI content or its error message.
|
||||
|
|
@ -751,18 +645,18 @@ pub const fn border <T, S: BorderStyle> (on: bool, style: S, draw: impl Tui) ->
|
|||
/// 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() {
|
||||
pub fn catcher <T, E> (error: Usually<E>, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|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)
|
||||
}
|
||||
let title = east(bold(true, "upsi daisy. "), "rendering failed.");
|
||||
let error = east("\"why?\" ", bold(true, format!("{e}")));
|
||||
&fg(err_fg, bg(err_bg, south(title, error)))(to)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// TUI buffer sized by `usize` instead of `u16`.
|
||||
|
|
@ -790,3 +684,141 @@ impl BigBuffer {
|
|||
y * self.width + x
|
||||
}
|
||||
}
|
||||
|
||||
use self::colors::*; mod colors {
|
||||
use ratatui::prelude::Color;
|
||||
pub const fn tui_color_bg () -> Color { Color::Rgb(28, 35, 25) }
|
||||
pub const fn tui_bg0 () -> Color { Color::Rgb(20, 20, 20) }
|
||||
pub const fn tui_bo1 () -> Color { Color::Rgb(100, 110, 40) }
|
||||
pub const fn tui_bo2 () -> Color { Color::Rgb(70, 80, 50) }
|
||||
pub const fn tui_border_bg () -> Color { Color::Rgb(40, 50, 30) }
|
||||
pub const fn tui_border_fg (f: bool) -> Color { if f { tui_bo1() } else { tui_bo2() } }
|
||||
pub const fn tui_brown () -> Color { Color::Rgb(128,255,0) }
|
||||
pub const fn tui_electric () -> Color { Color::Rgb(0,255,128) }
|
||||
pub const fn tui_g (g: u8) -> Color { Color::Rgb(g, g, g) }
|
||||
pub const fn tui_green () -> Color { Color::Rgb(0,255,0) }
|
||||
pub const fn tui_mode_bg () -> Color { Color::Rgb(150, 160, 90) }
|
||||
pub const fn tui_mode_fg () -> Color { Color::Rgb(255, 255, 255) }
|
||||
pub const fn tui_null () -> Color { Color::Reset }
|
||||
pub const fn tui_orange () -> Color { Color::Rgb(255,128,0) }
|
||||
pub const fn tui_red () -> Color { Color::Rgb(255,0, 0) }
|
||||
pub const fn tui_separator_fg (_: bool) -> Color { Color::Rgb(0, 0, 0) }
|
||||
pub const fn tui_status_bar_bg () -> Color { Color::Rgb(28, 35, 25) }
|
||||
pub const fn tui_ti1 () -> Color { Color::Rgb(150, 160, 90) }
|
||||
pub const fn tui_ti2 () -> Color { Color::Rgb(120, 130, 100) }
|
||||
pub const fn tui_title_fg (f: bool) -> Color { if f { tui_ti1() } else { tui_ti2() } }
|
||||
pub const fn tui_yellow () -> Color { Color::Rgb(255,255,0) }
|
||||
}
|
||||
|
||||
/// Spawn the TUI input thread which reads keys from the terminal.
|
||||
pub fn tui_input <T: Do<TuiEvent, T> + Send + Sync + 'static> (
|
||||
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
|
||||
) -> Result<Thread, std::io::Error> {
|
||||
let exited = exited.clone();
|
||||
let state = state.clone();
|
||||
Thread::new_poll(exited.clone(), poll, move |_| {
|
||||
let event = read().unwrap();
|
||||
match event {
|
||||
|
||||
// Hardcoded exit.
|
||||
Event::Key(KeyEvent {
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
code: KeyCode::Char('c'),
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE
|
||||
}) => { exited.store(true, Relaxed); },
|
||||
|
||||
// Handle all other events by the state:
|
||||
_ => {
|
||||
let event = TuiEvent::from_crossterm(event);
|
||||
if let Err(e) = state.write().unwrap().handle(&event) {
|
||||
panic!("{e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// TUI input loop event.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
||||
pub struct TuiEvent(pub Event);
|
||||
|
||||
impl_from!(TuiEvent: |e: Event| TuiEvent(e));
|
||||
impl_from!(TuiEvent: |c: char| TuiEvent(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE))));
|
||||
|
||||
impl TuiEvent {
|
||||
#[cfg(feature = "lang")]
|
||||
pub fn from_dsl (dsl: impl Language) -> Usually<Self> {
|
||||
Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for TuiEvent {
|
||||
fn cmp (&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.partial_cmp(other)
|
||||
.unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf
|
||||
}
|
||||
}
|
||||
|
||||
/// TUI key spec.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
||||
pub struct TuiKey(pub Option<KeyCode>, pub KeyModifiers);
|
||||
|
||||
impl TuiKey {
|
||||
|
||||
const SPLIT: char = '/';
|
||||
|
||||
pub fn to_crossterm (&self) -> Option<Event> {
|
||||
self.0.map(|code|Event::Key(KeyEvent {
|
||||
code,
|
||||
modifiers: self.1,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn named (token: &str) -> Option<KeyCode> {
|
||||
use KeyCode::*;
|
||||
Some(match token {
|
||||
"up" => Up,
|
||||
"down" => Down,
|
||||
"left" => Left,
|
||||
"right" => Right,
|
||||
"esc" | "escape" => Esc,
|
||||
"enter" | "return" => Enter,
|
||||
"delete" | "del" => Delete,
|
||||
"backspace" => Backspace,
|
||||
"tab" => Tab,
|
||||
"space" => Char(' '),
|
||||
"comma" => Char(','),
|
||||
"period" => Char('.'),
|
||||
"plus" => Char('+'),
|
||||
"minus" | "dash" => Char('-'),
|
||||
"equal" | "equals" => Char('='),
|
||||
"underscore" => Char('_'),
|
||||
"backtick" => Char('`'),
|
||||
"lt" => Char('<'),
|
||||
"gt" => Char('>'),
|
||||
"cbopen" | "openbrace" => Char('{'),
|
||||
"cbclose" | "closebrace" => Char('}'),
|
||||
"bropen" | "openbracket" => Char('['),
|
||||
"brclose" | "closebracket" => Char(']'),
|
||||
"pgup" | "pageup" => PageUp,
|
||||
"pgdn" | "pagedown" => PageDown,
|
||||
"f1" => F(1),
|
||||
"f2" => F(2),
|
||||
"f3" => F(3),
|
||||
"f4" => F(4),
|
||||
"f5" => F(5),
|
||||
"f6" => F(6),
|
||||
"f7" => F(7),
|
||||
"f8" => F(8),
|
||||
"f9" => F(9),
|
||||
"f10" => F(10),
|
||||
"f11" => F(11),
|
||||
"f12" => F(12),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
63
src/text.rs
63
src/text.rs
|
|
@ -1,4 +1,4 @@
|
|||
use crate::draw::Draw;
|
||||
use crate::{Usually, draw::{Draw, WH}};
|
||||
pub(crate) use ::unicode_width::*;
|
||||
|
||||
/// Displays an owned [str]-like with fixed maximum width.
|
||||
|
|
@ -46,37 +46,60 @@ pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
|
|||
#[cfg(feature = "term")] mod impl_term {
|
||||
use super::*;
|
||||
use crate::draw::XYWH;
|
||||
use ratatui::prelude::{Buffer, Position};
|
||||
impl<'a, T> Draw<Buffer> for TrimStringRef<'a, T> {
|
||||
fn draw (&self, to: &mut Buffer) {
|
||||
let XYWH(x, y, w, ..) = XYWH(to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h());
|
||||
to.text(&self, x, y, w)
|
||||
use crate::term::Tui;
|
||||
use ratatui::prelude::Position;
|
||||
|
||||
impl Draw<Tui> for str {
|
||||
fn draw (&self, to: &mut Tui) -> Usually<WH<u16>> { todo!() }
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> Draw<Tui> for TrimString<T> {
|
||||
fn draw (&self, to: &mut Tui) -> Usually<WH<u16>> { self.as_ref().draw(to) }
|
||||
}
|
||||
impl<T> Draw<Buffer> for TrimString<T> {
|
||||
fn draw (&self, to: &mut Buffer) { self.as_ref().draw(to) }
|
||||
}
|
||||
impl<'a, T: AsRef<str>> Draw<Buffer> for TrimString<T> {
|
||||
fn draw (&self, to: &mut Buffer) { Draw::draw(&self.as_ref(), to) }
|
||||
}
|
||||
impl<T: AsRef<str>> Draw<Buffer> for TrimStringRef<'_, T> {
|
||||
fn draw (&self, target: &mut Buffer) {
|
||||
let area = target.area();
|
||||
let mut buf = target.buffer.write().unwrap();
|
||||
|
||||
impl<T: AsRef<str>> Draw<Tui> for TrimStringRef<'_, T> {
|
||||
fn draw (&self, to: &mut Tui) -> Usually<WH<u16>> {
|
||||
let area = to.area();
|
||||
let mut width: u16 = 1;
|
||||
let mut chars = self.1.as_ref().chars();
|
||||
while let Some(c) = chars.next() {
|
||||
if width > self.0 || width > area.w() {
|
||||
break
|
||||
}
|
||||
if let Some(cell) = buf.cell_mut(Position {
|
||||
x: area.x() + width - 1,
|
||||
y: area.y()
|
||||
}) {
|
||||
let x = area.x() + width - 1;
|
||||
let y = area.y();
|
||||
let pos = Position { x, y };
|
||||
if let Some(cell) = to.cell_mut(pos) {
|
||||
cell.set_char(c);
|
||||
}
|
||||
width += c.width().unwrap_or(0) as u16;
|
||||
}
|
||||
let XYWH(x, y, w, ..) = XYWH(to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h());
|
||||
to.text(&self, x, y, w)
|
||||
}
|
||||
}
|
||||
|
||||
impl Tui {
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue