mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-04-03 13:30:44 +02:00
This commit is contained in:
parent
4e8d58d793
commit
b0fbe3c173
18 changed files with 1724 additions and 1808 deletions
39
Cargo.toml
39
Cargo.toml
|
|
@ -5,32 +5,35 @@ version = "0.15.0"
|
||||||
description = "UI metaframework."
|
description = "UI metaframework."
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["lang", "sing", "draw", "play", "tui"]
|
default = ["lang", "sing", "draw", "play", "tui", "text", "time", "rand", "okhsl"]
|
||||||
lang = ["dep:dizzle"]
|
|
||||||
sing = ["dep:jack"]
|
|
||||||
draw = []
|
|
||||||
play = []
|
|
||||||
tui = ["draw", "dep:ratatui", "dep:crossterm"]
|
|
||||||
gui = ["draw", "dep:winit"]
|
|
||||||
|
|
||||||
bumpalo = ["dep:bumpalo"]
|
bumpalo = ["dep:bumpalo"]
|
||||||
|
draw = []
|
||||||
dsl = ["dep:dizzle"]
|
dsl = ["dep:dizzle"]
|
||||||
|
gui = ["draw", "dep:winit"]
|
||||||
|
lang = ["dep:dizzle"]
|
||||||
|
okhsl = ["dep:palette"]
|
||||||
|
play = []
|
||||||
|
rand = ["dep:rand"]
|
||||||
|
sing = ["dep:jack"]
|
||||||
|
text = ["dep:unicode-width"]
|
||||||
|
time = ["dep:quanta"]
|
||||||
|
tui = ["draw", "dep:ratatui", "dep:crossterm"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0" }
|
anyhow = { version = "1.0" }
|
||||||
atomic_float = { version = "1" }
|
atomic_float = { version = "1" }
|
||||||
better-panic = { version = "0.3.0" }
|
better-panic = { version = "0.3.0" }
|
||||||
palette = { version = "0.7.6", features = [ "random" ] }
|
|
||||||
quanta = { version = "0.12.3" }
|
|
||||||
rand = { version = "0.8.5" }
|
|
||||||
unicode-width = { version = "0.2" }
|
|
||||||
|
|
||||||
dizzle = { optional = true, path = "./dizzle" }
|
|
||||||
jack = { optional = true, path = "./rust-jack" }
|
|
||||||
winit = { optional = true, version = "0.30.4", features = [ "x11" ]}
|
|
||||||
bumpalo = { optional = true, version = "3.19.0" }
|
bumpalo = { optional = true, version = "3.19.0" }
|
||||||
crossterm = { optional = true, version = "0.29.0" }
|
crossterm = { optional = true, version = "0.29.0" }
|
||||||
|
dizzle = { optional = true, path = "./dizzle" }
|
||||||
|
jack = { optional = true, path = "./rust-jack" }
|
||||||
|
palette = { optional = true, version = "0.7.6", features = [ "random" ] }
|
||||||
|
quanta = { optional = true, version = "0.12.3" }
|
||||||
|
rand = { optional = true, version = "0.8.5" }
|
||||||
ratatui = { optional = true, version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] }
|
ratatui = { optional = true, version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] }
|
||||||
|
unicode-width = { optional = true, version = "0.2" }
|
||||||
|
winit = { optional = true, version = "0.30.4", features = [ "x11" ]}
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
proptest = { version = "^1" }
|
proptest = { version = "^1" }
|
||||||
|
|
@ -38,12 +41,6 @@ proptest-derive = { version = "^0.5.1" }
|
||||||
tengri = { path = ".", features = [ "dsl" ] }
|
tengri = { path = ".", features = [ "dsl" ] }
|
||||||
#tengri_proc = { path = "./proc" }
|
#tengri_proc = { path = "./proc" }
|
||||||
|
|
||||||
[lib]
|
|
||||||
path = "src/tengri.rs"
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
lto = true
|
|
||||||
|
|
||||||
[profile.coverage]
|
[profile.coverage]
|
||||||
inherits = "test"
|
inherits = "test"
|
||||||
lto = false
|
lto = false
|
||||||
|
|
|
||||||
14
README.md
14
README.md
|
|
@ -13,15 +13,15 @@ connect to jack audio connection kit to process chunks of audio and midi.
|
||||||
|
|
||||||
### draw
|
### draw
|
||||||
|
|
||||||
abstract interface layout system for defining interface layouts abstractly.
|
backend-agnostic layout pattern.
|
||||||
|
|
||||||
### play
|
### play
|
||||||
|
|
||||||
the input handling system.
|
the input handling system.
|
||||||
|
|
||||||
### tui
|
### term
|
||||||
|
|
||||||
uses `ratatui` to run in a terminal.
|
uses `ratatui` to render in in a terminal.
|
||||||
|
|
||||||
### gui (todo-todo-todo)
|
### gui (todo-todo-todo)
|
||||||
|
|
||||||
|
|
@ -31,6 +31,14 @@ opens windows, runs shaders in them and/or delegates them (to e.g. plugin guis).
|
||||||
|
|
||||||
uses `dizzle` to let you livecode all of the above.
|
uses `dizzle` to let you livecode all of the above.
|
||||||
|
|
||||||
|
### time
|
||||||
|
|
||||||
|
performance counter.
|
||||||
|
|
||||||
|
### text
|
||||||
|
|
||||||
|
backend-agnostic text layout.
|
||||||
|
|
||||||
## license
|
## license
|
||||||
|
|
||||||
here and now, the blessings of `tengri` are invokable under the [**`AGPL3`**](./LICENSE).
|
here and now, the blessings of `tengri` are invokable under the [**`AGPL3`**](./LICENSE).
|
||||||
|
|
|
||||||
2
dizzle
2
dizzle
|
|
@ -1 +1 @@
|
||||||
Subproject commit 6026aff29f9a0ca9ecafdfac13ea59ddd65d8f83
|
Subproject commit 068e26dd50699e51e9db01ac23fc0778074647bd
|
||||||
455
src/.scratch.rs
455
src/.scratch.rs
|
|
@ -904,3 +904,458 @@ impl<E: Engine, S, C: Command<S>> MenuItem<E, S, C> {
|
||||||
////test(":ctrl-alt-shift-x",
|
////test(":ctrl-alt-shift-x",
|
||||||
////KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL | Mods::ALT | Mods::SHIFT ));
|
////KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL | Mods::ALT | Mods::SHIFT ));
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
//#[cfg(test)] pub(crate) use proptest_derive::Arbitrary;
|
||||||
|
//#[cfg(test)] pub(crate) use proptest::{prelude::*, option::of};
|
||||||
|
//#[cfg(test)] use Direction::*;
|
||||||
|
//#[cfg(test)] use proptest_derive::Arbitrary;
|
||||||
|
//#[cfg(test)] proptest! {
|
||||||
|
//#[test] fn proptest_direction (
|
||||||
|
//d in prop_oneof![
|
||||||
|
//Just(North), Just(South),
|
||||||
|
//Just(East), Just(West),
|
||||||
|
//Just(Above), Just(Below)
|
||||||
|
//],
|
||||||
|
//x in u16::MIN..u16::MAX,
|
||||||
|
//y in u16::MIN..u16::MAX,
|
||||||
|
//w in u16::MIN..u16::MAX,
|
||||||
|
//h in u16::MIN..u16::MAX,
|
||||||
|
//a in u16::MIN..u16::MAX,
|
||||||
|
//) {
|
||||||
|
//let _ = d.split_fixed(XYWH(x, y, w, h), a);
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
//proptest! {
|
||||||
|
//#[test] fn proptest_area (
|
||||||
|
//x in u16::MIN..u16::MAX,
|
||||||
|
//y in u16::MIN..u16::MAX,
|
||||||
|
//w in u16::MIN..u16::MAX,
|
||||||
|
//h in u16::MIN..u16::MAX,
|
||||||
|
//a in u16::MIN..u16::MAX,
|
||||||
|
//b in u16::MIN..u16::MAX,
|
||||||
|
//) {
|
||||||
|
//let _: XYWH<u16> = XYWH::zero();
|
||||||
|
////let _: XYWH<u16> = XYWH::from_position([a, b]);
|
||||||
|
////let _: XYWH<u16> = XYWH::from_size([a, b]);
|
||||||
|
//let area: XYWH<u16> = XYWH(x, y, w, h);
|
||||||
|
////let _ = area.expect_min(a, b);
|
||||||
|
//let _ = area.xy();
|
||||||
|
//let _ = area.wh();
|
||||||
|
////let _ = area.xywh();
|
||||||
|
//let _ = area.clipped_h(a);
|
||||||
|
//let _ = area.clipped_w(b);
|
||||||
|
//let _ = area.clipped(WH(a, b));
|
||||||
|
////let _ = area.set_w(a);
|
||||||
|
////let _ = area.set_h(b);
|
||||||
|
//let _ = area.x2();
|
||||||
|
//let _ = area.y2();
|
||||||
|
//let _ = area.lrtb();
|
||||||
|
//let _ = area.center();
|
||||||
|
//let _ = area.centered();
|
||||||
|
//let _ = area.centered_x(a);
|
||||||
|
//let _ = area.centered_y(b);
|
||||||
|
//let _ = area.centered_xy([a, b]);
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
//proptest! {
|
||||||
|
//#[test] fn proptest_size (
|
||||||
|
//x in u16::MIN..u16::MAX,
|
||||||
|
//y in u16::MIN..u16::MAX,
|
||||||
|
//a in u16::MIN..u16::MAX,
|
||||||
|
//b in u16::MIN..u16::MAX,
|
||||||
|
//) {
|
||||||
|
//let size = WH(x, y);
|
||||||
|
//let _ = size.w();
|
||||||
|
//let _ = size.h();
|
||||||
|
//let _ = size.wh();
|
||||||
|
//let _ = size.clip_w(a);
|
||||||
|
//let _ = size.clip_h(b);
|
||||||
|
////let _ = size.expect_min(a, b);
|
||||||
|
////let _ = size.to_area_pos();
|
||||||
|
////let _ = size.to_area_size();
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
////macro_rules! test_op_transform {
|
||||||
|
////($fn:ident, $Op:ident) => {
|
||||||
|
////proptest! {
|
||||||
|
////#[test] fn $fn (
|
||||||
|
////op_x in of(u16::MIN..u16::MAX),
|
||||||
|
////op_y in of(u16::MIN..u16::MAX),
|
||||||
|
////content in "\\PC*",
|
||||||
|
////x in u16::MIN..u16::MAX,
|
||||||
|
////y in u16::MIN..u16::MAX,
|
||||||
|
////w in u16::MIN..u16::MAX,
|
||||||
|
////h in u16::MIN..u16::MAX,
|
||||||
|
////) {
|
||||||
|
////if let Some(op) = match (op_x, op_y) {
|
||||||
|
////(Some(x), Some(y)) => Some($Op::XY(x, y, content)),
|
||||||
|
////(Some(x), None) => Some($Op::X(x, content)),
|
||||||
|
////(None, Some(y)) => Some($Op::Y(y, content)),
|
||||||
|
////_ => None
|
||||||
|
////} {
|
||||||
|
//////assert_eq!(Content::layout(&op, [x, y, w, h]),
|
||||||
|
//////Draw::layout(&op, [x, y, w, h]));
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////test_op_transform!(proptest_op_fixed, Fixed);
|
||||||
|
////test_op_transform!(proptest_op_min, Min);
|
||||||
|
////test_op_transform!(proptest_op_max, Max);
|
||||||
|
////test_op_transform!(proptest_op_push, Push);
|
||||||
|
////test_op_transform!(proptest_op_pull, Pull);
|
||||||
|
////test_op_transform!(proptest_op_shrink, Shrink);
|
||||||
|
////test_op_transform!(proptest_op_expand, Expand);
|
||||||
|
////test_op_transform!(proptest_op_padding, Pad);
|
||||||
|
|
||||||
|
//proptest! {
|
||||||
|
//#[test] fn proptest_op_bsp (
|
||||||
|
//d in prop_oneof![
|
||||||
|
//Just(North), Just(South),
|
||||||
|
//Just(East), Just(West),
|
||||||
|
//Just(Above), Just(Below)
|
||||||
|
//],
|
||||||
|
//a in "\\PC*",
|
||||||
|
//b in "\\PC*",
|
||||||
|
//x in u16::MIN..u16::MAX,
|
||||||
|
//y in u16::MIN..u16::MAX,
|
||||||
|
//w in u16::MIN..u16::MAX,
|
||||||
|
//h in u16::MIN..u16::MAX,
|
||||||
|
//) {
|
||||||
|
//let bsp = Bsp(d, a, b);
|
||||||
|
////assert_eq!(
|
||||||
|
////Content::layout(&bsp, [x, y, w, h]),
|
||||||
|
////Draw::layout(&bsp, [x, y, w, h]),
|
||||||
|
////);
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
////#[test] fn test_tui_engine () -> Usually<()> {
|
||||||
|
//////use std::sync::{Arc, RwLock};
|
||||||
|
////struct TestComponent(String);
|
||||||
|
////impl Draw<TuiOut> for TestComponent {
|
||||||
|
////fn draw (&self, _to: &mut TuiOut) {}
|
||||||
|
////}
|
||||||
|
////impl Handle<TuiIn> for TestComponent {
|
||||||
|
////fn handle (&mut self, _from: &TuiIn) -> Perhaps<bool> { Ok(None) }
|
||||||
|
////}
|
||||||
|
////let engine = Tui::new(Box::<&mut Vec<u8>>::new(vec![0u8;0].as_mut()))?;
|
||||||
|
////let state = engine.run(false, &Arc::new(RwLock::new(TestComponent("hello world".into()))))?;
|
||||||
|
////state.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
////Ok(())
|
||||||
|
////}
|
||||||
|
|
||||||
|
////impl<O: Screen> PartialEq for Measure<O> { fn eq (&self, other: &Self) -> bool { self.w() == other.w() && self.h() == other.h() } }
|
||||||
|
////impl<N: Coord> W<N> for Measure<N> { fn w (&self) -> N { (self.x.load(Relaxed) as u16).into() } }
|
||||||
|
////impl<N: Coord> H<N> for Measure<N> { fn h (&self) -> N { (self.y.load(Relaxed) as u16).into() } }
|
||||||
|
////impl<N: Coord> Debug for Measure<N> { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { f.debug_struct("Measure").field("width", &self.x).field("height", &self.y).finish() } }
|
||||||
|
////impl<N: Coord> Measure<N> {
|
||||||
|
////pub fn set_w (&self, w: impl Into<usize>) -> &Self { self.x.store(w.into(), Relaxed); self }
|
||||||
|
////pub fn set_h (&self, h: impl Into<usize>) -> &Self { self.y.store(h.into(), Relaxed); self }
|
||||||
|
////pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) -> &Self { self.set_w(w); self.set_h(h); self }
|
||||||
|
////pub fn format (&self) -> Arc<str> { format!("{}x{}", self.w(), self.h()).into() }
|
||||||
|
////pub fn of <T: Draw<O>, O: Screen<N>> (&self, item: T) -> Bsp<Fill<&Self>, T> { bsp_b(Fill::XY(self), item) }
|
||||||
|
////pub fn new (x: N, y: N) -> Self { Self { x: Arc::new(x.atomic()), y: Arc::new(y.atomic()), __: PhantomData::default(), } }
|
||||||
|
////}
|
||||||
|
////impl<N: Coord> From<WH<N>> for Measure<N> { fn from (WH(x, y): WH<O::Unit>) -> Self { Self::new(x, y) } }
|
||||||
|
////impl<O: Screen> Clone for Measure<O> {
|
||||||
|
////fn clone (&self) -> Self { Self { __: Default::default(), x: self.x.clone(), y: self.y.clone(), } }
|
||||||
|
////}
|
||||||
|
////impl<N: Coord, O: Screen<N>> Draw<O> for Measure<N> {
|
||||||
|
////fn draw (&self, to: &mut O) {
|
||||||
|
////// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small
|
||||||
|
////self.x.store(to.area().w().into(), Relaxed);
|
||||||
|
////self.y.store(to.area().h().into(), Relaxed);
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////use Alignment::*;
|
||||||
|
////let layout_x = match alignment {
|
||||||
|
////NW | W | SW => to.x(),
|
||||||
|
////N | C | S => to.x().plus(to.w() / 2.into()).minus(self.1.layout_w(to) / 2.into()),
|
||||||
|
////NE | E | SE => to.x().plus(to.w()).minus(self.1.layout_w(to)),
|
||||||
|
////_ => todo!(),
|
||||||
|
////};
|
||||||
|
////let layout_y = match alignment {
|
||||||
|
////NW | N | NE => to.y(),
|
||||||
|
////W | C | E => to.y().plus(to.h() / 2.into()).minus(self.1.layout_h(to) / 2.into()),
|
||||||
|
////SW | S | SE => to.y().plus(to.h()).minus(self.1.layout_h(to)),
|
||||||
|
////_ => todo!(),
|
||||||
|
////};
|
||||||
|
////draw.draw(to.clip(w, h))
|
||||||
|
////}
|
||||||
|
////todo!()
|
||||||
|
////impl<O: Screen, T: Layout<O>> Layout<O> for Align<T> {
|
||||||
|
////fn layout_x (&self, to: XYWH<O::Unit>) -> O::Unit {
|
||||||
|
////use Alignment::*;
|
||||||
|
////match self.0 {
|
||||||
|
////NW | W | SW => to.x(),
|
||||||
|
////N | C | S => to.x().plus(to.w() / 2.into()).minus(self.1.layout_w(to) / 2.into()),
|
||||||
|
////NE | E | SE => to.x().plus(to.w()).minus(self.1.layout_w(to)),
|
||||||
|
////_ => todo!(),
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////fn layout_y (&self, to: XYWH<O::Unit>) -> O::Unit {
|
||||||
|
////use Alignment::*;
|
||||||
|
////match self.0 {
|
||||||
|
////NW | N | NE => to.y(),
|
||||||
|
////W | C | E => to.y().plus(to.h() / 2.into()).minus(self.1.layout_h(to) / 2.into()),
|
||||||
|
////SW | S | SE => to.y().plus(to.h()).minus(self.1.layout_h(to)),
|
||||||
|
////_ => todo!(),
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////impl<E: Screen, T: Layout<E>> Layout<E> for Min<E::Unit, T> {
|
||||||
|
////fn layout (&self, area: XYWH<E::Unit>) -> XYWH<E::Unit> {
|
||||||
|
////let XYWH(x, y, w, h) = self.inner().layout(area);
|
||||||
|
////match self {
|
||||||
|
////Self::X(mw, _) => XYWH(x, y, w.max(*mw), h),
|
||||||
|
////Self::Y(mh, _) => XYWH(x, y, w, h.max(*mh)),
|
||||||
|
////Self::XY(mw, mh, _) => XYWH(x, y, w.max(*mw), h.max(*mh)),
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////impl<E: Screen, T: Layout<E>> Layout<E> for Max<E::Unit, T> {
|
||||||
|
////fn layout (&self, area: XYWH<E::Unit>) -> XYWH<E::Unit> {
|
||||||
|
////let XYWH(x, y, w, h) = self.inner().layout(area);
|
||||||
|
////match self {
|
||||||
|
////Self::X(mw, _) => XYWH(x, y, w.min(*mw), h ),
|
||||||
|
////Self::Y(mh, _) => XYWH(x, y, w, h.min(*mh)),
|
||||||
|
////Self::XY(mw, mh, _) => XYWH(x, y, w.min(*mw), h.min(*mh)),
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
|
||||||
|
////impl<'a, O, A, B, I, F, G> Map<O, A, B, I, F, G> where
|
||||||
|
////I: Iterator<Item = A> + Send + Sync + 'a,
|
||||||
|
////F: Fn() -> I + Send + Sync + 'a,
|
||||||
|
////{
|
||||||
|
////pub const fn new (get_iter: F, get_item: G) -> Self {
|
||||||
|
////Self {
|
||||||
|
////__: PhantomData,
|
||||||
|
////get_iter,
|
||||||
|
////get_item
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////const fn to_south <S: Screen> (
|
||||||
|
////item_offset: S::Unit, item_height: S::Unit, item: impl Draw<S>
|
||||||
|
////) -> impl Draw<S> {
|
||||||
|
////Push::Y(item_offset, Fixed::Y(item_height, Fill::X(item)))
|
||||||
|
////}
|
||||||
|
////const fn to_south_west <S: Screen> (
|
||||||
|
////item_offset: S::Unit, item_height: S::Unit, item: impl Draw<S>
|
||||||
|
////) -> impl Draw<S> {
|
||||||
|
////Push::Y(item_offset, Align::nw(Fixed::Y(item_height, Fill::X(item))))
|
||||||
|
////}
|
||||||
|
////const fn to_east <S: Screen> (
|
||||||
|
////item_offset: S::Unit, item_width: S::Unit, item: impl Draw<S>
|
||||||
|
////) -> impl Draw<S> {
|
||||||
|
////Push::X(item_offset, Align::w(Fixed::X(item_width, Fill::Y(item))))
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
|
||||||
|
////macro_rules! impl_map_direction (($name:ident, $axis:ident, $align:ident)=>{
|
||||||
|
////impl<'a, O, A, B, I, F> Map<
|
||||||
|
////O, A, Push<O::Unit, Align<Fixed<O::Unit, Fill<B>>>>, I, F, fn(A, usize)->B
|
||||||
|
////> where
|
||||||
|
////O: Screen,
|
||||||
|
////B: Draw<O>,
|
||||||
|
////I: Iterator<Item = A> + Send + Sync + 'a,
|
||||||
|
////F: Fn() -> I + Send + Sync + 'a
|
||||||
|
////{
|
||||||
|
////pub const fn $name (
|
||||||
|
////size: O::Unit,
|
||||||
|
////get_iter: F,
|
||||||
|
////get_item: impl Fn(A, usize)->B + Send + Sync
|
||||||
|
////) -> Map<
|
||||||
|
////O, A,
|
||||||
|
////Push<O::Unit, Align<Fixed<O::Unit, B>>>,
|
||||||
|
////I, F,
|
||||||
|
////impl Fn(A, usize)->Push<O::Unit, Align<Fixed<O::Unit, B>>> + Send + Sync
|
||||||
|
////> {
|
||||||
|
////Map {
|
||||||
|
////__: PhantomData,
|
||||||
|
////get_iter,
|
||||||
|
////get_item: move |item: A, index: usize|{
|
||||||
|
////// FIXME: multiply
|
||||||
|
////let mut push: O::Unit = O::Unit::from(0u16);
|
||||||
|
////for _ in 0..index {
|
||||||
|
////push = push + size;
|
||||||
|
////}
|
||||||
|
////Push::$axis(push, Align::$align(Fixed::$axis(size, get_item(item, index))))
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////});
|
||||||
|
////impl_map_direction!(east, X, w);
|
||||||
|
////impl_map_direction!(south, Y, n);
|
||||||
|
////impl_map_direction!(west, X, e);
|
||||||
|
////impl_map_direction!(north, Y, s);
|
||||||
|
|
||||||
|
////impl<'a, O, A, B, I, F, G> Layout<O> for Map<O, A, B, I, F, G> where
|
||||||
|
////O: Screen,
|
||||||
|
////B: Layout<O>,
|
||||||
|
////I: Iterator<Item = A> + Send + Sync + 'a,
|
||||||
|
////F: Fn() -> I + Send + Sync + 'a,
|
||||||
|
////G: Fn(A, usize)->B + Send + Sync
|
||||||
|
////{
|
||||||
|
////fn layout (&self, area: XYWH<O::Unit>) -> XYWH<O::Unit> {
|
||||||
|
////let Self { get_iter, get_item, .. } = self;
|
||||||
|
////let mut index = 0;
|
||||||
|
////let XY(mut min_x, mut min_y) = area.centered();
|
||||||
|
////let XY(mut max_x, mut max_y) = area.center();
|
||||||
|
////for item in get_iter() {
|
||||||
|
////let XYWH(x, y, w, h) = get_item(item, index).layout(area);
|
||||||
|
////min_x = min_x.min(x);
|
||||||
|
////min_y = min_y.min(y);
|
||||||
|
////max_x = max_x.max(x + w);
|
||||||
|
////max_y = max_y.max(y + h);
|
||||||
|
////index += 1;
|
||||||
|
////}
|
||||||
|
////let w = max_x - min_x;
|
||||||
|
////let h = max_y - min_y;
|
||||||
|
//////[min_x.into(), min_y.into(), w.into(), h.into()].into()
|
||||||
|
////area.centered_xy([w.into(), h.into()])
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
|
||||||
|
////impl<'a, O, A, B, I, F, G> Draw<O> for Map<O, A, B, I, F, G> where
|
||||||
|
////O: Screen,
|
||||||
|
////B: Draw<O>,
|
||||||
|
////I: Iterator<Item = A> + Send + Sync + 'a,
|
||||||
|
////F: Fn() -> I + Send + Sync + 'a,
|
||||||
|
////G: Fn(A, usize)->B + Send + Sync
|
||||||
|
////{
|
||||||
|
////fn draw (&self, to: &mut O) {
|
||||||
|
////let Self { get_iter, get_item, .. } = self;
|
||||||
|
////let mut index = 0;
|
||||||
|
////let area = self.layout(to.area());
|
||||||
|
////for item in get_iter() {
|
||||||
|
////let item = get_item(item, index);
|
||||||
|
//////to.show(area.into(), &item);
|
||||||
|
////to.show(item.layout(area), &item);
|
||||||
|
////index += 1;
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////fn split_cardinal <N: Coord> (
|
||||||
|
////direction: Direction, area: XYWH<N>, a: N
|
||||||
|
////) -> (XYWH<N>, XYWH<N>) {
|
||||||
|
////let XYWH(x, y, w, h) = area;
|
||||||
|
////match direction {
|
||||||
|
////North => (XYWH(x, y.plus(h).minus(a), w, a), XYWH(x, y, w, h.minus(a))),
|
||||||
|
////South => (XYWH(x, y, w, a), XYWH(x, y.plus(a), w, h.minus(a))),
|
||||||
|
////East => (XYWH(x, y, a, h), XYWH(x.plus(a), y, w.minus(a), h)),
|
||||||
|
////West => (XYWH(x.plus(w).minus(a), y, a, h), XYWH(x, y, w.minus(a), h)),
|
||||||
|
////Above | Below => (area, area)
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////fn bsp_areas <N: Coord, A: TwoD<N>, B: TwoD<N>> (
|
||||||
|
////area: [N;4],
|
||||||
|
////direction: Direction,
|
||||||
|
////a: &A,
|
||||||
|
////b: &B,
|
||||||
|
////) -> [XYWH<N>;3] {
|
||||||
|
////let XYWH(x, y, w, h) = area;
|
||||||
|
////let WH(aw, ah) = a.layout(area).wh();
|
||||||
|
////let WH(bw, bh) = b.layout(match direction {
|
||||||
|
////South => XYWH(x, y + ah, w, h.minus(ah)),
|
||||||
|
////North => XYWH(x, y, w, h.minus(ah)),
|
||||||
|
////East => XYWH(x + aw, y, w.minus(aw), h),
|
||||||
|
////West => XYWH(x, y, w.minus(aw), h),
|
||||||
|
////Above => area,
|
||||||
|
////Below => area,
|
||||||
|
////}).wh();
|
||||||
|
////match direction {
|
||||||
|
////Above | Below => {
|
||||||
|
////let XYWH(x, y, w, h) = area.centered_xy([aw.max(bw), ah.max(bh)]);
|
||||||
|
////let a = XYWH((x + w/2.into()).minus(aw/2.into()), (y + h/2.into()).minus(ah/2.into()), aw, ah);
|
||||||
|
////let b = XYWH((x + w/2.into()).minus(bw/2.into()), (y + h/2.into()).minus(bh/2.into()), bw, bh);
|
||||||
|
////[a.into(), b.into(), XYWH(x, y, w, h)]
|
||||||
|
////},
|
||||||
|
////South => {
|
||||||
|
////let XYWH(x, y, w, h) = area.centered_xy([aw.max(bw), ah + bh]);
|
||||||
|
////let a = XYWH((x + w/2.into()).minus(aw/2.into()), y, aw, ah);
|
||||||
|
////let b = XYWH((x + w/2.into()).minus(bw/2.into()), y + ah, bw, bh);
|
||||||
|
////[a.into(), b.into(), XYWH(x, y, w, h)]
|
||||||
|
////},
|
||||||
|
////North => {
|
||||||
|
////let XYWH(x, y, w, h) = area.centered_xy([aw.max(bw), ah + bh]);
|
||||||
|
////let a = XYWH((x + (w/2.into())).minus(aw/2.into()), y + bh, aw, ah);
|
||||||
|
////let b = XYWH((x + (w/2.into())).minus(bw/2.into()), y, bw, bh);
|
||||||
|
////[a.into(), b.into(), XYWH(x, y, w, h)]
|
||||||
|
////},
|
||||||
|
////East => {
|
||||||
|
////let XYWH(x, y, w, h) = area.centered_xy([aw + bw, ah.max(bh)]);
|
||||||
|
////let a = XYWH(x, (y + h/2.into()).minus(ah/2.into()), aw, ah);
|
||||||
|
////let b = XYWH(x + aw, (y + h/2.into()).minus(bh/2.into()), bw, bh);
|
||||||
|
////[a.into(), b.into(), XYWH(x, y, w, h)]
|
||||||
|
////},
|
||||||
|
////West => {
|
||||||
|
////let XYWH(x, y, w, h) = area.centered_xy([aw + bw, ah.max(bh)]);
|
||||||
|
////let a = XYWH(x + bw, (y + h/2.into()).minus(ah/2.into()), aw, ah);
|
||||||
|
////let b = XYWH(x, (y + h/2.into()).minus(bh/2.into()), bw, bh);
|
||||||
|
////[a.into(), b.into(), XYWH(x, y, w, h)]
|
||||||
|
////},
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////pub fn iter
|
||||||
|
|
||||||
|
////impl<O: Screen, Head: Layout<O>, Tail: Layout<O>> Layout<O> for Bsp<Head, Tail> {
|
||||||
|
////fn layout_w (&self, area: XYWH<O::Unit>) -> O::Unit {
|
||||||
|
////match self.0 {
|
||||||
|
////Above | Below | North | South => self.1.layout_w(area).max(self.2.layout_w(area)),
|
||||||
|
////East | West => self.1.layout_w_min(area).plus(self.2.layout_w(area)),
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////fn layout_w_min (&self, area: XYWH<O::Unit>) -> O::Unit {
|
||||||
|
////match self.0 {
|
||||||
|
////Above | Below | North | South => self.1.layout_w_min(area).max(self.2.layout_w_min(area)),
|
||||||
|
////East | West => self.1.layout_w_min(area).plus(self.2.layout_w_min(area)),
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////fn layout_w_max (&self, area: XYWH<O::Unit>) -> O::Unit {
|
||||||
|
////match self.0 {
|
||||||
|
////Above | Below | North | South => self.1.layout_w_max(area).max(self.2.layout_w_max(area)),
|
||||||
|
////East | West => self.1.layout_w_max(area).plus(self.2.layout_w_max(area)),
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////fn layout_h (&self, area: XYWH<O::Unit>) -> O::Unit {
|
||||||
|
////match self.0 {
|
||||||
|
////Above | Below | East | West => self.1.layout_h(area).max(self.2.layout_h(area)),
|
||||||
|
////North | South => self.1.layout_h(area).plus(self.2.layout_h(area)),
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////fn layout_h_min (&self, area: XYWH<O::Unit>) -> O::Unit {
|
||||||
|
////match self.0 {
|
||||||
|
////Above | Below | East | West => self.1.layout_h_min(area).max(self.2.layout_h_min(area)),
|
||||||
|
////North | South => self.1.layout_h_min(area).plus(self.2.layout_h_min(area)),
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////fn layout_h_max (&self, area: XYWH<O::Unit>) -> O::Unit {
|
||||||
|
////match self.0 {
|
||||||
|
////Above | Below | North | South => self.1.layout_h_max(area).max(self.2.layout_h_max(area)),
|
||||||
|
////East | West => self.1.layout_h_max(area).plus(self.2.layout_h_max(area)),
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////fn layout (&self, area: XYWH<O::Unit>) -> XYWH<O::Unit> {
|
||||||
|
////bsp_areas(area, self.0, &self.1, &self.2)[2]
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////impl<O: Screen, Head: Draw<O>, Tail: Draw<O>> Draw<O> for Bsp<Head, Tail> {
|
||||||
|
////fn draw (&self, to: &mut O) {
|
||||||
|
////let [a, b, _] = bsp_areas(to.area(), self.0, &self.1, &self.2);
|
||||||
|
//////panic!("{a:?} {b:?}");
|
||||||
|
////if self.0 == Below {
|
||||||
|
////to.show(a, &self.1);
|
||||||
|
////to.show(b, &self.2);
|
||||||
|
////} else {
|
||||||
|
////to.show(b, &self.2);
|
||||||
|
////to.show(a, &self.1);
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
use crate::*;
|
pub(crate) use ::palette::Okhsl;
|
||||||
|
|
||||||
|
pub trait HasColor { fn color (&self) -> ItemColor; }
|
||||||
|
|
||||||
|
pub struct ItemColor {}
|
||||||
|
|
||||||
|
pub struct ItemTheme {}
|
||||||
|
|
||||||
#[macro_export] macro_rules! has_color {
|
#[macro_export] macro_rules! has_color {
|
||||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||||
|
|
@ -7,3 +13,7 @@ use crate::*;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rgb (r: u8, g: u8, b: u8) -> ItemColor {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
1160
src/draw.rs
1160
src/draw.rs
File diff suppressed because it is too large
Load diff
220
src/eval.rs
Normal file
220
src/eval.rs
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Interpret layout operation.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use tengri::*;
|
||||||
|
/// struct Target { xywh: XYWH<u16> /*platform-specific*/}
|
||||||
|
/// impl tengri::Out for Target {
|
||||||
|
/// type Unit = u16;
|
||||||
|
/// fn area (&self) -> XYWH<u16> { self.xywh }
|
||||||
|
/// fn area_mut (&mut self) -> &mut XYWH<u16> { &mut self.xywh }
|
||||||
|
/// fn show <'t, T: Draw<Self> + ?Sized> (&mut self, area: XYWH<u16>, content: &'t T) {}
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// struct State {/*app-specific*/}
|
||||||
|
/// impl<'b> Namespace<'b, bool> for State {}
|
||||||
|
/// impl<'b> Namespace<'b, u16> for State {}
|
||||||
|
/// impl Understand<Target, ()> for State {}
|
||||||
|
///
|
||||||
|
/// # fn main () -> tengri::Usually<()> {
|
||||||
|
/// let state = State {};
|
||||||
|
/// let mut target = Target { xywh: Default::default() };
|
||||||
|
/// eval_view(&state, &mut target, &"")?;
|
||||||
|
/// eval_view(&state, &mut target, &"(when true (text hello))")?;
|
||||||
|
/// eval_view(&state, &mut target, &"(either true (text hello) (text world))")?;
|
||||||
|
/// // TODO test all
|
||||||
|
/// # Ok(()) }
|
||||||
|
/// ```
|
||||||
|
#[cfg(feature = "dsl")] pub fn eval_view <'a, O: Screen + 'a, S> (
|
||||||
|
state: &S, output: &mut O, expr: &'a impl Expression
|
||||||
|
) -> Usually<bool> where
|
||||||
|
S: Understand<O, ()>
|
||||||
|
+ for<'b>Namespace<'b, bool>
|
||||||
|
+ for<'b>Namespace<'b, O::Unit>
|
||||||
|
{
|
||||||
|
// First element of expression is used for dispatch.
|
||||||
|
// Dispatch is proto-namespaced using separator character
|
||||||
|
let head = expr.head()?;
|
||||||
|
let mut frags = head.src()?.unwrap_or_default().split("/");
|
||||||
|
// The rest of the tokens in the expr are arguments.
|
||||||
|
// Their meanings depend on the dispatched operation
|
||||||
|
let args = expr.tail();
|
||||||
|
let arg0 = args.head();
|
||||||
|
let tail0 = args.tail();
|
||||||
|
let arg1 = tail0.head();
|
||||||
|
let tail1 = tail0.tail();
|
||||||
|
let arg2 = tail1.head();
|
||||||
|
// And we also have to do the above binding dance
|
||||||
|
// so that the Perhaps<token>s remain in scope.
|
||||||
|
match frags.next() {
|
||||||
|
|
||||||
|
Some("when") => output.place(&when(
|
||||||
|
state.namespace(arg0?)?.unwrap(),
|
||||||
|
move|output: &mut O|state.understand(output, &arg1)
|
||||||
|
)),
|
||||||
|
|
||||||
|
Some("either") => output.place(&either(
|
||||||
|
state.namespace(arg0?)?.unwrap(),
|
||||||
|
move|output: &mut O|state.understand(output, &arg1),
|
||||||
|
move|output: &mut O|state.understand(output, &arg2),
|
||||||
|
)),
|
||||||
|
|
||||||
|
Some("bsp") => output.place(&{
|
||||||
|
let a = move|output: &mut O|state.understand(output, &arg0);
|
||||||
|
let b = move|output: &mut O|state.understand(output, &arg1);
|
||||||
|
bsp(match frags.next() {
|
||||||
|
Some("n") => Alignment::N,
|
||||||
|
Some("s") => Alignment::S,
|
||||||
|
Some("e") => Alignment::E,
|
||||||
|
Some("w") => Alignment::W,
|
||||||
|
Some("a") => Alignment::A,
|
||||||
|
Some("b") => Alignment::B,
|
||||||
|
frag => unimplemented!("bsp/{frag:?}")
|
||||||
|
}, a, b)
|
||||||
|
}),
|
||||||
|
|
||||||
|
Some("align") => output.place(&{
|
||||||
|
let a = move|output: &mut O|state.understand(output, &arg0).unwrap();
|
||||||
|
align(match frags.next() {
|
||||||
|
Some("c") => Alignment::Center,
|
||||||
|
Some("n") => Alignment::N,
|
||||||
|
Some("s") => Alignment::S,
|
||||||
|
Some("e") => Alignment::E,
|
||||||
|
Some("w") => Alignment::W,
|
||||||
|
Some("x") => Alignment::X,
|
||||||
|
Some("y") => Alignment::Y,
|
||||||
|
frag => unimplemented!("align/{frag:?}")
|
||||||
|
}, a)
|
||||||
|
}),
|
||||||
|
|
||||||
|
Some("fill") => output.place(&{
|
||||||
|
let a = move|output: &mut O|state.understand(output, &arg0).unwrap();
|
||||||
|
match frags.next() {
|
||||||
|
Some("xy") | None => fill_wh(a),
|
||||||
|
Some("x") => fill_w(a),
|
||||||
|
Some("y") => fill_h(a),
|
||||||
|
frag => unimplemented!("fill/{frag:?}")
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
Some("exact") => output.place(&{
|
||||||
|
let axis = frags.next();
|
||||||
|
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("exact: unsupported axis {axis:?}") };
|
||||||
|
let cb = move|output: &mut O|state.understand(output, &arg).unwrap();
|
||||||
|
match axis {
|
||||||
|
Some("xy") | None => exact_wh(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
||||||
|
Some("x") => exact_w(state.namespace(arg0?)?.unwrap(), cb),
|
||||||
|
Some("y") => exact_h(state.namespace(arg0?)?.unwrap(), cb),
|
||||||
|
frag => unimplemented!("exact/{frag:?} ({expr:?}) ({head:?}) ({:?})",
|
||||||
|
head.src()?.unwrap_or_default().split("/").next())
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
Some("min") => output.place(&{
|
||||||
|
let axis = frags.next();
|
||||||
|
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("min: unsupported axis {axis:?}") };
|
||||||
|
let cb = move|output: &mut O|state.understand(output, &arg).unwrap();
|
||||||
|
match axis {
|
||||||
|
Some("xy") | None => min_wh(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
||||||
|
Some("x") => min_w(state.namespace(arg0?)?.unwrap(), cb),
|
||||||
|
Some("y") => min_h(state.namespace(arg0?)?.unwrap(), cb),
|
||||||
|
frag => unimplemented!("min/{frag:?}")
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
Some("max") => output.place(&{
|
||||||
|
let axis = frags.next();
|
||||||
|
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("max: unsupported axis {axis:?}") };
|
||||||
|
let cb = move|output: &mut O|state.understand(output, &arg).unwrap();
|
||||||
|
match axis {
|
||||||
|
Some("xy") | None => max_wh(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
||||||
|
Some("x") => max_w(state.namespace(arg0?)?.unwrap(), cb),
|
||||||
|
Some("y") => max_h(state.namespace(arg0?)?.unwrap(), cb),
|
||||||
|
frag => unimplemented!("max/{frag:?}")
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
Some("push") => output.place(&{
|
||||||
|
let axis = frags.next();
|
||||||
|
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("push: unsupported axis {axis:?}") };
|
||||||
|
let cb = move|output: &mut O|state.understand(output, &arg);
|
||||||
|
match axis {
|
||||||
|
Some("xy") | None => push_xy(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
||||||
|
Some("x") => push_x(state.namespace(arg0?)?.unwrap(), cb),
|
||||||
|
Some("y") => push_y(state.namespace(arg0?)?.unwrap(), cb),
|
||||||
|
frag => unimplemented!("push/{frag:?}")
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
_ => return Ok(false)
|
||||||
|
|
||||||
|
};
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interpret TUI-specific layout operation.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tengri::{Namespace, Understand, Tui, ratatui::prelude::Color};
|
||||||
|
///
|
||||||
|
/// struct State;
|
||||||
|
/// impl<'b> Namespace<'b, bool> for State {}
|
||||||
|
/// impl<'b> Namespace<'b, u16> for State {}
|
||||||
|
/// impl<'b> Namespace<'b, Color> for State {}
|
||||||
|
/// impl Understand<Tui, ()> for State {}
|
||||||
|
/// # fn main () -> tengri::Usually<()> {
|
||||||
|
/// let state = State;
|
||||||
|
/// let mut out = TuiOut::default();
|
||||||
|
/// tengri::eval_view_tui(&state, &mut out, "")?;
|
||||||
|
/// tengri::eval_view_tui(&state, &mut out, "text Hello world!")?;
|
||||||
|
/// tengri::eval_view_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?;
|
||||||
|
/// tengri::eval_view_tui(&state, &mut out, "bg (g 2) (text Hello world!)")?;
|
||||||
|
/// tengri::eval_view_tui(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?;
|
||||||
|
/// # Ok(()) }
|
||||||
|
/// ```
|
||||||
|
pub fn eval_view_tui <'a, S> (
|
||||||
|
state: &S, output: &mut Buffer, expr: impl Expression + 'a
|
||||||
|
) -> Usually<bool> where
|
||||||
|
S: Understand<TuiOut, ()>
|
||||||
|
+ for<'b>Namespace<'b, bool>
|
||||||
|
+ for<'b>Namespace<'b, u16>
|
||||||
|
+ for<'b>Namespace<'b, Color>
|
||||||
|
{
|
||||||
|
// See `tengri::eval_view`
|
||||||
|
let head = expr.head()?;
|
||||||
|
let mut frags = head.src()?.unwrap_or_default().split("/");
|
||||||
|
let args = expr.tail();
|
||||||
|
let arg0 = args.head();
|
||||||
|
let tail0 = args.tail();
|
||||||
|
let arg1 = tail0.head();
|
||||||
|
let tail1 = tail0.tail();
|
||||||
|
let arg2 = tail1.head();
|
||||||
|
match frags.next() {
|
||||||
|
|
||||||
|
Some("text") => {
|
||||||
|
if let Some(src) = args?.src()? { output.place(&src) }
|
||||||
|
},
|
||||||
|
|
||||||
|
Some("fg") => {
|
||||||
|
let arg0 = arg0?.expect("fg: expected arg 0 (color)");
|
||||||
|
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color"));
|
||||||
|
let thunk = Thunk::new(move|output: &mut Buffer|state.understand(output, &arg1).unwrap());
|
||||||
|
output.place(&TuiOut::fg(color, thunk))
|
||||||
|
},
|
||||||
|
|
||||||
|
Some("bg") => {
|
||||||
|
//panic!("expr: {expr:?}\nhead: {head:?}\nfrags: {frags:?}\nargs: {args:?}\narg0: {arg0:?}\ntail0: {tail0:?}\narg1: {arg1:?}\ntail1: {tail1:?}\narg2: {arg2:?}");
|
||||||
|
//panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}");;
|
||||||
|
//panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}");
|
||||||
|
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
|
||||||
|
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color"));
|
||||||
|
let thunk = Thunk::new(move|output: &mut Buffer|state.understand(output, &arg1).unwrap());
|
||||||
|
output.place(&TuiOut::bg(color, thunk))
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => return Ok(false)
|
||||||
|
|
||||||
|
};
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
71
src/lib.rs
Normal file
71
src/lib.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
#![feature(anonymous_lifetime_in_impl_trait)]
|
||||||
|
#![feature(associated_type_defaults)]
|
||||||
|
#![feature(const_default)]
|
||||||
|
#![feature(const_option_ops)]
|
||||||
|
#![feature(const_precise_live_drops)]
|
||||||
|
#![feature(const_trait_impl)]
|
||||||
|
#![feature(impl_trait_in_assoc_type)]
|
||||||
|
#![feature(step_trait)]
|
||||||
|
#![feature(trait_alias)]
|
||||||
|
#![feature(type_alias_impl_trait)]
|
||||||
|
#![feature(type_changing_struct_update)]
|
||||||
|
pub extern crate atomic_float;
|
||||||
|
pub extern crate palette;
|
||||||
|
pub extern crate better_panic;
|
||||||
|
pub extern crate unicode_width;
|
||||||
|
#[cfg(test)] #[macro_use] pub extern crate proptest;
|
||||||
|
|
||||||
|
pub(crate) use ::{
|
||||||
|
atomic_float::AtomicF64,
|
||||||
|
std::fmt::{Debug, Display},
|
||||||
|
std::ops::{Add, Sub, Mul, Div},
|
||||||
|
std::sync::{Arc, RwLock},
|
||||||
|
std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::*},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "lang")] pub extern crate dizzle as lang;
|
||||||
|
#[cfg(feature = "lang")] pub use ::dizzle::{self, Usually, Perhaps, impl_default};
|
||||||
|
|
||||||
|
#[cfg(feature = "time")] pub mod time;
|
||||||
|
#[cfg(feature = "play")] pub mod play;
|
||||||
|
#[cfg(feature = "sing")] pub extern crate jack;
|
||||||
|
#[cfg(feature = "sing")] pub mod sing;
|
||||||
|
#[cfg(feature = "sing")] pub use ::jack::{*, contrib::{*, ClosureProcessHandler}};
|
||||||
|
|
||||||
|
#[cfg(feature = "draw")] pub mod draw;
|
||||||
|
#[cfg(feature = "draw")] pub mod color;
|
||||||
|
#[cfg(feature = "text")] pub mod text;
|
||||||
|
#[cfg(feature = "tui")] pub mod tui;
|
||||||
|
#[cfg(feature = "tui")] pub extern crate ratatui;
|
||||||
|
#[cfg(feature = "tui")] pub extern crate crossterm;
|
||||||
|
|
||||||
|
/// Define a trait an implement it for various mutation-enabled wrapper types. */
|
||||||
|
#[macro_export] macro_rules! flex_trait_mut (
|
||||||
|
($Trait:ident $(<$($A:ident:$T:ident),+>)? {
|
||||||
|
$(fn $fn:ident (&mut $self:ident $(, $arg:ident:$ty:ty)*) -> $ret:ty $body:block)*
|
||||||
|
})=>{
|
||||||
|
pub trait $Trait $(<$($A: $T),+>)? {
|
||||||
|
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret $body)*
|
||||||
|
}
|
||||||
|
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &mut _T_ {
|
||||||
|
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { (*$self).$fn($($arg),*) })*
|
||||||
|
}
|
||||||
|
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for Option<_T_> {
|
||||||
|
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret {
|
||||||
|
if let Some(this) = $self { this.$fn($($arg),*) } else { Ok(None) }
|
||||||
|
})*
|
||||||
|
}
|
||||||
|
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Mutex<_T_> {
|
||||||
|
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.get_mut().unwrap().$fn($($arg),*) })*
|
||||||
|
}
|
||||||
|
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::Mutex<_T_>> {
|
||||||
|
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.lock().unwrap().$fn($($arg),*) })*
|
||||||
|
}
|
||||||
|
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::RwLock<_T_> {
|
||||||
|
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.write().unwrap().$fn($($arg),*) })*
|
||||||
|
}
|
||||||
|
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::RwLock<_T_>> {
|
||||||
|
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.write().unwrap().$fn($($arg),*) })*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
);
|
||||||
59
src/play.rs
59
src/play.rs
|
|
@ -1,9 +1,11 @@
|
||||||
use crate::*;
|
use crate::{*, time::*, lang::*};
|
||||||
|
use ::std::{thread::JoinHandle, time::Duration};
|
||||||
|
#[cfg(feature = "tui")] use ::crossterm::event::poll;
|
||||||
|
|
||||||
#[derive(Clone)] pub struct Exit(Arc<AtomicBool>);
|
#[derive(Clone)] pub struct Exit(Arc<AtomicBool>);
|
||||||
|
|
||||||
impl Exit {
|
impl Exit {
|
||||||
pub fn run <T> (run: impl Fn()->Usually<T>) -> Usually<T> {
|
pub fn run <T> (run: impl Fn(Self)->Usually<T>) -> Usually<T> {
|
||||||
run(Self(Arc::new(AtomicBool::new(false))))
|
run(Self(Arc::new(AtomicBool::new(false))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -34,7 +36,7 @@ impl Thread {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawn a TUI thread that runs `callt least one, then repeats
|
/// Spawn a thread that runs `call` least one, then repeats
|
||||||
/// until `exit`, sleeping for `time` msec after every iteration.
|
/// until `exit`, sleeping for `time` msec after every iteration.
|
||||||
pub fn new_sleep <F> (
|
pub fn new_sleep <F> (
|
||||||
exit: Arc<AtomicBool>, time: Duration, call: F
|
exit: Arc<AtomicBool>, time: Duration, call: F
|
||||||
|
|
@ -44,9 +46,9 @@ impl Thread {
|
||||||
Self::new(exit, move |perf| { let _ = call(perf); std::thread::sleep(time); })
|
Self::new(exit, move |perf| { let _ = call(perf); std::thread::sleep(time); })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawn a TUI thread that runs `callt least one, then repeats
|
/// Spawn a thread that uses [crossterm::event::poll]
|
||||||
/// until `exit`, using polling to run every `time` msec.
|
/// to run `call` every `time` msec.
|
||||||
pub fn new_poll <F> (
|
#[cfg(feature = "tui")]pub fn new_poll <F> (
|
||||||
exit: Arc<AtomicBool>, time: Duration, call: F
|
exit: Arc<AtomicBool>, time: Duration, call: F
|
||||||
) -> Result<Self, std::io::Error>
|
) -> Result<Self, std::io::Error>
|
||||||
where F: Fn(&PerfModel)->() + Send + Sync + 'static
|
where F: Fn(&PerfModel)->() + Send + Sync + 'static
|
||||||
|
|
@ -98,48 +100,3 @@ impl Thread {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<S, T: Action<S>> Action<S> for Option<T> { fn action (&self, _: &mut S) -> Perhaps<Self> { Ok(None) } }
|
|
||||||
|
|
||||||
impl_default!(PerfModel: Self {
|
|
||||||
enabled: true,
|
|
||||||
clock: quanta::Clock::new(),
|
|
||||||
used: Default::default(),
|
|
||||||
window: Default::default(),
|
|
||||||
});
|
|
||||||
|
|
||||||
impl PerfModel {
|
|
||||||
pub fn get_t0 (&self) -> Option<u64> {
|
|
||||||
if self.enabled {
|
|
||||||
Some(self.clock.raw())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn get_t1 (&self, t0: Option<u64>) -> Option<std::time::Duration> {
|
|
||||||
if let Some(t0) = t0 {
|
|
||||||
if self.enabled {
|
|
||||||
Some(self.clock.delta(t0, self.clock.raw()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn update (&self, t0: Option<u64>, microseconds: f64) {
|
|
||||||
if let Some(t0) = t0 {
|
|
||||||
let t1 = self.clock.raw();
|
|
||||||
self.used.store(self.clock.delta_as_nanos(t0, t1) as f64, Relaxed);
|
|
||||||
self.window.store(microseconds, Relaxed,);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn percentage (&self) -> Option<f64> {
|
|
||||||
let window = self.window.load(Relaxed) * 1000.0;
|
|
||||||
if window > 0.0 {
|
|
||||||
let used = self.used.load(Relaxed);
|
|
||||||
Some(100.0 * used / window)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
106
src/sing.rs
106
src/sing.rs
|
|
@ -1,7 +1,27 @@
|
||||||
use crate::*;
|
use crate::{*, time::PerfModel};
|
||||||
use ::jack::*;
|
use ::jack::*;
|
||||||
use JackState::*;
|
use JackState::*;
|
||||||
|
|
||||||
|
/// Trait for thing that has a JACK process callback.
|
||||||
|
pub trait Audio {
|
||||||
|
/// Handle a JACK event.
|
||||||
|
fn handle (&mut self, _event: JackEvent) {}
|
||||||
|
/// Projecss a JACK chunk.
|
||||||
|
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
/// The JACK process callback function passed to the server.
|
||||||
|
fn callback (
|
||||||
|
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
|
||||||
|
) -> Control where Self: Sized {
|
||||||
|
if let Ok(mut state) = state.write() {
|
||||||
|
state.process(client, scope)
|
||||||
|
} else {
|
||||||
|
Control::Quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Wraps [JackState], and through it [jack::Client] when connected.
|
/// Wraps [JackState], and through it [jack::Client] when connected.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -199,3 +219,87 @@ impl JackPerfModel for PerfModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Things that can provide a [jack::Client] reference.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tengri::{Jack, HasJack};
|
||||||
|
///
|
||||||
|
/// let jack: &Jack = Jacked::default().jack();
|
||||||
|
///
|
||||||
|
/// #[derive(Default)] struct Jacked<'j>(Jack<'j>);
|
||||||
|
///
|
||||||
|
/// impl<'j> HasJack<'j> for Jacked<'j> {
|
||||||
|
/// fn jack (&self) -> &Jack<'j> { &self.0 }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub trait HasJack<'j>: Send + Sync {
|
||||||
|
/// Return the internal [jack::Client] handle
|
||||||
|
/// that lets you call the JACK API.
|
||||||
|
fn jack (&self) -> &Jack<'j>;
|
||||||
|
fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
|
||||||
|
self.jack().with_client(op)
|
||||||
|
}
|
||||||
|
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
|
||||||
|
self.with_client(|client|client.port_by_name(name))
|
||||||
|
}
|
||||||
|
fn port_by_id (&self, id: u32) -> Option<Port<Unowned>> {
|
||||||
|
self.with_client(|c|c.port_by_id(id))
|
||||||
|
}
|
||||||
|
fn register_port <PS: PortSpec + Default> (&self, name: impl AsRef<str>) -> Usually<Port<PS>> {
|
||||||
|
self.with_client(|client|Ok(client.register_port(name.as_ref(), PS::default())?))
|
||||||
|
}
|
||||||
|
fn sync_lead (&self, enable: bool, callback: impl Fn(TimebaseInfo)->jack::contrib::Position)
|
||||||
|
-> Usually<()>
|
||||||
|
{
|
||||||
|
if enable {
|
||||||
|
self.with_client(|client|match client.register_timebase_callback(false, callback) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(e)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn sync_follow (&self, _enable: bool) -> Usually<()> {
|
||||||
|
// TODO: sync follow
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait JackPerfModel {
|
||||||
|
fn update_from_jack_scope (&self, t0: Option<u64>, scope: &ProcessScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement [Audio]: provide JACK callbacks.
|
||||||
|
#[macro_export] macro_rules! impl_audio {
|
||||||
|
|
||||||
|
(|
|
||||||
|
$self1:ident:
|
||||||
|
$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident
|
||||||
|
|$cb:expr$(;|$self2:ident,$e:ident|$cb2:expr)?) => {
|
||||||
|
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
|
||||||
|
#[inline] fn process (&mut $self1, $c: &Client, $s: &ProcessScope) -> Control { $cb }
|
||||||
|
$(#[inline] fn handle (&mut $self2, $e: JackEvent) { $cb2 })?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($Struct:ident: $process:ident, $handle:ident) => {
|
||||||
|
impl Audio for $Struct {
|
||||||
|
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
|
||||||
|
$process(self, c, s)
|
||||||
|
}
|
||||||
|
#[inline] fn handle (&mut self, e: JackEvent) {
|
||||||
|
$handle(self, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($Struct:ident: $process:ident) => {
|
||||||
|
impl Audio for $Struct {
|
||||||
|
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
|
||||||
|
$process(self, c, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
#![feature(anonymous_lifetime_in_impl_trait)]
|
|
||||||
#![feature(associated_type_defaults)]
|
|
||||||
#![feature(const_default)]
|
|
||||||
#![feature(const_option_ops)]
|
|
||||||
#![feature(const_precise_live_drops)]
|
|
||||||
#![feature(const_trait_impl)]
|
|
||||||
#![feature(impl_trait_in_assoc_type)]
|
|
||||||
#![feature(step_trait)]
|
|
||||||
#![feature(trait_alias)]
|
|
||||||
#![feature(type_alias_impl_trait)]
|
|
||||||
#![feature(type_changing_struct_update)]
|
|
||||||
pub extern crate atomic_float;
|
|
||||||
pub extern crate palette;
|
|
||||||
pub extern crate better_panic;
|
|
||||||
pub extern crate unicode_width;
|
|
||||||
mod tengri_macros;
|
|
||||||
mod tengri_struct; pub use self::tengri_struct::*;
|
|
||||||
mod tengri_trait; pub use self::tengri_trait::*;
|
|
||||||
mod tengri_impl; pub use self::tengri_impl::*;
|
|
||||||
|
|
||||||
#[cfg(test)] pub(crate) use proptest_derive::Arbitrary;
|
|
||||||
|
|
||||||
pub(crate) use ::{
|
|
||||||
atomic_float::AtomicF64,
|
|
||||||
palette::{*, convert::*, okhsl::*},
|
|
||||||
better_panic::{Settings, Verbosity},
|
|
||||||
unicode_width::*,
|
|
||||||
std::{
|
|
||||||
io::{stdout, Stdout, Write},
|
|
||||||
sync::{Arc, Weak, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}},
|
|
||||||
fmt::{Debug, Display},
|
|
||||||
ops::{Add, Sub, Mul, Div},
|
|
||||||
marker::PhantomData,
|
|
||||||
time::Duration,
|
|
||||||
thread::{spawn, JoinHandle}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "lang")] extern crate dizzle;
|
|
||||||
#[cfg(feature = "lang")] pub use ::dizzle::self as lang;
|
|
||||||
|
|
||||||
#[cfg(feature = "draw")] pub mod draw;
|
|
||||||
|
|
||||||
#[cfg(feature = "tui")] pub extern crate ratatui;
|
|
||||||
#[cfg(feature = "tui")] pub extern crate crossterm;
|
|
||||||
#[cfg(feature = "tui")] pub(crate) use ::{
|
|
||||||
ratatui::{
|
|
||||||
prelude::{Color, Style, Buffer, Position},
|
|
||||||
style::{Stylize, Modifier, Color::*},
|
|
||||||
backend::{Backend, CrosstermBackend, ClearType},
|
|
||||||
layout::{Size, Rect},
|
|
||||||
buffer::Cell
|
|
||||||
},
|
|
||||||
crossterm::{
|
|
||||||
ExecutableCommand,
|
|
||||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
|
|
||||||
event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "sing")] pub extern crate jack;
|
|
||||||
#[cfg(feature = "sing")] pub mod sing;
|
|
||||||
#[cfg(feature = "sing")] pub use ::jack::{*, contrib::{*, ClosureProcessHandler}};
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use Direction::*;
|
|
||||||
use rand::{thread_rng, distributions::uniform::UniformSampler};
|
|
||||||
|
|
||||||
mod xywh {
|
|
||||||
use crate::*;
|
|
||||||
}
|
|
||||||
|
|
@ -1,230 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
pub use self::spatial::*; mod spatial {
|
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// A widget that tracks its rendered width and height.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let measure = tengri::Measure::<u16>::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Default)] pub struct Measure<N: Coord> {
|
|
||||||
pub x: Arc<AtomicUsize>,
|
|
||||||
pub y: Arc<AtomicUsize>,
|
|
||||||
pub __: PhantomData<N>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Increment X and/or Y coordinate.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let pushed = tengri::Push::XY(2, 2, "Hello");
|
|
||||||
/// ```
|
|
||||||
pub enum Push<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
|
||||||
|
|
||||||
/// Decrement X and/or Y coordinate.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let pulled = tengri::Pull::XY(2, 2, "Hello");
|
|
||||||
/// ```
|
|
||||||
pub enum Pull<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
|
||||||
|
|
||||||
/// Set the content to fill the container.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let filled = tengri::Fill::XY("Hello");
|
|
||||||
/// ```
|
|
||||||
pub enum Fill<A> { X(A), Y(A), XY(A) }
|
|
||||||
|
|
||||||
/// Set fixed size for content.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let fixed = tengri::Fixed::XY(3, 5, "Hello"); // 3x5
|
|
||||||
/// ```
|
|
||||||
pub enum Fixed<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
|
||||||
|
|
||||||
/// Decrease the width and/or height of the content.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let shrunk = tengri::Shrink::XY(2, 0, "Hello"); // 1x1
|
|
||||||
/// ```
|
|
||||||
pub enum Shrink<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
|
||||||
|
|
||||||
/// Increaase the width and/or height of the content.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let expanded = tengri::Expand::XY(5, 3, "HELLO"); // 15x3
|
|
||||||
/// ```
|
|
||||||
pub enum Expand<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
|
||||||
|
|
||||||
/// Align position of inner area to middle, side, or corner of outer area.
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use ::tengri::*;
|
|
||||||
/// let area = XYWH(10u16, 10, 20, 20);
|
|
||||||
/// fn test (area: XYWH<u16>, item: &impl Draw<Tui>, expected: [u16;4]) {
|
|
||||||
/// //assert_eq!(Lay::layout(item, area), expected);
|
|
||||||
/// //assert_eq!(Draw::layout(item, area), expected);
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// let four = ||Fixed::XY(4, 4, "");
|
|
||||||
/// test(area, &Align::nw(four()), [10, 10, 4, 4]);
|
|
||||||
/// test(area, &Align::n(four()), [18, 10, 4, 4]);
|
|
||||||
/// test(area, &Align::ne(four()), [26, 10, 4, 4]);
|
|
||||||
/// test(area, &Align::e(four()), [26, 18, 4, 4]);
|
|
||||||
/// test(area, &Align::se(four()), [26, 26, 4, 4]);
|
|
||||||
/// test(area, &Align::s(four()), [18, 26, 4, 4]);
|
|
||||||
/// test(area, &Align::sw(four()), [10, 26, 4, 4]);
|
|
||||||
/// test(area, &Align::w(four()), [10, 18, 4, 4]);
|
|
||||||
///
|
|
||||||
/// let two_by_four = ||Fixed::XY(4, 2, "");
|
|
||||||
/// test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]);
|
|
||||||
/// test(area, &Align::n(two_by_four()), [18, 10, 4, 2]);
|
|
||||||
/// test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]);
|
|
||||||
/// test(area, &Align::e(two_by_four()), [26, 19, 4, 2]);
|
|
||||||
/// test(area, &Align::se(two_by_four()), [26, 28, 4, 2]);
|
|
||||||
/// test(area, &Align::s(two_by_four()), [18, 28, 4, 2]);
|
|
||||||
/// test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]);
|
|
||||||
/// test(area, &Align::w(two_by_four()), [10, 19, 4, 2]);
|
|
||||||
/// ```
|
|
||||||
pub struct Align<T>(pub Alignment, pub T);
|
|
||||||
|
|
||||||
// TODO DOCUMENTME
|
|
||||||
pub enum Pad<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
|
||||||
|
|
||||||
// TODO DOCUMENTME
|
|
||||||
pub struct Bordered<S, W>(pub bool, pub S, pub W);
|
|
||||||
|
|
||||||
// TODO DOCUMENTME
|
|
||||||
pub struct Border<S>(pub bool, pub S);
|
|
||||||
|
|
||||||
/// TODO DOCUMENTME
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use tengri::{Bounded, XYWH};
|
|
||||||
/// let bounded: Bounded<tengri::Tui, _> = Bounded(0, 0 ,0 ,0 ,"");
|
|
||||||
/// ```
|
|
||||||
pub struct Bounded<N: Coord, D>(N, N, N, N, pub D);
|
|
||||||
|
|
||||||
/// Draws items from an iterator.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// // FIXME let map = tengri::Map(||[].iter(), |_|{});
|
|
||||||
/// ```
|
|
||||||
pub struct Map<O, A, B, I, F, G>
|
|
||||||
where
|
|
||||||
I: Iterator<Item = A> + Send + Sync,
|
|
||||||
F: Fn() -> I + Send + Sync,
|
|
||||||
{
|
|
||||||
/// Function that returns iterator over stacked components
|
|
||||||
pub get_iter: F,
|
|
||||||
/// Function that returns each stacked component
|
|
||||||
pub get_item: G,
|
|
||||||
|
|
||||||
pub __: PhantomData<(O, B)>,
|
|
||||||
}
|
|
||||||
/// A color in OKHSL and RGB representations.
|
|
||||||
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor {
|
|
||||||
pub okhsl: Okhsl<f32>,
|
|
||||||
#[cfg(feature = "tui")] pub rgb: Color,
|
|
||||||
}
|
|
||||||
/// A color in OKHSL and RGB with lighter and darker variants.
|
|
||||||
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemTheme {
|
|
||||||
pub base: ItemColor,
|
|
||||||
pub light: ItemColor,
|
|
||||||
pub lighter: ItemColor,
|
|
||||||
pub lightest: ItemColor,
|
|
||||||
pub dark: ItemColor,
|
|
||||||
pub darker: ItemColor,
|
|
||||||
pub darkest: ItemColor,
|
|
||||||
}
|
|
||||||
// TODO DOCUMENTME
|
|
||||||
pub struct FieldH<Theme, Label, Value>(pub Theme, pub Label, pub Value);
|
|
||||||
// TODO DOCUMENTME
|
|
||||||
pub struct FieldV<Theme, Label, Value>(pub Theme, pub Label, pub Value);
|
|
||||||
// TODO:
|
|
||||||
pub struct Field<C, T, U> {
|
|
||||||
pub direction: Direction,
|
|
||||||
pub label: Option<T>,
|
|
||||||
pub label_fg: Option<C>,
|
|
||||||
pub label_bg: Option<C>,
|
|
||||||
pub label_align: Option<Direction>,
|
|
||||||
pub value: Option<U>,
|
|
||||||
pub value_fg: Option<C>,
|
|
||||||
pub value_bg: Option<C>,
|
|
||||||
pub value_align: Option<Direction>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:wqa
|
|
||||||
#[cfg(feature = "tui")] pub use self::terminal::*;
|
|
||||||
#[cfg(feature = "tui")] mod terminal {
|
|
||||||
use crate::*;
|
|
||||||
/// TUI input loop event.
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
|
||||||
pub struct TuiEvent(pub Event);
|
|
||||||
/// TUI key spec.
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
|
||||||
pub struct TuiKey(pub Option<KeyCode>, pub KeyModifiers);
|
|
||||||
/// TUI buffer sized by `usize` instead of `u16`.
|
|
||||||
#[derive(Default)] pub struct BigBuffer {
|
|
||||||
pub width: usize,
|
|
||||||
pub height: usize,
|
|
||||||
pub content: Vec<Cell>
|
|
||||||
}
|
|
||||||
pub struct Foreground<Color, Item>(pub Color, pub Item);
|
|
||||||
pub struct Background<Color, Item>(pub Color, pub Item);
|
|
||||||
pub struct Modify<T>(pub bool, pub Modifier, pub T);
|
|
||||||
pub struct Styled<T>(pub Option<Style>, pub T);
|
|
||||||
/// A cell that takes up 3 rows on its own,
|
|
||||||
/// but stacks, giving (N+1)*2 rows per N cells.
|
|
||||||
pub struct Phat<T> { pub width: u16, pub height: u16, pub content: T, pub colors: [Color;4], }
|
|
||||||
/// A three-column layout.
|
|
||||||
pub struct Tryptich<A, B, C> {
|
|
||||||
pub top: bool,
|
|
||||||
pub h: u16,
|
|
||||||
pub left: (u16, A),
|
|
||||||
pub middle: (u16, B),
|
|
||||||
pub right: (u16, C),
|
|
||||||
}
|
|
||||||
/// Repeat a string, e.g. for background fill
|
|
||||||
pub enum Repeat<'a> { X(&'a str), Y(&'a str), XY(&'a str) }
|
|
||||||
/// Scroll indicator
|
|
||||||
pub enum Scrollbar {
|
|
||||||
/// Horizontal scrollbar
|
|
||||||
X { offset: usize, length: usize, total: usize, },
|
|
||||||
/// Vertical scrollbar
|
|
||||||
Y { offset: usize, length: usize, total: usize, }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use self::textual::*;
|
|
||||||
mod textual {
|
|
||||||
/// Displays an owned [str]-like with fixed maximum width.
|
|
||||||
///
|
|
||||||
/// Width is computed using [unicode_width].
|
|
||||||
pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
|
|
||||||
/// Displays a borrowed [str]-like with fixed maximum width
|
|
||||||
///
|
|
||||||
/// Width is computed using [unicode_width].
|
|
||||||
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "jack")] pub use self::aural::*;
|
|
||||||
#[cfg(feature = "jack")] mod aural {
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use self::temporal::*; mod temporal {
|
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Performance counter
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PerfModel {
|
|
||||||
pub clock: quanta::Clock,
|
|
||||||
/// Measurement has a small cost. Disable it here.
|
|
||||||
pub enabled: bool,
|
|
||||||
// In nanoseconds. Time used by last iteration.
|
|
||||||
pub used: AtomicF64,
|
|
||||||
// In microseconds. Max prescribed time for iteration (frame, chunk...).
|
|
||||||
pub window: AtomicF64,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use Direction::*;
|
|
||||||
use proptest::{prelude::*, option::of};
|
|
||||||
use proptest_derive::Arbitrary;
|
|
||||||
|
|
||||||
proptest! {
|
|
||||||
#[test] fn proptest_direction (
|
|
||||||
d in prop_oneof![
|
|
||||||
Just(North), Just(South),
|
|
||||||
Just(East), Just(West),
|
|
||||||
Just(Above), Just(Below)
|
|
||||||
],
|
|
||||||
x in u16::MIN..u16::MAX,
|
|
||||||
y in u16::MIN..u16::MAX,
|
|
||||||
w in u16::MIN..u16::MAX,
|
|
||||||
h in u16::MIN..u16::MAX,
|
|
||||||
a in u16::MIN..u16::MAX,
|
|
||||||
) {
|
|
||||||
let _ = d.split_fixed(XYWH(x, y, w, h), a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
proptest! {
|
|
||||||
#[test] fn proptest_area (
|
|
||||||
x in u16::MIN..u16::MAX,
|
|
||||||
y in u16::MIN..u16::MAX,
|
|
||||||
w in u16::MIN..u16::MAX,
|
|
||||||
h in u16::MIN..u16::MAX,
|
|
||||||
a in u16::MIN..u16::MAX,
|
|
||||||
b in u16::MIN..u16::MAX,
|
|
||||||
) {
|
|
||||||
let _: XYWH<u16> = XYWH::zero();
|
|
||||||
//let _: XYWH<u16> = XYWH::from_position([a, b]);
|
|
||||||
//let _: XYWH<u16> = XYWH::from_size([a, b]);
|
|
||||||
let area: XYWH<u16> = XYWH(x, y, w, h);
|
|
||||||
//let _ = area.expect_min(a, b);
|
|
||||||
let _ = area.xy();
|
|
||||||
let _ = area.wh();
|
|
||||||
//let _ = area.xywh();
|
|
||||||
let _ = area.clipped_h(a);
|
|
||||||
let _ = area.clipped_w(b);
|
|
||||||
let _ = area.clipped(WH(a, b));
|
|
||||||
//let _ = area.set_w(a);
|
|
||||||
//let _ = area.set_h(b);
|
|
||||||
let _ = area.x2();
|
|
||||||
let _ = area.y2();
|
|
||||||
let _ = area.lrtb();
|
|
||||||
let _ = area.center();
|
|
||||||
let _ = area.centered();
|
|
||||||
let _ = area.centered_x(a);
|
|
||||||
let _ = area.centered_y(b);
|
|
||||||
let _ = area.centered_xy([a, b]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
proptest! {
|
|
||||||
#[test] fn proptest_size (
|
|
||||||
x in u16::MIN..u16::MAX,
|
|
||||||
y in u16::MIN..u16::MAX,
|
|
||||||
a in u16::MIN..u16::MAX,
|
|
||||||
b in u16::MIN..u16::MAX,
|
|
||||||
) {
|
|
||||||
let size = WH(x, y);
|
|
||||||
let _ = size.w();
|
|
||||||
let _ = size.h();
|
|
||||||
let _ = size.wh();
|
|
||||||
let _ = size.clip_w(a);
|
|
||||||
let _ = size.clip_h(b);
|
|
||||||
//let _ = size.expect_min(a, b);
|
|
||||||
//let _ = size.to_area_pos();
|
|
||||||
//let _ = size.to_area_size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! test_op_transform {
|
|
||||||
($fn:ident, $Op:ident) => {
|
|
||||||
proptest! {
|
|
||||||
#[test] fn $fn (
|
|
||||||
op_x in of(u16::MIN..u16::MAX),
|
|
||||||
op_y in of(u16::MIN..u16::MAX),
|
|
||||||
content in "\\PC*",
|
|
||||||
x in u16::MIN..u16::MAX,
|
|
||||||
y in u16::MIN..u16::MAX,
|
|
||||||
w in u16::MIN..u16::MAX,
|
|
||||||
h in u16::MIN..u16::MAX,
|
|
||||||
) {
|
|
||||||
if let Some(op) = match (op_x, op_y) {
|
|
||||||
(Some(x), Some(y)) => Some($Op::XY(x, y, content)),
|
|
||||||
(Some(x), None) => Some($Op::X(x, content)),
|
|
||||||
(None, Some(y)) => Some($Op::Y(y, content)),
|
|
||||||
_ => None
|
|
||||||
} {
|
|
||||||
//assert_eq!(Content::layout(&op, [x, y, w, h]),
|
|
||||||
//Draw::layout(&op, [x, y, w, h]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
test_op_transform!(proptest_op_fixed, Fixed);
|
|
||||||
test_op_transform!(proptest_op_min, Min);
|
|
||||||
test_op_transform!(proptest_op_max, Max);
|
|
||||||
test_op_transform!(proptest_op_push, Push);
|
|
||||||
test_op_transform!(proptest_op_pull, Pull);
|
|
||||||
test_op_transform!(proptest_op_shrink, Shrink);
|
|
||||||
test_op_transform!(proptest_op_expand, Expand);
|
|
||||||
test_op_transform!(proptest_op_padding, Pad);
|
|
||||||
|
|
||||||
proptest! {
|
|
||||||
#[test] fn proptest_op_bsp (
|
|
||||||
d in prop_oneof![
|
|
||||||
Just(North), Just(South),
|
|
||||||
Just(East), Just(West),
|
|
||||||
Just(Above), Just(Below)
|
|
||||||
],
|
|
||||||
a in "\\PC*",
|
|
||||||
b in "\\PC*",
|
|
||||||
x in u16::MIN..u16::MAX,
|
|
||||||
y in u16::MIN..u16::MAX,
|
|
||||||
w in u16::MIN..u16::MAX,
|
|
||||||
h in u16::MIN..u16::MAX,
|
|
||||||
) {
|
|
||||||
let bsp = Bsp(d, a, b);
|
|
||||||
//assert_eq!(
|
|
||||||
//Content::layout(&bsp, [x, y, w, h]),
|
|
||||||
//Draw::layout(&bsp, [x, y, w, h]),
|
|
||||||
//);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//#[test] fn test_tui_engine () -> Usually<()> {
|
|
||||||
////use std::sync::{Arc, RwLock};
|
|
||||||
//struct TestComponent(String);
|
|
||||||
//impl Draw<TuiOut> for TestComponent {
|
|
||||||
//fn draw (&self, _to: &mut TuiOut) {}
|
|
||||||
//}
|
|
||||||
//impl Handle<TuiIn> for TestComponent {
|
|
||||||
//fn handle (&mut self, _from: &TuiIn) -> Perhaps<bool> { Ok(None) }
|
|
||||||
//}
|
|
||||||
//let engine = Tui::new(Box::<&mut Vec<u8>>::new(vec![0u8;0].as_mut()))?;
|
|
||||||
//let state = engine.run(false, &Arc::new(RwLock::new(TestComponent("hello world".into()))))?;
|
|
||||||
//state.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
//Ok(())
|
|
||||||
//}
|
|
||||||
|
|
@ -1,341 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Output target.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use tengri::*;
|
|
||||||
///
|
|
||||||
/// struct TestOut(impl TwoD<u16>);
|
|
||||||
///
|
|
||||||
/// impl tengri::Out for TestOut {
|
|
||||||
/// type Unit = u16;
|
|
||||||
/// fn area (&self) -> impl TwoD<u16> { self.0 }
|
|
||||||
/// fn area_mut (&mut self) -> &mut impl TwoD<u16> { &mut self.0 }
|
|
||||||
/// fn show <T: Draw<Self> + ?Sized> (&mut self, area: impl TwoD<u16>, _: &T) {
|
|
||||||
/// println!("show: {area:?}");
|
|
||||||
/// ()
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// impl tengri::Draw<TestOut> for String {
|
|
||||||
/// fn draw (&self, to: &mut TestOut) {
|
|
||||||
/// //to.area_mut().set_w(self.len() as u16);
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub trait Screen: W<Self::Unit> + H<Self::Unit> + Send + Sync + Sized {
|
|
||||||
type Unit: Coord;
|
|
||||||
/// Render drawable in area specified by `area`
|
|
||||||
fn show <'t, T: Draw<Self> + ?Sized> (&mut self, area: TwoD<Self::Unit>, content: &'t T);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasColor {
|
|
||||||
fn color (&self) -> ItemColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Something that has a [Measure] of its rendered size.
|
|
||||||
pub trait Measured<O: Screen> {
|
|
||||||
fn measure (&self) -> &Measure<O>;
|
|
||||||
fn measure_width (&self) -> O::Unit { self.measure().w() }
|
|
||||||
fn measure_height (&self) -> O::Unit { self.measure().h() }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "tui")] pub trait TuiOut: Screen {
|
|
||||||
fn tui_out (&mut self) -> &mut Buffer;
|
|
||||||
fn update (&mut self, area: impl TwoD<u16>, callback: &impl Fn(&mut Cell, u16, u16)) {
|
|
||||||
tui_update(self.buffer(), area, callback);
|
|
||||||
}
|
|
||||||
fn fill_char (&mut self, area: impl TwoD<u16>, c: char) {
|
|
||||||
self.update(area, &|cell,_,_|{cell.set_char(c);})
|
|
||||||
}
|
|
||||||
fn fill_bg (&mut self, area: impl TwoD<u16>, color: Color) {
|
|
||||||
self.update(area, &|cell,_,_|{cell.set_bg(color);})
|
|
||||||
}
|
|
||||||
fn fill_fg (&mut self, area: impl TwoD<u16>, color: Color) {
|
|
||||||
self.update(area, &|cell,_,_|{cell.set_fg(color);})
|
|
||||||
}
|
|
||||||
fn fill_mod (&mut self, area: impl TwoD<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 TwoD<u16>, on: bool) {
|
|
||||||
self.fill_mod(area, on, Modifier::BOLD)
|
|
||||||
}
|
|
||||||
fn fill_reversed (&mut self, area: impl TwoD<u16>, on: bool) {
|
|
||||||
self.fill_mod(area, on, Modifier::REVERSED)
|
|
||||||
}
|
|
||||||
fn fill_crossed_out (&mut self, area: impl TwoD<u16>, on: bool) {
|
|
||||||
self.fill_mod(area, on, Modifier::CROSSED_OUT)
|
|
||||||
}
|
|
||||||
fn fill_ul (&mut self, area: impl TwoD<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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "tui")] pub trait BorderStyle: Draw<Buffer> + Copy {
|
|
||||||
fn enabled (&self) -> bool;
|
|
||||||
fn border_n (&self) -> &str { Self::N }
|
|
||||||
fn border_s (&self) -> &str { Self::S }
|
|
||||||
fn border_e (&self) -> &str { Self::E }
|
|
||||||
fn border_w (&self) -> &str { Self::W }
|
|
||||||
fn border_nw (&self) -> &str { Self::NW }
|
|
||||||
fn border_ne (&self) -> &str { Self::NE }
|
|
||||||
fn border_sw (&self) -> &str { Self::SW }
|
|
||||||
fn border_se (&self) -> &str { Self::SE }
|
|
||||||
|
|
||||||
fn enclose (self, w: impl Draw<Buffer>) -> impl Draw<Buffer> {
|
|
||||||
Bsp::b(Fill::XY(Border(self.enabled(), self)), w)
|
|
||||||
}
|
|
||||||
fn enclose2 (self, w: impl Draw<Buffer>) -> impl Draw<Buffer> {
|
|
||||||
Bsp::b(Pad::XY(1, 1, Fill::XY(Border(self.enabled(), self))), w)
|
|
||||||
}
|
|
||||||
fn enclose_bg (self, w: impl Draw<Buffer>) -> impl Draw<Buffer> {
|
|
||||||
TuiOut::bg(self.style().unwrap().bg.unwrap_or(Color::Reset),
|
|
||||||
Bsp::b(Fill::XY(Border(self.enabled(), self)), w))
|
|
||||||
}
|
|
||||||
#[inline] fn draw <'a> (&self, to: &mut impl TuiOut) -> Usually<()> {
|
|
||||||
if self.enabled() {
|
|
||||||
self.draw_h(to, None)?;
|
|
||||||
self.draw_v(to, None)?;
|
|
||||||
self.draw_c(to, None)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[inline] fn draw_h (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl TwoD<u16>> {
|
|
||||||
let area = to.area();
|
|
||||||
let style = style.or_else(||self.style_horizontal());
|
|
||||||
let [x, x2, y, y2] = area.lrtb();
|
|
||||||
for x in x..x2.saturating_sub(1) {
|
|
||||||
to.blit(&Self::N, x, y, style);
|
|
||||||
to.blit(&Self::S, x, y2.saturating_sub(1), style)
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
#[inline] fn draw_v (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl TwoD<u16>> {
|
|
||||||
let area = to.area();
|
|
||||||
let style = style.or_else(||self.style_vertical());
|
|
||||||
let [x, x2, y, y2] = area.lrtb();
|
|
||||||
let h = y2 - y;
|
|
||||||
if h > 1 {
|
|
||||||
for y in y..y2.saturating_sub(1) {
|
|
||||||
to.blit(&Self::W, x, y, style);
|
|
||||||
to.blit(&Self::E, x2.saturating_sub(1), y, style);
|
|
||||||
}
|
|
||||||
} else if h > 0 {
|
|
||||||
to.blit(&Self::W0, x, y, style);
|
|
||||||
to.blit(&Self::E0, x2.saturating_sub(1), y, style);
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
#[inline] fn draw_c (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl TwoD<u16>> {
|
|
||||||
let area = to.area();
|
|
||||||
let style = style.or_else(||self.style_corners());
|
|
||||||
let XYWH(x, y, w, h) = area;
|
|
||||||
if w > 1 && h > 1 {
|
|
||||||
to.blit(&Self::NW, x, y, style);
|
|
||||||
to.blit(&Self::NE, x + w - 1, y, style);
|
|
||||||
to.blit(&Self::SW, x, y + h- 1, style);
|
|
||||||
to.blit(&Self::SE, x + w - 1, y + h - 1, style);
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
#[inline] fn style (&self) -> Option<Style> { None }
|
|
||||||
#[inline] fn style_horizontal (&self) -> Option<Style> { self.style() }
|
|
||||||
#[inline] fn style_vertical (&self) -> Option<Style> { self.style() }
|
|
||||||
#[inline] fn style_corners (&self) -> Option<Style> { self.style() }
|
|
||||||
|
|
||||||
const NW: &'static str = "";
|
|
||||||
const N: &'static str = "";
|
|
||||||
const NE: &'static str = "";
|
|
||||||
const E: &'static str = "";
|
|
||||||
const SE: &'static str = "";
|
|
||||||
const S: &'static str = "";
|
|
||||||
const SW: &'static str = "";
|
|
||||||
const W: &'static str = "";
|
|
||||||
|
|
||||||
const N0: &'static str = "";
|
|
||||||
const S0: &'static str = "";
|
|
||||||
const W0: &'static str = "";
|
|
||||||
const E0: &'static str = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Things that can provide a [jack::Client] reference.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use tengri::{Jack, HasJack};
|
|
||||||
///
|
|
||||||
/// let jack: &Jack = Jacked::default().jack();
|
|
||||||
///
|
|
||||||
/// #[derive(Default)] struct Jacked<'j>(Jack<'j>);
|
|
||||||
///
|
|
||||||
/// impl<'j> HasJack<'j> for Jacked<'j> {
|
|
||||||
/// fn jack (&self) -> &Jack<'j> { &self.0 }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[cfg(feature = "jack")] pub trait HasJack<'j>: Send + Sync {
|
|
||||||
/// Return the internal [jack::Client] handle
|
|
||||||
/// that lets you call the JACK API.
|
|
||||||
fn jack (&self) -> &Jack<'j>;
|
|
||||||
fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
|
|
||||||
self.jack().with_client(op)
|
|
||||||
}
|
|
||||||
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
|
|
||||||
self.with_client(|client|client.port_by_name(name))
|
|
||||||
}
|
|
||||||
fn port_by_id (&self, id: u32) -> Option<Port<Unowned>> {
|
|
||||||
self.with_client(|c|c.port_by_id(id))
|
|
||||||
}
|
|
||||||
fn register_port <PS: PortSpec + Default> (&self, name: impl AsRef<str>) -> Usually<Port<PS>> {
|
|
||||||
self.with_client(|client|Ok(client.register_port(name.as_ref(), PS::default())?))
|
|
||||||
}
|
|
||||||
fn sync_lead (&self, enable: bool, callback: impl Fn(TimebaseInfo)->Position) -> Usually<()> {
|
|
||||||
if enable {
|
|
||||||
self.with_client(|client|match client.register_timebase_callback(false, callback) {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(e) => Err(e)
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn sync_follow (&self, _enable: bool) -> Usually<()> {
|
|
||||||
// TODO: sync follow
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait for thing that has a JACK process callback.
|
|
||||||
#[cfg(feature = "jack")] pub trait Audio {
|
|
||||||
/// Handle a JACK event.
|
|
||||||
fn handle (&mut self, _event: JackEvent) {}
|
|
||||||
/// Projecss a JACK chunk.
|
|
||||||
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
/// The JACK process callback function passed to the server.
|
|
||||||
fn callback (
|
|
||||||
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
|
|
||||||
) -> Control where Self: Sized {
|
|
||||||
if let Ok(mut state) = state.write() {
|
|
||||||
state.process(client, scope)
|
|
||||||
} else {
|
|
||||||
Control::Quit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implement [Audio]: provide JACK callbacks.
|
|
||||||
#[cfg(feature = "jack")] #[macro_export] macro_rules! impl_audio {
|
|
||||||
|
|
||||||
(|
|
|
||||||
$self1:ident:
|
|
||||||
$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident
|
|
||||||
|$cb:expr$(;|$self2:ident,$e:ident|$cb2:expr)?) => {
|
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
|
|
||||||
#[inline] fn process (&mut $self1, $c: &Client, $s: &ProcessScope) -> Control { $cb }
|
|
||||||
$(#[inline] fn handle (&mut $self2, $e: JackEvent) { $cb2 })?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
($Struct:ident: $process:ident, $handle:ident) => {
|
|
||||||
impl Audio for $Struct {
|
|
||||||
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
|
|
||||||
$process(self, c, s)
|
|
||||||
}
|
|
||||||
#[inline] fn handle (&mut self, e: JackEvent) {
|
|
||||||
$handle(self, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
($Struct:ident: $process:ident) => {
|
|
||||||
impl Audio for $Struct {
|
|
||||||
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
|
|
||||||
$process(self, c, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "jack")] pub trait JackPerfModel {
|
|
||||||
fn update_from_jack_scope (&self, t0: Option<u64>, scope: &ProcessScope);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define a trait an implement it for various mutation-enabled wrapper types. */
|
|
||||||
#[macro_export] macro_rules! flex_trait_mut (
|
|
||||||
($Trait:ident $(<$($A:ident:$T:ident),+>)? {
|
|
||||||
$(fn $fn:ident (&mut $self:ident $(, $arg:ident:$ty:ty)*) -> $ret:ty $body:block)*
|
|
||||||
})=>{
|
|
||||||
pub trait $Trait $(<$($A: $T),+>)? {
|
|
||||||
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret $body)*
|
|
||||||
}
|
|
||||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &mut _T_ {
|
|
||||||
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { (*$self).$fn($($arg),*) })*
|
|
||||||
}
|
|
||||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for Option<_T_> {
|
|
||||||
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret {
|
|
||||||
if let Some(this) = $self { this.$fn($($arg),*) } else { Ok(None) }
|
|
||||||
})*
|
|
||||||
}
|
|
||||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Mutex<_T_> {
|
|
||||||
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.get_mut().unwrap().$fn($($arg),*) })*
|
|
||||||
}
|
|
||||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::Mutex<_T_>> {
|
|
||||||
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.lock().unwrap().$fn($($arg),*) })*
|
|
||||||
}
|
|
||||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::RwLock<_T_> {
|
|
||||||
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.write().unwrap().$fn($($arg),*) })*
|
|
||||||
}
|
|
||||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::RwLock<_T_>> {
|
|
||||||
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.write().unwrap().$fn($($arg),*) })*
|
|
||||||
}
|
|
||||||
};
|
|
||||||
);
|
|
||||||
43
src/text.rs
Normal file
43
src/text.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
pub(crate) use ::unicode_width::*;
|
||||||
|
|
||||||
|
/// Displays an owned [str]-like with fixed maximum width.
|
||||||
|
///
|
||||||
|
/// Width is computed using [unicode_width].
|
||||||
|
pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
|
||||||
|
|
||||||
|
/// Displays a borrowed [str]-like with fixed maximum width
|
||||||
|
///
|
||||||
|
/// Width is computed using [unicode_width].
|
||||||
|
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
|
||||||
|
|
||||||
|
impl<'a, T: AsRef<str>> TrimString<T> {
|
||||||
|
fn to_ref (&self) -> TrimStringRef<'_, T> { TrimStringRef(self.0, &self.1) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trim string with [unicode_width].
|
||||||
|
pub fn trim_string (max_width: usize, input: impl AsRef<str>) -> String {
|
||||||
|
let input = input.as_ref();
|
||||||
|
let mut output = Vec::with_capacity(input.len());
|
||||||
|
let mut width: usize = 1;
|
||||||
|
let mut chars = input.chars();
|
||||||
|
while let Some(c) = chars.next() {
|
||||||
|
if width > max_width {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
output.push(c);
|
||||||
|
width += c.width().unwrap_or(0);
|
||||||
|
}
|
||||||
|
return output.into_iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
|
||||||
|
let mut width: u16 = 0;
|
||||||
|
let mut chars = text.as_ref().chars();
|
||||||
|
while let Some(c) = chars.next() {
|
||||||
|
width += c.width().unwrap_or(0) as u16;
|
||||||
|
if width > max {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return width
|
||||||
|
}
|
||||||
63
src/time.rs
Normal file
63
src/time.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Performance counter
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PerfModel {
|
||||||
|
pub clock: quanta::Clock,
|
||||||
|
/// Measurement has a small cost. Disable it here.
|
||||||
|
pub enabled: bool,
|
||||||
|
// In nanoseconds. Time used by last iteration.
|
||||||
|
pub used: AtomicF64,
|
||||||
|
// In microseconds. Max prescribed time for iteration (frame, chunk...).
|
||||||
|
pub window: AtomicF64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_default!(PerfModel: Self {
|
||||||
|
enabled: true,
|
||||||
|
clock: quanta::Clock::new(),
|
||||||
|
used: Default::default(),
|
||||||
|
window: Default::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
impl PerfModel {
|
||||||
|
pub fn get_t0 (&self) -> Option<u64> {
|
||||||
|
if self.enabled {
|
||||||
|
Some(self.clock.raw())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_t1 (&self, t0: Option<u64>) -> Option<std::time::Duration> {
|
||||||
|
if let Some(t0) = t0 {
|
||||||
|
if self.enabled {
|
||||||
|
Some(self.clock.delta(t0, self.clock.raw()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn update (&self, t0: Option<u64>, microseconds: f64) {
|
||||||
|
if let Some(t0) = t0 {
|
||||||
|
let t1 = self.clock.raw();
|
||||||
|
self.used.store(self.clock.delta_as_nanos(t0, t1) as f64, Relaxed);
|
||||||
|
self.window.store(microseconds, Relaxed,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn percentage (&self) -> Option<f64> {
|
||||||
|
let window = self.window.load(Relaxed) * 1000.0;
|
||||||
|
if window > 0.0 {
|
||||||
|
let used = self.used.load(Relaxed);
|
||||||
|
Some(100.0 * used / window)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn cycle <F: Fn(&Self)->T, T> (&self, call: &F) -> T {
|
||||||
|
let t0 = self.get_t0();
|
||||||
|
let result = call(self);
|
||||||
|
let _t1 = self.get_t1(t0).unwrap();
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
495
src/tui.rs
495
src/tui.rs
|
|
@ -1,99 +1,114 @@
|
||||||
use crate::*;
|
use crate::{*, lang::*, play::*, draw::*, color::*};
|
||||||
use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
|
use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
|
||||||
use rand::distributions::uniform::UniformSampler;
|
use rand::distributions::uniform::UniformSampler;
|
||||||
|
use ::{
|
||||||
#[cfg(feature = "tui")] pub use self::tui_fns::*;
|
std::{
|
||||||
|
io::{stdout, Write},
|
||||||
#[cfg(feature = "tui")] mod tui_fns {
|
time::Duration
|
||||||
use crate::*;
|
},
|
||||||
|
better_panic::{Settings, Verbosity},
|
||||||
|
ratatui::{
|
||||||
|
prelude::{Color, Style, Buffer, Position, Backend},
|
||||||
|
style::{Modifier, Color::*},
|
||||||
|
backend::{CrosstermBackend, ClearType},
|
||||||
|
layout::{Size, Rect},
|
||||||
|
buffer::Cell
|
||||||
|
},
|
||||||
|
crossterm::{
|
||||||
|
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
|
||||||
|
event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Interpret TUI-specific layout operation.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use tengri::{Namespace, Understand, Tui, ratatui::prelude::Color};
|
|
||||||
///
|
|
||||||
/// struct State;
|
|
||||||
/// impl<'b> Namespace<'b, bool> for State {}
|
|
||||||
/// impl<'b> Namespace<'b, u16> for State {}
|
|
||||||
/// impl<'b> Namespace<'b, Color> for State {}
|
|
||||||
/// impl Understand<Tui, ()> for State {}
|
|
||||||
/// # fn main () -> tengri::Usually<()> {
|
|
||||||
/// let state = State;
|
|
||||||
/// let mut out = TuiOut::default();
|
|
||||||
/// tengri::eval_view_tui(&state, &mut out, "")?;
|
|
||||||
/// tengri::eval_view_tui(&state, &mut out, "text Hello world!")?;
|
|
||||||
/// tengri::eval_view_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?;
|
|
||||||
/// tengri::eval_view_tui(&state, &mut out, "bg (g 2) (text Hello world!)")?;
|
|
||||||
/// tengri::eval_view_tui(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?;
|
|
||||||
/// # Ok(()) }
|
|
||||||
/// ```
|
|
||||||
pub fn eval_view_tui <'a, S> (
|
|
||||||
state: &S, output: &mut Buffer, expr: impl Expression + 'a
|
|
||||||
) -> Usually<bool> where
|
|
||||||
S: Understand<TuiOut, ()>
|
|
||||||
+ for<'b>Namespace<'b, bool>
|
|
||||||
+ for<'b>Namespace<'b, u16>
|
|
||||||
+ for<'b>Namespace<'b, Color>
|
|
||||||
{
|
|
||||||
// See `tengri::eval_view`
|
|
||||||
let head = expr.head()?;
|
|
||||||
let mut frags = head.src()?.unwrap_or_default().split("/");
|
|
||||||
let args = expr.tail();
|
|
||||||
let arg0 = args.head();
|
|
||||||
let tail0 = args.tail();
|
|
||||||
let arg1 = tail0.head();
|
|
||||||
let tail1 = tail0.tail();
|
|
||||||
let arg2 = tail1.head();
|
|
||||||
match frags.next() {
|
|
||||||
|
|
||||||
Some("text") => {
|
|
||||||
if let Some(src) = args?.src()? { output.place(&src) }
|
|
||||||
},
|
|
||||||
|
|
||||||
Some("fg") => {
|
|
||||||
let arg0 = arg0?.expect("fg: expected arg 0 (color)");
|
|
||||||
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color"));
|
|
||||||
let thunk = Thunk::new(move|output: &mut Buffer|state.understand(output, &arg1).unwrap());
|
|
||||||
output.place(&TuiOut::fg(color, thunk))
|
|
||||||
},
|
|
||||||
|
|
||||||
Some("bg") => {
|
|
||||||
//panic!("expr: {expr:?}\nhead: {head:?}\nfrags: {frags:?}\nargs: {args:?}\narg0: {arg0:?}\ntail0: {tail0:?}\narg1: {arg1:?}\ntail1: {tail1:?}\narg2: {arg2:?}");
|
|
||||||
//panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}");;
|
|
||||||
//panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}");
|
|
||||||
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
|
|
||||||
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color"));
|
|
||||||
let thunk = Thunk::new(move|output: &mut Buffer|state.understand(output, &arg1).unwrap());
|
|
||||||
output.place(&TuiOut::bg(color, thunk))
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => return Ok(false)
|
|
||||||
|
|
||||||
};
|
};
|
||||||
Ok(true)
|
|
||||||
|
/// One trait to bind 'em.
|
||||||
|
pub trait Tui: Draw<Buffer> + Do<TuiEvent, Perhaps<TuiEvent>> {
|
||||||
|
fn handle (&mut self, e: TuiEvent) -> Perhaps<TuiEvent> { Ok(None) }
|
||||||
|
fn render (&self, to: &mut Buffer) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tui")] pub fn tui_setup <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()> {
|
/// Rendering is delegated to [Tui] impl.
|
||||||
|
impl<T: Tui> Draw<TuiOut> for T {
|
||||||
|
fn draw (&self, to: &mut Buffer) {
|
||||||
|
Tui::render(self, to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Event handling is delegated to [Tui] impl.
|
||||||
|
impl<T: Tui> Do<TuiEvent, Perhaps<TuiEvent>> for T {
|
||||||
|
fn apply (&mut self, event: TuiEvent) -> Perhaps<TuiEvent> {
|
||||||
|
Tui::handle(self, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawn the TUI output thread which writes colored characters to the terminal.
|
||||||
|
pub fn tui_output <W: Write, T: Draw<Buffer> + Send + Sync + 'static> (
|
||||||
|
output: W,
|
||||||
|
exited: &Arc<AtomicBool>,
|
||||||
|
state: &Arc<RwLock<T>>,
|
||||||
|
sleep: Duration
|
||||||
|
) -> Result<Thread, std::io::Error> {
|
||||||
|
let state = state.clone();
|
||||||
|
tui_setup(&mut output)?;
|
||||||
|
let mut backend = CrosstermBackend::new(output);
|
||||||
|
let WH(width, height) = tui_wh(&mut backend);
|
||||||
|
let mut buffer_a = Buffer::empty(Rect { x: 0, y: 0, width, height });
|
||||||
|
let mut buffer_b = Buffer::empty(Rect { x: 0, y: 0, width, height });
|
||||||
|
Thread::new_sleep(exited.clone(), sleep, move |perf| {
|
||||||
|
let size = tui_wh(&mut backend);
|
||||||
|
if let Ok(state) = state.try_read() {
|
||||||
|
tui_resize(&mut backend, &mut buffer_a, size);
|
||||||
|
buffer_a = tui_redraw(&mut backend, &mut buffer_a, &mut buffer_b);
|
||||||
|
}
|
||||||
|
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
|
||||||
|
buffer_a.set_string(0, 0, &timer, Style::default());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tui_setup (output: &mut impl Write) -> Usually<()> {
|
||||||
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
|
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
|
||||||
std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{
|
std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{
|
||||||
stdout().execute(LeaveAlternateScreen).unwrap();
|
output.execute(LeaveAlternateScreen).unwrap();
|
||||||
CrosstermBackend::new(stdout()).show_cursor().unwrap();
|
CrosstermBackend::new(output).show_cursor().unwrap();
|
||||||
disable_raw_mode().unwrap();
|
disable_raw_mode().unwrap();
|
||||||
better_panic_handler(info);
|
better_panic_handler(info);
|
||||||
}));
|
}));
|
||||||
stdout().execute(EnterAlternateScreen)?;
|
output.execute(EnterAlternateScreen)?;
|
||||||
backend.hide_cursor()?;
|
CrosstermBackend::new(output).hide_cursor()?;
|
||||||
enable_raw_mode().map_err(Into::into)
|
enable_raw_mode().map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tui")] pub fn tui_teardown <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()> {
|
pub fn tui_resize <W: Write> (
|
||||||
|
backend: &mut CrosstermBackend<W>,
|
||||||
|
buffer: &mut Buffer,
|
||||||
|
size: WH<u16>
|
||||||
|
) {
|
||||||
|
if buffer.area != size {
|
||||||
|
backend.clear_region(ClearType::All).unwrap();
|
||||||
|
buffer.resize(size);
|
||||||
|
buffer.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tui_redraw <'b, W: Write> (
|
||||||
|
backend: &mut CrosstermBackend<W>,
|
||||||
|
mut prev_buffer: &'b mut Buffer,
|
||||||
|
mut next_buffer: &'b mut Buffer
|
||||||
|
) {
|
||||||
|
let updates = prev_buffer.diff(&next_buffer);
|
||||||
|
backend.draw(updates.into_iter()).expect("failed to render");
|
||||||
|
Backend::flush(backend).expect("failed to flush output new_buffer");
|
||||||
|
std::mem::swap(&mut prev_buffer, &mut next_buffer);
|
||||||
|
next_buffer.reset();
|
||||||
|
next_buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tui_teardown <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()> {
|
||||||
stdout().execute(LeaveAlternateScreen)?;
|
stdout().execute(LeaveAlternateScreen)?;
|
||||||
backend.show_cursor()?;
|
backend.show_cursor()?;
|
||||||
disable_raw_mode().map_err(Into::into)
|
disable_raw_mode().map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tui")] pub fn tui_update (
|
pub fn tui_update (
|
||||||
buf: &mut Buffer, area: XYWH<u16>, callback: &impl Fn(&mut Cell, u16, u16)
|
buf: &mut Buffer, area: XYWH<u16>, callback: &impl Fn(&mut Cell, u16, u16)
|
||||||
) {
|
) {
|
||||||
for row in 0..area.h() {
|
for row in 0..area.h() {
|
||||||
|
|
@ -109,15 +124,71 @@ pub fn eval_view_tui <'a, S> (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tui")] pub(crate) fn tui_wh <W: Write> (
|
pub(crate) fn tui_wh <W: Write> (backend: &mut CrosstermBackend<W>) -> WH<u16> {
|
||||||
backend: &mut CrosstermBackend<W>
|
|
||||||
) -> WH<u16> {
|
|
||||||
let Size { width, height } = backend.size().expect("get size failed");
|
let Size { width, height } = backend.size().expect("get size failed");
|
||||||
WH(width, height)
|
WH(width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draw contents with foreground color applied.
|
||||||
|
pub fn fg (enabled: bool, color: Color, item: impl Tui) -> impl Tui {
|
||||||
|
|to: &mut Buffer|item.draw(to.with_fg(enabled, color))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw contents with background color applied.
|
||||||
|
pub fn bg (enabled: bool, color: Color, item: impl Tui) -> impl Tui {
|
||||||
|
|to: &mut Buffer|item.draw(to.with_bg(enabled, color))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw contents with modifier applied.
|
||||||
|
pub fn modify (enabled: bool, modifier: Modifier, item: impl Tui) -> impl Tui {
|
||||||
|
|to: &mut Buffer|item.draw(to.with_modifier(enabled, modifier, modifier))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw contents with style applied.
|
||||||
|
pub fn styled (enabled: bool, style: Style, item: impl Tui) -> impl Tui {
|
||||||
|
|to: &mut Buffer|item.draw(to.with_style(enabled, style, modifier))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw border around shrinked item.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// /// TODO
|
||||||
|
/// ```
|
||||||
|
pub fn border <T, S: BorderStyle> (on: bool, style: S, draw: impl Tui) -> impl Tui {
|
||||||
|
fill_wh(bsp_a(when(on, |output|{/*TODO*/}), pad(Some(1), Some(1), draw)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw TUI content or its error message.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let _ = tengri::tui::catcher(Ok(Some("hello")));
|
||||||
|
/// let _ = tengri::tui::catcher(Ok(None));
|
||||||
|
/// let _ = tengri::tui::catcher(Err("draw fail".into()));
|
||||||
|
/// ```
|
||||||
|
pub fn catcher <T, E> (error: Perhaps<E>, draw: impl Tui) -> impl Tui {
|
||||||
|
move|to: &mut Buffer|match error.as_ref() {
|
||||||
|
Ok(Some(content)) => draw(to),
|
||||||
|
Ok(None) => to.blit(&"<empty>", 0, 0, Some(Style::default().yellow())),
|
||||||
|
Err(e) => {
|
||||||
|
let err_fg = rgb(255,224,244);
|
||||||
|
let err_bg = rgb(96, 24, 24);
|
||||||
|
let title = bsp_e(bold(true, "upsi daisy. "), "rendering failed.");
|
||||||
|
let error = bsp_e("\"why?\" ", bold(true, format!("{e}")));
|
||||||
|
&fg(err_fg, bg(err_bg, bsp_s(title, error)))(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TUI buffer sized by `usize` instead of `u16`.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct BigBuffer {
|
||||||
|
pub width: usize,
|
||||||
|
pub height: usize,
|
||||||
|
pub content: Vec<Cell>
|
||||||
|
}
|
||||||
|
|
||||||
/// Spawn the TUI input thread which reads keys from the terminal.
|
/// Spawn the TUI input thread which reads keys from the terminal.
|
||||||
#[cfg(feature = "tui")] pub fn tui_input <T: Act<TuiEvent, T> + Send + Sync + 'static> (
|
pub fn tui_input <T: Act<TuiEvent, T> + Send + Sync + 'static> (
|
||||||
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
|
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
|
||||||
) -> Result<Thread, std::io::Error> {
|
) -> Result<Thread, std::io::Error> {
|
||||||
let exited = exited.clone();
|
let exited = exited.clone();
|
||||||
|
|
@ -145,61 +216,6 @@ pub fn eval_view_tui <'a, S> (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawn the TUI output thread which writes colored characters to the terminal.
|
|
||||||
#[cfg(feature = "tui")] pub fn tui_output <T: Draw<Buffer> + Send + Sync + 'static> (
|
|
||||||
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, sleep: Duration
|
|
||||||
) -> Result<Thread, std::io::Error> {
|
|
||||||
let state = state.clone();
|
|
||||||
let mut backend = CrosstermBackend::new(stdout());
|
|
||||||
let WH(width, height) = tui_wh(&mut backend);
|
|
||||||
let mut buffer_a = Buffer::empty(Rect { x: 0, y: 0, width, height });
|
|
||||||
let mut buffer_b = Buffer::empty(Rect { x: 0, y: 0, width, height });
|
|
||||||
Thread::new_sleep(exited.clone(), sleep, move |perf| {
|
|
||||||
let size = tui_wh(&mut backend);
|
|
||||||
if let Ok(state) = state.try_read() {
|
|
||||||
tui_resize(&mut backend, &mut buffer_a, size);
|
|
||||||
buffer_a = tui_redraw(&mut backend, &mut buffer_a, &mut buffer_b);
|
|
||||||
}
|
|
||||||
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
|
|
||||||
buffer_a.set_string(0, 0, &timer, Style::default());
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "tui")] pub fn tui_resize <W: Write> (
|
|
||||||
backend: &mut CrosstermBackend<W>,
|
|
||||||
buffer: &mut Buffer,
|
|
||||||
size: WH<u16>
|
|
||||||
) {
|
|
||||||
if buffer.area != size {
|
|
||||||
backend.clear_region(ClearType::All).unwrap();
|
|
||||||
buffer.resize(size);
|
|
||||||
buffer.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "tui")] pub fn tui_redraw <'b, W: Write> (
|
|
||||||
backend: &mut CrosstermBackend<W>,
|
|
||||||
mut prev_buffer: &'b mut Buffer,
|
|
||||||
mut next_buffer: &'b mut Buffer
|
|
||||||
) {
|
|
||||||
let updates = prev_buffer.diff(&next_buffer);
|
|
||||||
backend.draw(updates.into_iter()).expect("failed to render");
|
|
||||||
Backend::flush(backend).expect("failed to flush output new_buffer");
|
|
||||||
std::mem::swap(&mut prev_buffer, &mut next_buffer);
|
|
||||||
next_buffer.reset();
|
|
||||||
next_buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! tui_main {
|
|
||||||
($expr:expr) => {
|
|
||||||
fn main () -> Usually<()> {
|
|
||||||
let engine = ::tengri::Tui::new(Box::new(stdout()))?;
|
|
||||||
let state = ::std::sync::Arc::new(std::sync::RwLock::new($expr));
|
|
||||||
engine.run(true, &state)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Coord for u16 { fn plus (self, other: Self) -> Self { self.saturating_add(other) } }
|
impl Coord for u16 { fn plus (self, other: Self) -> Self { self.saturating_add(other) } }
|
||||||
impl_from!(BigBuffer: |size:(usize, usize)| Self::new(size.0, size.1));
|
impl_from!(BigBuffer: |size:(usize, usize)| Self::new(size.0, size.1));
|
||||||
|
|
@ -350,16 +366,6 @@ impl BigBuffer {
|
||||||
y * self.width + x
|
y * self.width + x
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PerfModel {
|
|
||||||
fn cycle <F: Fn(&Self)->T, T> (&self, call: &F) -> T {
|
|
||||||
let t0 = self.get_t0();
|
|
||||||
let result = call(self);
|
|
||||||
let _t1 = self.get_t1(t0).unwrap();
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Phat<T> {
|
impl<T> Phat<T> {
|
||||||
pub const LO: &'static str = "▄";
|
pub const LO: &'static str = "▄";
|
||||||
pub const HI: &'static str = "▀";
|
pub const HI: &'static str = "▀";
|
||||||
|
|
@ -522,13 +528,6 @@ impl Draw<Buffer> for Arc<str> {
|
||||||
fn draw (&self, to: &mut Buffer) { self.as_ref().draw(to) }
|
fn draw (&self, to: &mut Buffer) { self.as_ref().draw(to) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Draw<Buffer>> Draw<Buffer> for Foreground<Color, T> {
|
|
||||||
fn draw (&self, to: &mut Buffer) {
|
|
||||||
let area = self.layout(to.area());
|
|
||||||
to.fill_fg(area, self.0);
|
|
||||||
to.show(area, &self.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Draw<Buffer>> Draw<Buffer> for Background<Color, T> {
|
impl<T: Draw<Buffer>> Draw<Buffer> for Background<Color, T> {
|
||||||
fn draw (&self, to: &mut Buffer) {
|
fn draw (&self, to: &mut Buffer) {
|
||||||
|
|
@ -674,9 +673,6 @@ pub fn named_key (token: &str) -> Option<KeyCode> {
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
impl<O: Screen, Color, Item: Layout<O>> Layout<O> for Foreground<Color, Item> {
|
|
||||||
fn layout (&self, to: XYWH<O::Unit>) -> XYWH<O::Unit> { self.1.layout(to) }
|
|
||||||
}
|
|
||||||
impl<O: Screen, Color, Item: Layout<O>> Layout<O> for Background<Color, Item> {
|
impl<O: Screen, Color, Item: Layout<O>> Layout<O> for Background<Color, Item> {
|
||||||
fn layout (&self, to: XYWH<O::Unit>) -> XYWH<O::Unit> { self.1.layout(to) }
|
fn layout (&self, to: XYWH<O::Unit>) -> XYWH<O::Unit> { self.1.layout(to) }
|
||||||
}
|
}
|
||||||
|
|
@ -969,3 +965,190 @@ impl ItemTheme {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait TuiOut: Screen {
|
||||||
|
fn tui_out (&mut self) -> &mut Buffer;
|
||||||
|
fn update (&mut self, area: impl Area<u16>, callback: &impl Fn(&mut Cell, u16, u16)) {
|
||||||
|
tui_update(self.buffer(), area, callback);
|
||||||
|
}
|
||||||
|
fn fill_char (&mut self, area: impl Area<u16>, c: char) {
|
||||||
|
self.update(area, &|cell,_,_|{cell.set_char(c);})
|
||||||
|
}
|
||||||
|
fn fill_bg (&mut self, area: impl Area<u16>, color: Color) {
|
||||||
|
self.update(area, &|cell,_,_|{cell.set_bg(color);})
|
||||||
|
}
|
||||||
|
fn fill_fg (&mut self, area: impl Area<u16>, color: Color) {
|
||||||
|
self.update(area, &|cell,_,_|{cell.set_fg(color);})
|
||||||
|
}
|
||||||
|
fn fill_mod (&mut self, area: impl Area<u16>, on: bool, modifier: Modifier) {
|
||||||
|
if on {
|
||||||
|
self.update(area, &|cell,_,_|cell.modifier.insert(modifier))
|
||||||
|
} else {
|
||||||
|
self.update(area, &|cell,_,_|cell.modifier.remove(modifier))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn fill_bold (&mut self, area: impl Area<u16>, on: bool) {
|
||||||
|
self.fill_mod(area, on, Modifier::BOLD)
|
||||||
|
}
|
||||||
|
fn fill_reversed (&mut self, area: impl Area<u16>, on: bool) {
|
||||||
|
self.fill_mod(area, on, Modifier::REVERSED)
|
||||||
|
}
|
||||||
|
fn fill_crossed_out (&mut self, area: impl Area<u16>, on: bool) {
|
||||||
|
self.fill_mod(area, on, Modifier::CROSSED_OUT)
|
||||||
|
}
|
||||||
|
fn fill_ul (&mut self, area: impl Area<u16>, color: Option<Color>) {
|
||||||
|
if let Some(color) = color {
|
||||||
|
self.update(area, &|cell,_,_|{
|
||||||
|
cell.modifier.insert(ratatui::prelude::Modifier::UNDERLINED);
|
||||||
|
cell.underline_color = color;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self.update(area, &|cell,_,_|{
|
||||||
|
cell.modifier.remove(ratatui::prelude::Modifier::UNDERLINED);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) {
|
||||||
|
for cell in self.buffer().content.iter_mut() {
|
||||||
|
cell.fg = fg;
|
||||||
|
cell.bg = bg;
|
||||||
|
cell.modifier = modifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn blit (&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>) {
|
||||||
|
let text = text.as_ref();
|
||||||
|
let style = style.unwrap_or(Style::default());
|
||||||
|
let buf = self.buffer();
|
||||||
|
if x < buf.area.width && y < buf.area.height {
|
||||||
|
buf.set_string(x, y, text, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Write a line of text
|
||||||
|
///
|
||||||
|
/// TODO: do a paragraph (handle newlines)
|
||||||
|
fn text (&mut self, text: &impl AsRef<str>, x0: u16, y: u16, max_width: u16) {
|
||||||
|
let text = text.as_ref();
|
||||||
|
let buf = self.buffer();
|
||||||
|
let mut string_width: u16 = 0;
|
||||||
|
for character in text.chars() {
|
||||||
|
let x = x0 + string_width;
|
||||||
|
let character_width = character.width().unwrap_or(0) as u16;
|
||||||
|
string_width += character_width;
|
||||||
|
if string_width > max_width {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if let Some(cell) = buf.write().unwrap().cell_mut(ratatui::prelude::Position { x, y }) {
|
||||||
|
cell.set_char(character);
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait BorderStyle: Draw<Buffer> + Copy {
|
||||||
|
fn enabled (&self) -> bool;
|
||||||
|
fn border_n (&self) -> &str { Self::N }
|
||||||
|
fn border_s (&self) -> &str { Self::S }
|
||||||
|
fn border_e (&self) -> &str { Self::E }
|
||||||
|
fn border_w (&self) -> &str { Self::W }
|
||||||
|
fn border_nw (&self) -> &str { Self::NW }
|
||||||
|
fn border_ne (&self) -> &str { Self::NE }
|
||||||
|
fn border_sw (&self) -> &str { Self::SW }
|
||||||
|
fn border_se (&self) -> &str { Self::SE }
|
||||||
|
|
||||||
|
fn enclose (self, w: impl Draw<Buffer>) -> impl Draw<Buffer> {
|
||||||
|
Bsp::b(Fill::XY(Border(self.enabled(), self)), w)
|
||||||
|
}
|
||||||
|
fn enclose2 (self, w: impl Draw<Buffer>) -> impl Draw<Buffer> {
|
||||||
|
Bsp::b(Pad::XY(1, 1, Fill::XY(Border(self.enabled(), self))), w)
|
||||||
|
}
|
||||||
|
fn enclose_bg (self, w: impl Draw<Buffer>) -> impl Draw<Buffer> {
|
||||||
|
TuiOut::bg(self.style().unwrap().bg.unwrap_or(Color::Reset),
|
||||||
|
Bsp::b(Fill::XY(Border(self.enabled(), self)), w))
|
||||||
|
}
|
||||||
|
#[inline] fn draw <'a> (&self, to: &mut impl TuiOut) -> Usually<()> {
|
||||||
|
if self.enabled() {
|
||||||
|
self.draw_h(to, None)?;
|
||||||
|
self.draw_v(to, None)?;
|
||||||
|
self.draw_c(to, None)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[inline] fn draw_h (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl Area<u16>> {
|
||||||
|
let area = to.area();
|
||||||
|
let style = style.or_else(||self.style_horizontal());
|
||||||
|
let [x, x2, y, y2] = area.lrtb();
|
||||||
|
for x in x..x2.saturating_sub(1) {
|
||||||
|
to.blit(&Self::N, x, y, style);
|
||||||
|
to.blit(&Self::S, x, y2.saturating_sub(1), style)
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
#[inline] fn draw_v (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl Area<u16>> {
|
||||||
|
let area = to.area();
|
||||||
|
let style = style.or_else(||self.style_vertical());
|
||||||
|
let [x, x2, y, y2] = area.lrtb();
|
||||||
|
let h = y2 - y;
|
||||||
|
if h > 1 {
|
||||||
|
for y in y..y2.saturating_sub(1) {
|
||||||
|
to.blit(&Self::W, x, y, style);
|
||||||
|
to.blit(&Self::E, x2.saturating_sub(1), y, style);
|
||||||
|
}
|
||||||
|
} else if h > 0 {
|
||||||
|
to.blit(&Self::W0, x, y, style);
|
||||||
|
to.blit(&Self::E0, x2.saturating_sub(1), y, style);
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
#[inline] fn draw_c (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl Area<u16>> {
|
||||||
|
let area = to.area();
|
||||||
|
let style = style.or_else(||self.style_corners());
|
||||||
|
let XYWH(x, y, w, h) = area;
|
||||||
|
if w > 1 && h > 1 {
|
||||||
|
to.blit(&Self::NW, x, y, style);
|
||||||
|
to.blit(&Self::NE, x + w - 1, y, style);
|
||||||
|
to.blit(&Self::SW, x, y + h- 1, style);
|
||||||
|
to.blit(&Self::SE, x + w - 1, y + h - 1, style);
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
#[inline] fn style (&self) -> Option<Style> { None }
|
||||||
|
#[inline] fn style_horizontal (&self) -> Option<Style> { self.style() }
|
||||||
|
#[inline] fn style_vertical (&self) -> Option<Style> { self.style() }
|
||||||
|
#[inline] fn style_corners (&self) -> Option<Style> { self.style() }
|
||||||
|
|
||||||
|
const NW: &'static str = "";
|
||||||
|
const N: &'static str = "";
|
||||||
|
const NE: &'static str = "";
|
||||||
|
const E: &'static str = "";
|
||||||
|
const SE: &'static str = "";
|
||||||
|
const S: &'static str = "";
|
||||||
|
const SW: &'static str = "";
|
||||||
|
const W: &'static str = "";
|
||||||
|
|
||||||
|
const N0: &'static str = "";
|
||||||
|
const S0: &'static str = "";
|
||||||
|
const W0: &'static str = "";
|
||||||
|
const E0: &'static str = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stackably padded.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// /// TODO
|
||||||
|
/// ```
|
||||||
|
pub fn phat <T, N: Coord> (w: N, h: N, [fg, bg, hi, lo]: [Color;4], draw: impl Tui) -> impl Tui {
|
||||||
|
let top = exact(Some(1), None, Self::lo(bg, hi));
|
||||||
|
let low = exact(Some(1), None, Self::hi(bg, lo));
|
||||||
|
let draw = Tui::fg_bg(fg, bg, draw);
|
||||||
|
min_wh(w, h, bsp_s(top, bsp_n(low, fill_wh(draw))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TUI input loop event.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
||||||
|
pub struct TuiEvent(pub Event);
|
||||||
|
|
||||||
|
/// TUI key spec.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
||||||
|
pub struct TuiKey(pub Option<KeyCode>, pub KeyModifiers);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue