mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-08-07 22:17:07 +02:00
more entrypoint macros
This commit is contained in:
parent
4cfe8d087c
commit
66ac2bcbb6
18 changed files with 696 additions and 663 deletions
28
Cargo.lock
generated
28
Cargo.lock
generated
|
|
@ -527,6 +527,7 @@ dependencies = [
|
||||||
"dizzle_proc",
|
"dizzle_proc",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"konst",
|
"konst",
|
||||||
|
"peg",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1390,6 +1391,33 @@ version = "1.0.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "peg"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0aad070be5b63aa72103f2fcdd70a83adbd5e90112ce5b574171ff1c65501773"
|
||||||
|
dependencies = [
|
||||||
|
"peg-macros",
|
||||||
|
"peg-runtime",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "peg-macros"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ddd8ef6825cae95355031ae26a99b616a2a21f22ba2de0197c43dfb05acbe7ee"
|
||||||
|
dependencies = [
|
||||||
|
"peg-runtime",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "peg-runtime"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7011d97b484a5ebdc4b1fdb3b12d5e4bbbea56e9d22b688f2e79e04b65a7d8a6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
|
|
|
||||||
7
Justfile
7
Justfile
|
|
@ -30,7 +30,10 @@ doc:
|
||||||
cargo doc
|
cargo doc
|
||||||
|
|
||||||
example-tui-00:
|
example-tui-00:
|
||||||
cargo run --example tui_00
|
cargo run --example mode_0
|
||||||
|
|
||||||
example-tui-01:
|
example-tui-01:
|
||||||
cargo run --example tui_01
|
cargo run --example mode_1
|
||||||
|
|
||||||
|
example-tui-02:
|
||||||
|
cargo run --example mode_2
|
||||||
|
|
|
||||||
2
dizzle
2
dizzle
|
|
@ -1 +1 @@
|
||||||
Subproject commit e9768535f35cd298a7b1035825e86cd13347792d
|
Subproject commit e4e01c025befd4fd7ed217fa5cc492373f5d7e3b
|
||||||
3
examples/mode_00.rs
Normal file
3
examples/mode_00.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
//! Mode 0: Direct draw
|
||||||
|
use ::{std::sync::{Arc, RwLock}, ratatui::style::Color, tengri::*};
|
||||||
|
fn main () {}
|
||||||
128
examples/mode_01.rs
Normal file
128
examples/mode_01.rs
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
//! Mode 01
|
||||||
|
use ::std::sync::{Arc, RwLock};
|
||||||
|
use ::crossterm::event::{Event::*, KeyEvent, KeyCode::*};
|
||||||
|
use ::ratatui::style::Color;
|
||||||
|
use ::tengri::{*, lang::*};
|
||||||
|
tui_app!(State {
|
||||||
|
/** Command history (undo/redo). */
|
||||||
|
history: Vec<Action>,
|
||||||
|
/** User-controllable value. */
|
||||||
|
cursor: usize,
|
||||||
|
/** Rendered window size. */
|
||||||
|
size: Sizer,
|
||||||
|
});
|
||||||
|
tui_keys!(self: State, input {
|
||||||
|
Ok(if let Key(KeyEvent { code, .. }) = input.0 {
|
||||||
|
match code {
|
||||||
|
Up | Right => { self.next()?.map(|x|self.history.push(x)); },
|
||||||
|
Down | Left => { self.prev()?.map(|x|self.history.push(x)); },
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
tui_view!(self: State {
|
||||||
|
let index = self.cursor + 1;
|
||||||
|
let wh = (self.size.w(), self.size.h());
|
||||||
|
let src = VIEWS.get(self.cursor).unwrap_or(&"");
|
||||||
|
let heading = format!("State {}/{} in {:?}", index, VIEWS.len(), &wh);
|
||||||
|
let title = bg(Color::Rgb(60, 10, 10), heading.align_n().push_y(1));
|
||||||
|
let code = bg(Color::Rgb(10, 60, 10), format!("{}", src).align_n().push_y(2));
|
||||||
|
let widget = thunk(move|to: &mut Tui|self.interpret(to, &src));
|
||||||
|
self.size.of(south(title, north(code, widget)))
|
||||||
|
});
|
||||||
|
tui_ns!(self: State, to, src {
|
||||||
|
match src.src()? {
|
||||||
|
Some(":foo") => "foo".draw(to),
|
||||||
|
Some(":bar") => "bar".draw(to),
|
||||||
|
Some(":foobar") => "FOOBAR".draw(to),
|
||||||
|
_ => todo!()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Action {
|
||||||
|
/** Increment cursor */ Next,
|
||||||
|
/** Decrement cursor */ Prev,
|
||||||
|
}
|
||||||
|
impl Action {
|
||||||
|
fn eval (&self, state: &mut State) -> Perhaps<Self> {
|
||||||
|
use Action::*;
|
||||||
|
match self { Next => state.next(), Prev => state.prev(), }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl State {
|
||||||
|
fn next (&mut self) -> Perhaps<Action> {
|
||||||
|
self.cursor = (self.cursor + 1) % VIEWS.len();
|
||||||
|
Ok(Some(Action::Prev))
|
||||||
|
}
|
||||||
|
fn prev (&mut self) -> Perhaps<Action> {
|
||||||
|
self.cursor = if self.cursor > 0 { self.cursor - 1 } else { VIEWS.len() - 1 };
|
||||||
|
Ok(Some(Action::Next))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const VIEWS: &'static [&'static str] = &[
|
||||||
|
stringify! { :foobar },
|
||||||
|
stringify! { (bg (g 8) :foobar) },
|
||||||
|
stringify! { (fill/xy :foobar) },
|
||||||
|
stringify! { (bsp/s :foo :bar) },
|
||||||
|
stringify! { (fixed/xy 20 10 :foobar) },
|
||||||
|
stringify! { (bsp/s (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) },
|
||||||
|
stringify! { (bsp/e (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) },
|
||||||
|
stringify! { (bsp/n (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) },
|
||||||
|
stringify! { (bsp/w (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) },
|
||||||
|
stringify! { (bsp/a (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) },
|
||||||
|
stringify! { (bsp/b (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) },
|
||||||
|
stringify! {
|
||||||
|
(bsp/s
|
||||||
|
(bsp/e (align/nw (fixed/xy 5 3 :foo))
|
||||||
|
(bsp/e (align/n (fixed/xy 5 3 :foo))
|
||||||
|
(align/ne (fixed/xy 5 3 :foo))))
|
||||||
|
(bsp/s
|
||||||
|
(bsp/e (align/w (fixed/xy 5 3 :foo))
|
||||||
|
(bsp/e (align/c (fixed/xy 5 3 :foo))
|
||||||
|
(align/e (fixed/xy 5 3 :foo))))
|
||||||
|
(bsp/e (align/sw (fixed/xy 5 3 :foo))
|
||||||
|
(bsp/e (align/s (fixed/xy 5 3 :foo))
|
||||||
|
(align/se (fixed/xy 5 3 :foo))))))
|
||||||
|
},
|
||||||
|
stringify! {
|
||||||
|
(bsp/s
|
||||||
|
(bsp/e (fixed/xy 8 5 (align/nw :foo))
|
||||||
|
(bsp/e (fixed/xy 8 5 (align/n :foo))
|
||||||
|
(fixed/xy 8 5 (align/ne :foo))))
|
||||||
|
(bsp/s
|
||||||
|
(bsp/e (fixed/xy 8 5 (align/w :foo))
|
||||||
|
(bsp/e (fixed/xy 8 5 (align/c :foo))
|
||||||
|
(fixed/xy 8 5 (align/e :foo))))
|
||||||
|
(bsp/e (fixed/xy 8 5 (align/sw :foo))
|
||||||
|
(bsp/e (fixed/xy 8 5 (align/s :foo))
|
||||||
|
(fixed/xy 8 5 (align/se :foo))))))
|
||||||
|
},
|
||||||
|
stringify! {
|
||||||
|
(bsp/s
|
||||||
|
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/nw :foo)))
|
||||||
|
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/n :foo)))
|
||||||
|
(grow/xy 1 1 (fixed/xy 8 5 (align/ne :foo)))))
|
||||||
|
(bsp/s
|
||||||
|
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/w :foo)))
|
||||||
|
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/c :foo)))
|
||||||
|
(grow/xy 1 1 (fixed/xy 8 5 (align/e :foo)))))
|
||||||
|
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/sw :foo)))
|
||||||
|
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/s :foo)))
|
||||||
|
(grow/xy 1 1 (fixed/xy 8 5 (align/se :foo)))))))
|
||||||
|
},
|
||||||
|
stringify! { :map-e },
|
||||||
|
stringify! { (align/c :map-e) },
|
||||||
|
stringify! { :map-s },
|
||||||
|
stringify! { (align/c :map-s) },
|
||||||
|
stringify! {
|
||||||
|
(align/c (bg/behind :bg0 (margin/xy 1 1 (col
|
||||||
|
(bg/behind :bg1 (border/around :border1 (margin/xy 2 1 :label1)))
|
||||||
|
(bg/behind :bg2 (border/around :border2 (margin/xy 4 2 :label2)))
|
||||||
|
(bg/behind :bg3 (border/around :border3 (margin/xy 6 3 :label3)))))))
|
||||||
|
},
|
||||||
|
];
|
||||||
|
//handle!(TuiIn: |self: State, input|Action::from(input).eval(self).map(|_|None));
|
||||||
|
//view!(State: Tui: [ evaluate_output_expression, evaluate_output_expression_tui ]);
|
||||||
|
//draw!(State: Tui: [ draw_example ]);
|
||||||
|
//impl_from!(Action: |input: &TuiIn| todo!());
|
||||||
|
//fn draw_example (state: &State, to: &mut Tui) {}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
//! Mode 02
|
||||||
use ::{std::sync::{Arc, RwLock}, ratatui::style::Color, tengri::*};
|
use ::{std::sync::{Arc, RwLock}, ratatui::style::Color, tengri::*};
|
||||||
fn main () {}
|
fn main () {}
|
||||||
|
|
||||||
|
|
@ -1,180 +0,0 @@
|
||||||
use ::{
|
|
||||||
std::{io::stdout, sync::{Arc, RwLock}},
|
|
||||||
ratatui::style::Color,
|
|
||||||
tengri::{*, lang::*},
|
|
||||||
};
|
|
||||||
|
|
||||||
tui_main!(State {
|
|
||||||
cursor: 0,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
tui_keys!(|self: State, input| {
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
tui_view!(|self: State| {
|
|
||||||
let index = self.cursor + 1;
|
|
||||||
let wh = (self.size.w(), self.size.h());
|
|
||||||
let src = VIEWS.get(self.cursor).unwrap_or(&"");
|
|
||||||
let heading = format!("State {}/{} in {:?}", index, VIEWS.len(), &wh);
|
|
||||||
let title = bg(Color::Rgb(60, 10, 10), heading.align_n().push_y(1));
|
|
||||||
let code = bg(Color::Rgb(10, 60, 10), format!("{}", src).align_n().push_y(2));
|
|
||||||
let widget = thunk(move|to: &mut Tui|self.interpret(to, &src));
|
|
||||||
self.size.of(south(title, north(code, widget)))
|
|
||||||
});
|
|
||||||
|
|
||||||
#[dizzle::namespace(bool)]
|
|
||||||
#[dizzle::namespace(u16)]
|
|
||||||
#[dizzle::namespace(Option<u16>)]
|
|
||||||
#[dizzle::namespace(Color)]
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct State {
|
|
||||||
/** Command history (undo/redo). */
|
|
||||||
history: Vec<Action>,
|
|
||||||
/** User-controllable value. */
|
|
||||||
cursor: usize,
|
|
||||||
/** Rendered window size. */
|
|
||||||
size: Sizer,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Interpret<Tui, Option<XYWH<u16>>> for State {
|
|
||||||
fn interpret_word <'a> (&'a self, to: &mut Tui, lang: &'a impl Symbol) -> Drawn<u16> {
|
|
||||||
match lang.src()? {
|
|
||||||
Some(":hello") => "hello".draw(to),
|
|
||||||
Some(":world") => "world".draw(to),
|
|
||||||
Some(":hello-world") => "Hello World!".draw(to),
|
|
||||||
_ => todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn interpret_expr <'a> (&'a self, to: &mut Tui, lang: &'a impl Expression) -> Drawn<u16> {
|
|
||||||
Ok(Some(if let Some(area) = eval_view(self, to, lang)? {
|
|
||||||
area
|
|
||||||
} else if let Some(area) = eval_view_tui(self, to, lang)? {
|
|
||||||
area
|
|
||||||
} else {
|
|
||||||
return Err(format!("App::interpret_expr: unexpected: {lang:?}").into())
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)] enum Action {
|
|
||||||
/** Increment cursor */
|
|
||||||
Next,
|
|
||||||
/** Decrement cursor */
|
|
||||||
Prev
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Action {
|
|
||||||
|
|
||||||
const BINDS: &'static str = stringify! {
|
|
||||||
(@left prev)
|
|
||||||
(@right next)
|
|
||||||
};
|
|
||||||
|
|
||||||
fn eval (&self, state: &mut State) -> Perhaps<Self> {
|
|
||||||
use Action::*;
|
|
||||||
match self { Next => Self::next(state), Prev => Self::prev(state), }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next (state: &mut State) -> Perhaps<Self> {
|
|
||||||
state.cursor = (state.cursor + 1) % VIEWS.len();
|
|
||||||
Ok(Some(Self::Prev))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prev (state: &mut State) -> Perhaps<Self> {
|
|
||||||
state.cursor = if state.cursor > 0 { state.cursor - 1 } else { VIEWS.len() - 1 };
|
|
||||||
Ok(Some(Self::Next))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const VIEWS: &'static [&'static str] = &[
|
|
||||||
|
|
||||||
stringify! { :hello-world },
|
|
||||||
|
|
||||||
stringify! { (fill/xy :hello-world) },
|
|
||||||
|
|
||||||
stringify! { (bsp/s :hello :world) },
|
|
||||||
|
|
||||||
stringify! { (fixed/xy 20 10 :hello-world) },
|
|
||||||
|
|
||||||
stringify! { (bsp/s (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world)) },
|
|
||||||
|
|
||||||
stringify! { (bsp/e (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world)) },
|
|
||||||
|
|
||||||
stringify! { (bsp/n (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world)) },
|
|
||||||
|
|
||||||
stringify! { (bsp/w (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world)) },
|
|
||||||
|
|
||||||
stringify! { (bsp/a (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world)) },
|
|
||||||
|
|
||||||
stringify! { (bsp/b (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world)) },
|
|
||||||
|
|
||||||
stringify! {
|
|
||||||
(bsp/s
|
|
||||||
(bsp/e (align/nw (fixed/xy 5 3 :hello))
|
|
||||||
(bsp/e (align/n (fixed/xy 5 3 :hello))
|
|
||||||
(align/ne (fixed/xy 5 3 :hello))))
|
|
||||||
(bsp/s
|
|
||||||
(bsp/e (align/w (fixed/xy 5 3 :hello))
|
|
||||||
(bsp/e (align/c (fixed/xy 5 3 :hello))
|
|
||||||
(align/e (fixed/xy 5 3 :hello))))
|
|
||||||
(bsp/e (align/sw (fixed/xy 5 3 :hello))
|
|
||||||
(bsp/e (align/s (fixed/xy 5 3 :hello))
|
|
||||||
(align/se (fixed/xy 5 3 :hello))))))
|
|
||||||
},
|
|
||||||
|
|
||||||
stringify! {
|
|
||||||
(bsp/s
|
|
||||||
(bsp/e (fixed/xy 8 5 (align/nw :hello))
|
|
||||||
(bsp/e (fixed/xy 8 5 (align/n :hello))
|
|
||||||
(fixed/xy 8 5 (align/ne :hello))))
|
|
||||||
(bsp/s
|
|
||||||
(bsp/e (fixed/xy 8 5 (align/w :hello))
|
|
||||||
(bsp/e (fixed/xy 8 5 (align/c :hello))
|
|
||||||
(fixed/xy 8 5 (align/e :hello))))
|
|
||||||
(bsp/e (fixed/xy 8 5 (align/sw :hello))
|
|
||||||
(bsp/e (fixed/xy 8 5 (align/s :hello))
|
|
||||||
(fixed/xy 8 5 (align/se :hello))))))
|
|
||||||
},
|
|
||||||
|
|
||||||
stringify! {
|
|
||||||
(bsp/s
|
|
||||||
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/nw :hello)))
|
|
||||||
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/n :hello)))
|
|
||||||
(grow/xy 1 1 (fixed/xy 8 5 (align/ne :hello)))))
|
|
||||||
(bsp/s
|
|
||||||
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/w :hello)))
|
|
||||||
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/c :hello)))
|
|
||||||
(grow/xy 1 1 (fixed/xy 8 5 (align/e :hello)))))
|
|
||||||
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/sw :hello)))
|
|
||||||
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/s :hello)))
|
|
||||||
(grow/xy 1 1 (fixed/xy 8 5 (align/se :hello)))))))
|
|
||||||
},
|
|
||||||
|
|
||||||
stringify! { :map-e },
|
|
||||||
|
|
||||||
stringify! { (align/c :map-e) },
|
|
||||||
|
|
||||||
stringify! { :map-s },
|
|
||||||
|
|
||||||
stringify! { (align/c :map-s) },
|
|
||||||
|
|
||||||
stringify! {
|
|
||||||
(align/c (bg/behind :bg0 (margin/xy 1 1 (col
|
|
||||||
(bg/behind :bg1 (border/around :border1 (margin/xy 2 1 :label1)))
|
|
||||||
(bg/behind :bg2 (border/around :border2 (margin/xy 4 2 :label2)))
|
|
||||||
(bg/behind :bg3 (border/around :border3 (margin/xy 6 3 :label3)))))))
|
|
||||||
},
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|
||||||
//handle!(TuiIn: |self: State, input|Action::from(input).eval(self).map(|_|None));
|
|
||||||
//view!(State: Tui: [ evaluate_output_expression, evaluate_output_expression_tui ]);
|
|
||||||
//draw!(State: Tui: [ draw_example ]);
|
|
||||||
|
|
||||||
//impl_from!(Action: |input: &TuiIn| todo!());
|
|
||||||
|
|
||||||
//fn draw_example (state: &State, to: &mut Tui) {
|
|
||||||
//}
|
|
||||||
19
src/draw.rs
19
src/draw.rs
|
|
@ -7,13 +7,22 @@ use crate::*;
|
||||||
/// struct TestOut { w: u16, h: u16 };
|
/// struct TestOut { w: u16, h: u16 };
|
||||||
/// impl Wide<u16> for TestOut {}
|
/// impl Wide<u16> for TestOut {}
|
||||||
/// impl Tall<u16> for TestOut {}
|
/// impl Tall<u16> for TestOut {}
|
||||||
/// impl Xy<u16> for TestOut { fn x (&self) -> u16 { 0 } fn y (&self) -> u16 { 0 } }
|
/// impl Xy<u16> for TestOut {
|
||||||
|
/// fn x (&self) -> u16 { 0 }
|
||||||
|
/// fn y (&self) -> u16 { 0 }
|
||||||
|
/// }
|
||||||
/// impl Screen for TestOut {
|
/// impl Screen for TestOut {
|
||||||
/// type Unit = u16;
|
/// type Unit = u16;
|
||||||
/// fn show <D: Draw<Self>> (&mut self, _: D) -> Drawn<u16> {
|
/// fn show (&mut self, _: impl Draw<Self>) -> Perhaps<XYWH<u16>>
|
||||||
/// println!("placed");
|
/// { println!("placed"); Ok(None) }
|
||||||
/// Ok(None)
|
/// fn area (&self) -> XYWH<Self::Unit>
|
||||||
/// }
|
/// { Default::default() }
|
||||||
|
/// fn clip <T> (
|
||||||
|
/// &mut self,
|
||||||
|
/// area: impl Into<Option<XYWH<u16>>>,
|
||||||
|
/// draw: impl FnOnce(&mut Self)->T
|
||||||
|
/// ) -> T
|
||||||
|
/// { draw(self) }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// impl_draw!(|self: String, to: TestOut|{
|
/// impl_draw!(|self: String, to: TestOut|{
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,7 @@ pub fn eval_view_tui <'a, S> (
|
||||||
+ for<'b>Namespace<'b, u16>
|
+ for<'b>Namespace<'b, u16>
|
||||||
+ for<'b>Namespace<'b, Color>
|
+ for<'b>Namespace<'b, Color>
|
||||||
{
|
{
|
||||||
|
use crate::term::*;
|
||||||
// See `tengri::eval_view`
|
// See `tengri::eval_view`
|
||||||
let head = expr.head()?;
|
let head = expr.head()?;
|
||||||
let mut frags = head.src()?.unwrap_or_default().split("/");
|
let mut frags = head.src()?.unwrap_or_default().split("/");
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ pub(crate) use ::{
|
||||||
std::ops::{Add, Sub, Mul, Div},
|
std::ops::{Add, Sub, Mul, Div},
|
||||||
std::sync::{Arc, RwLock},
|
std::sync::{Arc, RwLock},
|
||||||
std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::*},
|
std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::*},
|
||||||
std::error::Error,
|
|
||||||
std::marker::PhantomData
|
std::marker::PhantomData
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
350
src/term.rs
350
src/term.rs
|
|
@ -1,11 +1,72 @@
|
||||||
use crate::{*, lang::*};
|
#[macro_export] macro_rules! tui_app {
|
||||||
|
($Struct:ident { $($fields:tt)* }) => {
|
||||||
|
#[dizzle::namespace(bool)]
|
||||||
|
#[dizzle::namespace(u16)]
|
||||||
|
#[dizzle::namespace(Option<u16>)]
|
||||||
|
#[dizzle::namespace(Color)]
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct $Struct { $($fields)* }
|
||||||
|
tui_main!($Struct { ..Default::default() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement standard [main] entrypoint for TUI apps.
|
||||||
|
#[macro_export] macro_rules! tui_main {
|
||||||
|
($state:expr) => {
|
||||||
|
pub fn main () -> Usually<()> {
|
||||||
|
Tui::setup_panic();
|
||||||
|
Tui::run_main(Arc::new(RwLock::new($state)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable TUI output for state struct.
|
||||||
|
#[macro_export] macro_rules! tui_view {
|
||||||
|
($self:ident: $State:ty $body:block) => {
|
||||||
|
impl View<Tui> for $State {
|
||||||
|
fn view (&$self) -> impl Draw<Tui> $body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! tui_ns {
|
||||||
|
($self:ident: $State:ident, $to:pat, $pat:ident $body:expr) => {
|
||||||
|
impl Interpret<Tui, Option<XYWH<u16>>> for $State {
|
||||||
|
fn interpret_word <'a> (&'a $self, $to: &mut Tui, $pat: &'a impl Symbol) -> Drawn<u16> {
|
||||||
|
$body
|
||||||
|
}
|
||||||
|
fn interpret_expr <'a> (&'a self, to: &mut Tui, src: &'a impl Expression) -> Drawn<u16> {
|
||||||
|
Ok(Some(if let Some(area) = eval_view(self, to, src)? {
|
||||||
|
area
|
||||||
|
} else if let Some(area) = eval_view_tui(self, to, src)? {
|
||||||
|
area
|
||||||
|
} else {
|
||||||
|
return Err(format!("App::interpret_expr: unexpected: {src:?}").into())
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
use crate::{*, lang::*};
|
||||||
mod border; pub use self::border::*;
|
mod border; pub use self::border::*;
|
||||||
mod event; pub use self::event::*;
|
mod event; pub use self::event::*;
|
||||||
mod keys; pub use self::keys::*;
|
mod keys; pub use self::keys::*;
|
||||||
mod buffer; pub use self::buffer::*;
|
mod buffer; pub use self::buffer::*;
|
||||||
mod input; pub use self::input::*;
|
mod repeat; pub use self::repeat::*;
|
||||||
mod output; pub use self::output::*;
|
mod scroll; pub use self::scroll::*;
|
||||||
|
mod colors; pub use self::colors::*;
|
||||||
|
mod phat; pub use self::phat::*;
|
||||||
|
mod button; pub use self::button::*;
|
||||||
|
|
||||||
//use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
|
//use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
|
||||||
//use rand::distributions::uniform::UniformSampler;
|
//use rand::distributions::uniform::UniformSampler;
|
||||||
|
|
@ -32,14 +93,6 @@ pub(crate) use ::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Terminal output.
|
|
||||||
pub struct Tui(
|
|
||||||
/// Ratatui buffer; area is screen size
|
|
||||||
pub Buffer,
|
|
||||||
/// Current draw area
|
|
||||||
pub XYWH<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 AsMut<Buffer> for Tui { fn as_mut (&mut self) -> &mut Buffer { &mut self.0 } }
|
impl AsMut<Buffer> for Tui { fn as_mut (&mut self) -> &mut Buffer { &mut self.0 } }
|
||||||
|
|
@ -47,8 +100,86 @@ impl Wide<u16> for Tui { fn w (&self) -> u16 { self.1.2 } }
|
||||||
impl Tall<u16> for Tui { fn h (&self) -> u16 { self.1.3 } }
|
impl Tall<u16> for Tui { fn h (&self) -> u16 { self.1.3 } }
|
||||||
impl HasOrigin for Tui { fn origin (&self) -> Azimuth { Azimuth::NW } }
|
impl HasOrigin for Tui { fn origin (&self) -> Azimuth { Azimuth::NW } }
|
||||||
impl Xy<u16> for Tui { fn x (&self) -> u16 { self.1.0 } fn y (&self) -> u16 { self.1.1 } }
|
impl Xy<u16> for Tui { fn x (&self) -> u16 { self.1.0 } fn y (&self) -> u16 { self.1.1 } }
|
||||||
|
/// Terminal output.
|
||||||
|
pub struct Tui(
|
||||||
|
/// Ratatui buffer; area is screen size
|
||||||
|
pub Buffer,
|
||||||
|
/// Current draw area
|
||||||
|
pub XYWH<u16>
|
||||||
|
);
|
||||||
impl Tui {
|
impl Tui {
|
||||||
|
pub fn setup_panic () {
|
||||||
|
use ::std::panic::{set_hook, PanicHookInfo};
|
||||||
|
use ::better_panic::{Settings, Verbosity};
|
||||||
|
let panic = Settings::auto()
|
||||||
|
.verbosity(Verbosity::Full)
|
||||||
|
.create_panic_handler();
|
||||||
|
set_hook(Box::new(move |info: &PanicHookInfo|{
|
||||||
|
let _ = Tui::teardown(&mut stdout());
|
||||||
|
panic(info);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
pub fn run_main <T> (state: Arc<RwLock<T>>) -> Usually<()> where
|
||||||
|
T: View<Tui> + Apply<TuiEvent, Usually<()>> + Send + Sync + 'static
|
||||||
|
{
|
||||||
|
Exit::run(|exit|{
|
||||||
|
let scan = Duration::from_millis(100);
|
||||||
|
let frame = Duration::from_millis(10);
|
||||||
|
let (_input, output) = Tui::io(exit.as_ref(), &state, scan, frame, std::io::stdout())?;
|
||||||
|
let _ = output.join();
|
||||||
|
Tui::teardown(&mut stdout())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Spawn the TUI input and output threadsl.
|
||||||
|
pub fn io <
|
||||||
|
T: View<Tui> + Apply<TuiEvent, Usually<()>> + Send + Sync + 'static,
|
||||||
|
W: Write + Send + Sync + 'static,
|
||||||
|
> (
|
||||||
|
exited: &Arc<AtomicBool>,
|
||||||
|
state: &Arc<RwLock<T>>,
|
||||||
|
poll: Duration,
|
||||||
|
sleep: Duration,
|
||||||
|
output: W,
|
||||||
|
) -> Result<(Task, Task), Box<dyn std::error::Error>> {
|
||||||
|
Ok((
|
||||||
|
Tui::input(exited, state, poll)?,
|
||||||
|
Tui::output(exited, state, sleep, output)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
/// Spawn the TUI input thread which reads keys from the terminal.
|
||||||
|
pub fn input <T: Apply<TuiEvent, Usually<()>> + Send + Sync + 'static> (
|
||||||
|
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
|
||||||
|
) -> Result<Task, std::io::Error> {
|
||||||
|
let exited = exited.clone();
|
||||||
|
let state = state.clone();
|
||||||
|
Task::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:
|
||||||
|
event => {
|
||||||
|
if let Err(e) = state.write().unwrap().apply(&TuiEvent(event)) {
|
||||||
|
panic!("{e}")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn teardown <W: Write> (backend: &mut W) -> Usually<()> {
|
||||||
|
use ::ratatui::backend::Backend;
|
||||||
|
stdout().execute(LeaveAlternateScreen)?;
|
||||||
|
CrosstermBackend::new(backend).show_cursor()?;
|
||||||
|
disable_raw_mode().map_err(Into::into)
|
||||||
|
}
|
||||||
pub fn new (width: u16, height: u16) -> Self {
|
pub fn new (width: u16, height: u16) -> Self {
|
||||||
Self(Buffer::empty(Rect { x: 0, y: 0, width, height }), XYWH(0, 0, width, height))
|
Self(Buffer::empty(Rect { x: 0, y: 0, width, height }), XYWH(0, 0, width, height))
|
||||||
}
|
}
|
||||||
|
|
@ -60,16 +191,17 @@ impl Tui {
|
||||||
self.0.reset();
|
self.0.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn redraw <'b, W: Write> (&'b mut self, back: &mut CrosstermBackend<W>, mut next: &'b mut Self) {
|
pub fn redraw <'b, W: Write> (
|
||||||
|
&'b mut self,
|
||||||
|
back: &mut CrosstermBackend<W>,
|
||||||
|
mut next: &'b mut Self
|
||||||
|
) {
|
||||||
let updates = self.0.diff(&next.0);
|
let updates = self.0.diff(&next.0);
|
||||||
back.draw(updates.into_iter()).expect("failed to render");
|
back.draw(updates.into_iter()).expect("failed to render");
|
||||||
Backend::flush(back).expect("failed to flush output new");
|
Backend::flush(back).expect("failed to flush output new");
|
||||||
std::mem::swap(self, &mut next);
|
std::mem::swap(self, &mut next);
|
||||||
next.0.reset();
|
next.0.reset();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
||||||
let y = self.y() + row;
|
let y = self.y() + row;
|
||||||
|
|
@ -84,13 +216,6 @@ impl Tui {
|
||||||
}
|
}
|
||||||
self.xywh()
|
self.xywh()
|
||||||
}
|
}
|
||||||
pub fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) {
|
|
||||||
for cell in self.0.content.iter_mut() {
|
|
||||||
cell.fg = fg;
|
|
||||||
cell.bg = bg;
|
|
||||||
cell.modifier = modifier;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn blit (&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>) {
|
pub fn blit (&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>) {
|
||||||
let text = text.as_ref();
|
let text = text.as_ref();
|
||||||
let style = style.unwrap_or(Style::default());
|
let style = style.unwrap_or(Style::default());
|
||||||
|
|
@ -98,61 +223,140 @@ impl Tui {
|
||||||
self.0.set_string(x, y, text, style);
|
self.0.set_string(x, y, text, style);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
pub fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) {
|
||||||
|
for cell in self.0.content.iter_mut() {
|
||||||
/// Implement standard [main] entrypoint for TUI apps.
|
cell.fg = fg;
|
||||||
#[macro_export] macro_rules! tui_main {
|
cell.bg = bg;
|
||||||
($state:expr) => {
|
cell.modifier = modifier;
|
||||||
pub fn main () -> Usually<()> {
|
|
||||||
tui_setup_panic();
|
|
||||||
tui_run_main(Arc::new(RwLock::new($state)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
/// Spawn the TUI output thread which writes colored characters to the terminal.
|
||||||
|
///
|
||||||
pub fn tui_run_main <T> (state: Arc<RwLock<T>>) -> Usually<()> where
|
/// ```
|
||||||
T: View<Tui> + Apply<TuiEvent, Usually<()>> + Send + Sync + 'static
|
/// let state = std::sync::Arc::new(std::sync::RwLock::new(()));
|
||||||
{
|
/// let _ = tengri::Exit::run(|exit|{
|
||||||
Exit::run(|exit|{
|
/// tengri::Tui::output(
|
||||||
let scan = Duration::from_millis(100);
|
/// exit.as_ref(),
|
||||||
let frame = Duration::from_millis(10);
|
/// &state,
|
||||||
let (input, output) = tui_io(exit.as_ref(), &state, scan, frame, std::io::stdout())?;
|
/// std::time::Duration::from_millis(10),
|
||||||
output.join();
|
/// std::io::stdout()
|
||||||
tui_teardown(&mut stdout())
|
/// )
|
||||||
})
|
/// });
|
||||||
}
|
/// ```
|
||||||
|
pub fn output <
|
||||||
pub fn tui_teardown <W: Write> (backend: &mut W) -> Usually<()> {
|
W: Write + Send + Sync + 'static, T: View<Tui> + Send + Sync + 'static
|
||||||
use ::ratatui::backend::Backend;
|
|
||||||
stdout().execute(LeaveAlternateScreen)?;
|
|
||||||
CrosstermBackend::new(backend).show_cursor()?;
|
|
||||||
disable_raw_mode().map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tui_setup_panic () {
|
|
||||||
use ::std::panic::{set_hook, PanicHookInfo};
|
|
||||||
use ::better_panic::{Settings, Verbosity};
|
|
||||||
let panic = Settings::auto()
|
|
||||||
.verbosity(Verbosity::Full)
|
|
||||||
.create_panic_handler();
|
|
||||||
set_hook(Box::new(move |info: &PanicHookInfo|{
|
|
||||||
let _ = tui_teardown(&mut stdout());
|
|
||||||
panic(info);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Spawn the TUI input and output threadsl.
|
|
||||||
pub fn tui_io <
|
|
||||||
T: View<Tui> + Apply<TuiEvent, Usually<()>> + Send + Sync + 'static,
|
|
||||||
W: Write + Send + Sync + 'static,
|
|
||||||
> (
|
> (
|
||||||
exited: &Arc<AtomicBool>,
|
exited: &Arc<AtomicBool>,
|
||||||
state: &Arc<RwLock<T>>,
|
state: &Arc<RwLock<T>>,
|
||||||
poll: Duration,
|
|
||||||
sleep: Duration,
|
sleep: Duration,
|
||||||
output: W,
|
output: W,
|
||||||
) -> Result<(Task, Task), Box<dyn Error>> {
|
) -> Usually<Task> {
|
||||||
let keyboard = tui_input(exited, state, poll)?;
|
let state = state.clone();
|
||||||
let terminal = tui_output(exited, state, sleep, output)?;
|
stdout().execute(EnterAlternateScreen)?;
|
||||||
Ok((keyboard, terminal))
|
CrosstermBackend::new(stdout()).hide_cursor()?;
|
||||||
|
enable_raw_mode()?;
|
||||||
|
let mut backend = CrosstermBackend::new(output);
|
||||||
|
let Size { width, height } = backend.size().expect("get size failed");
|
||||||
|
let mut prev = Tui::new(width, height);
|
||||||
|
let mut next = Tui::new(width, height);
|
||||||
|
Ok(Task::new_sleep(exited.clone(), sleep, move |perf| {
|
||||||
|
let Size { width, height } = backend.size().expect("get size failed");
|
||||||
|
if let Ok(state) = state.try_read() {
|
||||||
|
prev.resize(&mut backend, width, height);
|
||||||
|
state.view().draw(&mut next).expect("draw failed"); // TODO draw error
|
||||||
|
prev.redraw(&mut backend, &mut next);
|
||||||
|
}
|
||||||
|
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
|
||||||
|
prev.set_string(0, 0, &timer, Style::default());
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
/// Draw TUI content or its error message.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// for variant in [
|
||||||
|
/// Ok(Some("hello")),
|
||||||
|
/// Ok(None),
|
||||||
|
/// Err("fail".into()),
|
||||||
|
/// ] {
|
||||||
|
/// let _ = tengri::Tui::catcher(variant);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn catcher <T: Draw<Tui>> (result: Usually<T>) -> impl Draw<Tui> {
|
||||||
|
thunk(move|to: &mut Tui|match result {
|
||||||
|
Ok(content) => content.draw(to),
|
||||||
|
Err(e) => {
|
||||||
|
let err_fg = Color::Rgb(255,224,244);
|
||||||
|
let err_bg = Color::Rgb(96, 24, 24);
|
||||||
|
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))).draw(to)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Screen for Tui {
|
||||||
|
type Unit = u16;
|
||||||
|
/// Render drawable in subarea specified by `area`
|
||||||
|
fn show (&mut self, content: impl Draw<Self>) -> Perhaps<XYWH<u16>> {
|
||||||
|
let previous_area = self.1;
|
||||||
|
Ok(if let Some(area) = content.layout(self.1)? {
|
||||||
|
self.1 = area;
|
||||||
|
if let Some(result_area) = content.draw(self)? {
|
||||||
|
self.1 = previous_area;
|
||||||
|
Some(result_area)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get current clipping area
|
||||||
|
fn area (&self) -> XYWH<Self::Unit> {
|
||||||
|
self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clip <T> (
|
||||||
|
&mut self,
|
||||||
|
area: impl Into<Option<XYWH<u16>>>,
|
||||||
|
draw: impl FnOnce(&mut Self)->T
|
||||||
|
) -> T {
|
||||||
|
let prev = self.1;
|
||||||
|
if let Some(area) = area.into() {
|
||||||
|
self.1 = area.into();
|
||||||
|
}
|
||||||
|
let result = draw(self);
|
||||||
|
self.1 = prev;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn fill_char (c: char) -> impl Draw<Tui> {
|
||||||
|
thunk(move|to: &mut Tui|Ok(Some(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) -> impl Draw<Tui> {
|
||||||
|
thunk(move|to: &mut Tui|Ok(Some({
|
||||||
|
if on {
|
||||||
|
to.update(&|cell,_,_|cell.modifier.insert(modifier))
|
||||||
|
} else {
|
||||||
|
to.update(&|cell,_,_|cell.modifier.remove(modifier))
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw contents with bold modifier applied.
|
||||||
|
pub const fn bold (on: bool, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
|
modify(on, Modifier::BOLD, draw)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
src/term/button.rs
Normal file
29
src/term/button.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// let _ = tengri::button_2("", "", true);
|
||||||
|
/// let _ = tengri::button_2("", "", false);
|
||||||
|
/// ```
|
||||||
|
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 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, "▌"), ))))
|
||||||
|
}
|
||||||
80
src/term/colors.rs
Normal file
80
src/term/colors.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
use crate::*;
|
||||||
|
use ratatui::prelude::Color;
|
||||||
|
use dizzle::{Ostensibly, Expression, LanguageError::*};
|
||||||
|
|
||||||
|
pub trait ColorDsl<T: Expression + Display>: Sized {
|
||||||
|
fn new_g (expr: T, try_to_u8: impl Fn(Ostensibly<&str>)->Ostensibly<u8>) -> Ostensibly<Self>;
|
||||||
|
fn new_rgb (expr: T, try_to_u8: impl Fn(Ostensibly<&str>)->Ostensibly<u8>) -> Ostensibly<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Expression + Display> ColorDsl<T> for Color {
|
||||||
|
fn new_g (expr: T, try_to_u8: impl Fn(Ostensibly<&str>)->Ostensibly<u8>) -> Ostensibly<Self> {
|
||||||
|
let n = try_to_u8(expr.tail().map_err(Into::into))?.ok_or(Domain("not gray"))?;
|
||||||
|
Ok(Some(Self::Rgb(n, n, n)))
|
||||||
|
}
|
||||||
|
fn new_rgb (expr: T, try_to_u8: impl Fn(Ostensibly<&str>)->Ostensibly<u8>) -> Ostensibly<Self> {
|
||||||
|
let r = try_to_u8(expr.tail().map_err(Into::into))?.ok_or(Domain("not red"))?;
|
||||||
|
let g = try_to_u8(expr.tail().map_err(Into::into).tail().head())?.ok_or(Domain("not green"))?;
|
||||||
|
let b = try_to_u8(expr.tail().map_err(Into::into).tail().tail().head())?.ok_or(Domain("not blue"))?;
|
||||||
|
Ok(Some(Color::Rgb(r, g, b)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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_ul (color: Option<Color>) -> impl Draw<Tui> {
|
||||||
|
thunk(move|to: &mut Tui|Ok(Some(if let Some(color) = color {
|
||||||
|
to.update(&|cell,_,_|{
|
||||||
|
cell.modifier.insert(Modifier::UNDERLINED);
|
||||||
|
cell.underline_color = color;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
to.update(&|cell,_,_|{
|
||||||
|
cell.modifier.remove(Modifier::UNDERLINED);
|
||||||
|
cell.underline_color = Reset;
|
||||||
|
})
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
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) }
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
use crate::{*, lang::*};
|
|
||||||
|
|
||||||
/// 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.
|
|
||||||
pub fn tui_input <T: Apply<TuiEvent, Usually<()>> + Send + Sync + 'static> (
|
|
||||||
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
|
|
||||||
) -> Result<Task, std::io::Error> {
|
|
||||||
let exited = exited.clone();
|
|
||||||
let state = state.clone();
|
|
||||||
Task::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:
|
|
||||||
event => {
|
|
||||||
if let Err(e) = state.write().unwrap().apply(&TuiEvent(event)) {
|
|
||||||
panic!("{e}")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,364 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use ratatui::{prelude::{Style, Position, Backend, Color}};
|
|
||||||
|
|
||||||
impl Screen for Tui {
|
|
||||||
type Unit = u16;
|
|
||||||
/// Render drawable in subarea specified by `area`
|
|
||||||
fn show (&mut self, content: impl Draw<Self>) -> Perhaps<XYWH<u16>> {
|
|
||||||
let previous_area = self.1;
|
|
||||||
Ok(if let Some(area) = content.layout(self.1)? {
|
|
||||||
self.1 = area;
|
|
||||||
if let Some(result_area) = content.draw(self)? {
|
|
||||||
self.1 = previous_area;
|
|
||||||
Some(result_area)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get current clipping area
|
|
||||||
fn area (&self) -> XYWH<Self::Unit> {
|
|
||||||
self.1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clip <T> (
|
|
||||||
&mut self,
|
|
||||||
area: impl Into<Option<XYWH<u16>>>,
|
|
||||||
draw: impl FnOnce(&mut Self)->T
|
|
||||||
) -> T {
|
|
||||||
let prev = self.1;
|
|
||||||
if let Some(area) = area.into() {
|
|
||||||
self.1 = area.into();
|
|
||||||
}
|
|
||||||
let result = draw(self);
|
|
||||||
self.1 = prev;
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enable TUI output for state struct.
|
|
||||||
#[macro_export] macro_rules! tui_view {
|
|
||||||
(|$self:ident: $State:ty|$body:block) => {
|
|
||||||
impl View<Tui> for $State {
|
|
||||||
fn view (&$self) -> impl Draw<Tui> $body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Spawn the TUI output thread which writes colored characters to the terminal.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let state = std::sync::Arc::new(std::sync::RwLock::new(()));
|
|
||||||
/// let _ = tengri::Exit::run(|exit|{
|
|
||||||
/// tengri::tui_output(
|
|
||||||
/// exit.as_ref(),
|
|
||||||
/// &state,
|
|
||||||
/// std::time::Duration::from_millis(10),
|
|
||||||
/// std::io::stdout()
|
|
||||||
/// )
|
|
||||||
/// });
|
|
||||||
/// ```
|
|
||||||
pub fn tui_output <
|
|
||||||
W: Write + Send + Sync + 'static, T: View<Tui> + Send + Sync + 'static
|
|
||||||
> (
|
|
||||||
exited: &Arc<AtomicBool>,
|
|
||||||
state: &Arc<RwLock<T>>,
|
|
||||||
sleep: Duration,
|
|
||||||
output: W,
|
|
||||||
) -> Usually<Task> {
|
|
||||||
let state = state.clone();
|
|
||||||
stdout().execute(EnterAlternateScreen)?;
|
|
||||||
CrosstermBackend::new(stdout()).hide_cursor()?;
|
|
||||||
enable_raw_mode()?;
|
|
||||||
let mut backend = CrosstermBackend::new(output);
|
|
||||||
let Size { width, height } = backend.size().expect("get size failed");
|
|
||||||
let mut prev = Tui::new(width, height);
|
|
||||||
let mut next = Tui::new(width, height);
|
|
||||||
Ok(Task::new_sleep(exited.clone(), sleep, move |perf| {
|
|
||||||
let Size { width, height } = backend.size().expect("get size failed");
|
|
||||||
if let Ok(state) = state.try_read() {
|
|
||||||
prev.resize(&mut backend, width, height);
|
|
||||||
state.view().draw(&mut next).expect("draw failed"); // TODO draw error
|
|
||||||
prev.redraw(&mut backend, &mut next);
|
|
||||||
}
|
|
||||||
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
|
|
||||||
prev.set_string(0, 0, &timer, Style::default());
|
|
||||||
})?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub 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) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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) -> impl Draw<Tui> {
|
|
||||||
thunk(move|to: &mut Tui|Ok(Some(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) -> impl Draw<Tui> {
|
|
||||||
thunk(move|to: &mut Tui|Ok(Some({
|
|
||||||
if on {
|
|
||||||
to.update(&|cell,_,_|cell.modifier.insert(modifier))
|
|
||||||
} else {
|
|
||||||
to.update(&|cell,_,_|cell.modifier.remove(modifier))
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw contents with bold modifier applied.
|
|
||||||
pub const fn bold (on: bool, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
|
||||||
modify(on, Modifier::BOLD, draw)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn fill_ul (color: Option<Color>) -> impl Draw<Tui> {
|
|
||||||
thunk(move|to: &mut Tui|Ok(Some(if let Some(color) = color {
|
|
||||||
to.update(&|cell,_,_|{
|
|
||||||
cell.modifier.insert(Modifier::UNDERLINED);
|
|
||||||
cell.underline_color = color;
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
to.update(&|cell,_,_|{
|
|
||||||
cell.modifier.remove(Modifier::UNDERLINED);
|
|
||||||
cell.underline_color = Reset;
|
|
||||||
})
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
mod phat {
|
|
||||||
use super::*;
|
|
||||||
pub const LO: &'static str = "▄";
|
|
||||||
pub const HI: &'static str = "▀";
|
|
||||||
/// A phat line
|
|
||||||
pub fn lo (fg: Color, bg: Color) -> impl Draw<Tui> {
|
|
||||||
fg_bg(fg, bg, x_repeat(self::phat::LO)).exact_h(1)
|
|
||||||
}
|
|
||||||
/// A phat line
|
|
||||||
pub fn hi (fg: Color, bg: Color) -> impl Draw<Tui> {
|
|
||||||
fg_bg(fg, bg, x_repeat(self::phat::HI)).exact_h(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod scroll {
|
|
||||||
pub const ICON_DEC_V: &[char] = &['▲'];
|
|
||||||
pub const ICON_INC_V: &[char] = &['▼'];
|
|
||||||
pub const ICON_DEC_H: &[char] = &[' ', '🞀', ' '];
|
|
||||||
pub const ICON_INC_H: &[char] = &[' ', '🞂', ' '];
|
|
||||||
}
|
|
||||||
|
|
||||||
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.0.cell_mut(Position::from((x, y))) {
|
|
||||||
cell.set_symbol(&c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Some(XYWH(x, y, w, 1)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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.0.cell_mut(Position::from((x, y))) {
|
|
||||||
cell.set_symbol(&c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Some(XYWH(x, y, 1, h)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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.0.cell_mut(Position::from((x, y))) {
|
|
||||||
let u = u % a;
|
|
||||||
cell.set_symbol(&c[u..u+1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Some(XYWH(x, y, w, h)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ```
|
|
||||||
/// let _ = tengri::button_2("", "", true);
|
|
||||||
/// let _ = tengri::button_2("", "", false);
|
|
||||||
/// ```
|
|
||||||
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 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, "▌"), ))))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stackably padded.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// /// TODO
|
|
||||||
/// ```
|
|
||||||
pub fn phat (w: u16, h: u16, [fg, bg, hi, lo]: [Color;4], draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
|
||||||
let top = self::phat::lo(bg, hi).exact_h(1);
|
|
||||||
let low = self::phat::hi(bg, lo).exact_h(1);
|
|
||||||
let draw = fg_bg(fg, bg, draw);
|
|
||||||
south(top, north(low, draw)).min_wh(w, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn x_scroll () -> impl Draw<Tui> {
|
|
||||||
thunk(|Tui(buf, XYWH(x1, y1, w, h)): &mut Tui|{
|
|
||||||
let x2 = *x1 + *w;
|
|
||||||
for (i, x) in (*x1..=x2).enumerate() {
|
|
||||||
if let Some(cell) = buf.cell_mut(Position::from((x, *y1))) {
|
|
||||||
if i < (self::scroll::ICON_DEC_H.len()) {
|
|
||||||
cell.set_fg(Rgb(255, 255, 255));
|
|
||||||
cell.set_bg(Rgb(0, 0, 0));
|
|
||||||
cell.set_char(self::scroll::ICON_DEC_H[i as usize]);
|
|
||||||
} else if i > (*w as usize - self::scroll::ICON_INC_H.len()) {
|
|
||||||
cell.set_fg(Rgb(255, 255, 255));
|
|
||||||
cell.set_bg(Rgb(0, 0, 0));
|
|
||||||
cell.set_char(self::scroll::ICON_INC_H[*w as usize - i]);
|
|
||||||
} else if false {
|
|
||||||
cell.set_fg(Rgb(255, 255, 255));
|
|
||||||
cell.set_bg(Reset);
|
|
||||||
cell.set_char('━');
|
|
||||||
} else {
|
|
||||||
cell.set_fg(Rgb(0, 0, 0));
|
|
||||||
cell.set_bg(Reset);
|
|
||||||
cell.set_char('╌');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Some(XYWH(*x1, *y1, *w, 1)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn y_scroll () -> impl Draw<Tui> {
|
|
||||||
thunk(|Tui(buf, XYWH(x1, y1, w, h)): &mut Tui|{
|
|
||||||
let y2 = *y1 + *h;
|
|
||||||
for (i, y) in (*y1..=y2).enumerate() {
|
|
||||||
if let Some(cell) = buf.cell_mut(Position::from((*x1, y))) {
|
|
||||||
if (i as usize) < (self::scroll::ICON_DEC_V.len()) {
|
|
||||||
cell.set_fg(Rgb(255, 255, 255));
|
|
||||||
cell.set_bg(Rgb(0, 0, 0));
|
|
||||||
cell.set_char(self::scroll::ICON_DEC_V[i as usize]);
|
|
||||||
} else if (i as usize) > (*h as usize - self::scroll::ICON_INC_V.len()) {
|
|
||||||
cell.set_fg(Rgb(255, 255, 255));
|
|
||||||
cell.set_bg(Rgb(0, 0, 0));
|
|
||||||
cell.set_char(self::scroll::ICON_INC_V[*h as usize - i]);
|
|
||||||
} else if false {
|
|
||||||
cell.set_fg(Rgb(255, 255, 255));
|
|
||||||
cell.set_bg(Reset);
|
|
||||||
cell.set_char('‖'); // ━
|
|
||||||
} else {
|
|
||||||
cell.set_fg(Rgb(0, 0, 0));
|
|
||||||
cell.set_bg(Reset);
|
|
||||||
cell.set_char('╎'); // ━
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Some(XYWH(*x1, *y1, 1, *h)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw TUI content or its error message.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// for variant in [
|
|
||||||
/// Ok(Some("hello")),
|
|
||||||
/// Ok(None),
|
|
||||||
/// Err("fail".into()),
|
|
||||||
/// ] {
|
|
||||||
/// let _ = tengri::catcher(variant);
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn catcher <T: Draw<Tui>> (result: Usually<T>) -> impl Draw<Tui> {
|
|
||||||
thunk(move|to: &mut Tui|match result {
|
|
||||||
Ok(content) => content.draw(to),
|
|
||||||
Err(e) => {
|
|
||||||
let err_fg = Color::Rgb(255,224,244);
|
|
||||||
let err_bg = Color::Rgb(96, 24, 24);
|
|
||||||
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))).draw(to)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_draw!(|self: u64, _to: Tui|{ todo!() });
|
|
||||||
impl_draw!(|self: f64, _to: Tui|{ todo!() });
|
|
||||||
24
src/term/phat.rs
Normal file
24
src/term/phat.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Stackably padded.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// /// TODO
|
||||||
|
/// ```
|
||||||
|
pub fn phat (w: u16, h: u16, [fg, bg, hi, lo]: [Color;4], draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
|
let top = phat_lo(bg, hi).exact_h(1);
|
||||||
|
let low = phat_hi(bg, lo).exact_h(1);
|
||||||
|
let draw = fg_bg(fg, bg, draw);
|
||||||
|
south(top, north(low, draw)).min_wh(w, h)
|
||||||
|
}
|
||||||
|
use super::*;
|
||||||
|
pub const LO: &'static str = "▄";
|
||||||
|
pub const HI: &'static str = "▀";
|
||||||
|
/// A phat line
|
||||||
|
fn phat_lo (fg: Color, bg: Color) -> impl Draw<Tui> {
|
||||||
|
fg_bg(fg, bg, x_repeat(self::phat::LO)).exact_h(1)
|
||||||
|
}
|
||||||
|
/// A phat line
|
||||||
|
fn phat_hi (fg: Color, bg: Color) -> impl Draw<Tui> {
|
||||||
|
fg_bg(fg, bg, x_repeat(self::phat::HI)).exact_h(1)
|
||||||
|
}
|
||||||
43
src/term/repeat.rs
Normal file
43
src/term/repeat.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
use crate::*;
|
||||||
|
use ratatui::{prelude::{Style, Position, Backend, Color}};
|
||||||
|
|
||||||
|
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.0.cell_mut(Position::from((x, y))) {
|
||||||
|
cell.set_symbol(&c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(XYWH(x, y, w, 1)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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.0.cell_mut(Position::from((x, y))) {
|
||||||
|
cell.set_symbol(&c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(XYWH(x, y, 1, h)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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.0.cell_mut(Position::from((x, y))) {
|
||||||
|
let u = u % a;
|
||||||
|
cell.set_symbol(&c[u..u+1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(XYWH(x, y, w, h)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
64
src/term/scroll.rs
Normal file
64
src/term/scroll.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
use crate::*;
|
||||||
|
use ratatui::{prelude::{Style, Position, Backend, Color}};
|
||||||
|
|
||||||
|
pub const ICON_DEC_V: &[char] = &['▲'];
|
||||||
|
pub const ICON_INC_V: &[char] = &['▼'];
|
||||||
|
pub const ICON_DEC_H: &[char] = &[' ', '🞀', ' '];
|
||||||
|
pub const ICON_INC_H: &[char] = &[' ', '🞂', ' '];
|
||||||
|
|
||||||
|
pub fn x_scroll () -> impl Draw<Tui> {
|
||||||
|
thunk(|Tui(buf, XYWH(x1, y1, w, h)): &mut Tui|{
|
||||||
|
let x2 = *x1 + *w;
|
||||||
|
for (i, x) in (*x1..=x2).enumerate() {
|
||||||
|
if let Some(cell) = buf.cell_mut(Position::from((x, *y1))) {
|
||||||
|
if i < (ICON_DEC_H.len()) {
|
||||||
|
cell.set_fg(Rgb(255, 255, 255));
|
||||||
|
cell.set_bg(Rgb(0, 0, 0));
|
||||||
|
cell.set_char(ICON_DEC_H[i as usize]);
|
||||||
|
} else if i > (*w as usize - ICON_INC_H.len()) {
|
||||||
|
cell.set_fg(Rgb(255, 255, 255));
|
||||||
|
cell.set_bg(Rgb(0, 0, 0));
|
||||||
|
cell.set_char(ICON_INC_H[*w as usize - i]);
|
||||||
|
} else if false {
|
||||||
|
cell.set_fg(Rgb(255, 255, 255));
|
||||||
|
cell.set_bg(Reset);
|
||||||
|
cell.set_char('━');
|
||||||
|
} else {
|
||||||
|
cell.set_fg(Rgb(0, 0, 0));
|
||||||
|
cell.set_bg(Reset);
|
||||||
|
cell.set_char('╌');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(XYWH(*x1, *y1, *w, 1)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn y_scroll () -> impl Draw<Tui> {
|
||||||
|
thunk(|Tui(buf, XYWH(x1, y1, w, h)): &mut Tui|{
|
||||||
|
let y2 = *y1 + *h;
|
||||||
|
for (i, y) in (*y1..=y2).enumerate() {
|
||||||
|
if let Some(cell) = buf.cell_mut(Position::from((*x1, y))) {
|
||||||
|
if (i as usize) < (ICON_DEC_V.len()) {
|
||||||
|
cell.set_fg(Rgb(255, 255, 255));
|
||||||
|
cell.set_bg(Rgb(0, 0, 0));
|
||||||
|
cell.set_char(ICON_DEC_V[i as usize]);
|
||||||
|
} else if (i as usize) > (*h as usize - ICON_INC_V.len()) {
|
||||||
|
cell.set_fg(Rgb(255, 255, 255));
|
||||||
|
cell.set_bg(Rgb(0, 0, 0));
|
||||||
|
cell.set_char(ICON_INC_V[*h as usize - i]);
|
||||||
|
} else if false {
|
||||||
|
cell.set_fg(Rgb(255, 255, 255));
|
||||||
|
cell.set_bg(Reset);
|
||||||
|
cell.set_char('‖'); // ━
|
||||||
|
} else {
|
||||||
|
cell.set_fg(Rgb(0, 0, 0));
|
||||||
|
cell.set_bg(Reset);
|
||||||
|
cell.set_char('╎'); // ━
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(XYWH(*x1, *y1, 1, *h)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue