diff --git a/Cargo.toml b/Cargo.toml index ab18d99..e442ec2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,32 +5,35 @@ version = "0.15.0" description = "UI metaframework." [features] -default = ["lang", "sing", "draw", "play", "tui"] -lang = ["dep:dizzle"] -sing = ["dep:jack"] -draw = [] -play = [] -tui = ["draw", "dep:ratatui", "dep:crossterm"] -gui = ["draw", "dep:winit"] - +default = ["lang", "sing", "draw", "play", "tui", "text", "time", "rand", "okhsl"] bumpalo = ["dep:bumpalo"] +draw = [] 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] anyhow = { version = "1.0" } atomic_float = { version = "1" } 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" } -crossterm = { optional = true, version = "0.29.0" } -ratatui = { optional = true, version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] } +bumpalo = { optional = true, version = "3.19.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" ] } +unicode-width = { optional = true, version = "0.2" } +winit = { optional = true, version = "0.30.4", features = [ "x11" ]} [dev-dependencies] proptest = { version = "^1" } @@ -38,12 +41,6 @@ proptest-derive = { version = "^0.5.1" } tengri = { path = ".", features = [ "dsl" ] } #tengri_proc = { path = "./proc" } -[lib] -path = "src/tengri.rs" - -[profile.release] -lto = true - [profile.coverage] inherits = "test" lto = false diff --git a/README.md b/README.md index 0bf1e39..4cd2d57 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,15 @@ connect to jack audio connection kit to process chunks of audio and midi. ### draw -abstract interface layout system for defining interface layouts abstractly. +backend-agnostic layout pattern. ### play 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) @@ -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. +### time + +performance counter. + +### text + +backend-agnostic text layout. + ## license here and now, the blessings of `tengri` are invokable under the [**`AGPL3`**](./LICENSE). diff --git a/dizzle b/dizzle index 6026aff..068e26d 160000 --- a/dizzle +++ b/dizzle @@ -1 +1 @@ -Subproject commit 6026aff29f9a0ca9ecafdfac13ea59ddd65d8f83 +Subproject commit 068e26dd50699e51e9db01ac23fc0778074647bd diff --git a/src/.scratch.rs b/src/.scratch.rs index 1c7cebe..beb03ea 100644 --- a/src/.scratch.rs +++ b/src/.scratch.rs @@ -904,3 +904,458 @@ impl> MenuItem { ////test(":ctrl-alt-shift-x", ////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 = XYWH::zero(); + ////let _: XYWH = XYWH::from_position([a, b]); + ////let _: XYWH = XYWH::from_size([a, b]); + //let area: XYWH = 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 for TestComponent { + ////fn draw (&self, _to: &mut TuiOut) {} + ////} + ////impl Handle for TestComponent { + ////fn handle (&mut self, _from: &TuiIn) -> Perhaps { Ok(None) } + ////} + ////let engine = Tui::new(Box::<&mut Vec>::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 PartialEq for Measure { fn eq (&self, other: &Self) -> bool { self.w() == other.w() && self.h() == other.h() } } +////impl W for Measure { fn w (&self) -> N { (self.x.load(Relaxed) as u16).into() } } +////impl H for Measure { fn h (&self) -> N { (self.y.load(Relaxed) as u16).into() } } +////impl Debug for Measure { 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 Measure { + ////pub fn set_w (&self, w: impl Into) -> &Self { self.x.store(w.into(), Relaxed); self } + ////pub fn set_h (&self, h: impl Into) -> &Self { self.y.store(h.into(), Relaxed); self } + ////pub fn set_wh (&self, w: impl Into, h: impl Into) -> &Self { self.set_w(w); self.set_h(h); self } + ////pub fn format (&self) -> Arc { format!("{}x{}", self.w(), self.h()).into() } + ////pub fn of , O: Screen> (&self, item: T) -> Bsp, 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 From> for Measure { fn from (WH(x, y): WH) -> Self { Self::new(x, y) } } +////impl Clone for Measure { + ////fn clone (&self) -> Self { Self { __: Default::default(), x: self.x.clone(), y: self.y.clone(), } } +////} +////impl> Draw for Measure { + ////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> Layout for Align { + ////fn layout_x (&self, to: XYWH) -> 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 { + ////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> Layout for Min { + ////fn layout (&self, area: XYWH) -> XYWH { + ////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> Layout for Max { + ////fn layout (&self, area: XYWH) -> XYWH { + ////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 where + ////I: Iterator + 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 ( + ////item_offset: S::Unit, item_height: S::Unit, item: impl Draw + ////) -> impl Draw { + ////Push::Y(item_offset, Fixed::Y(item_height, Fill::X(item))) + ////} + ////const fn to_south_west ( + ////item_offset: S::Unit, item_height: S::Unit, item: impl Draw + ////) -> impl Draw { + ////Push::Y(item_offset, Align::nw(Fixed::Y(item_height, Fill::X(item)))) + ////} + ////const fn to_east ( + ////item_offset: S::Unit, item_width: S::Unit, item: impl Draw + ////) -> impl Draw { + ////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>>>, I, F, fn(A, usize)->B + ////> where + ////O: Screen, + ////B: Draw, + ////I: Iterator + 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>>, + ////I, F, + ////impl Fn(A, usize)->Push>> + 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 for Map where + ////O: Screen, + ////B: Layout, + ////I: Iterator + Send + Sync + 'a, + ////F: Fn() -> I + Send + Sync + 'a, + ////G: Fn(A, usize)->B + Send + Sync + ////{ + ////fn layout (&self, area: XYWH) -> XYWH { + ////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 for Map where + ////O: Screen, + ////B: Draw, + ////I: Iterator + 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 ( + ////direction: Direction, area: XYWH, a: N + ////) -> (XYWH, XYWH) { + ////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 , B: TwoD> ( + ////area: [N;4], + ////direction: Direction, + ////a: &A, + ////b: &B, + ////) -> [XYWH;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, Tail: Layout> Layout for Bsp { + ////fn layout_w (&self, area: XYWH) -> 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 { + ////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 { + ////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 { + ////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 { + ////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 { + ////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) -> XYWH { + ////bsp_areas(area, self.0, &self.1, &self.2)[2] + ////} + ////} + ////impl, Tail: Draw> Draw for Bsp { + ////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); + ////} + ////} + ////} diff --git a/src/tengri_macros.rs b/src/color.rs similarity index 59% rename from src/tengri_macros.rs rename to src/color.rs index 5007800..3f0d48a 100644 --- a/src/tengri_macros.rs +++ b/src/color.rs @@ -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 { (|$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!(); +} diff --git a/src/draw.rs b/src/draw.rs index a12f95b..323093e 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,4 +1,218 @@ -use crate::*; +use crate::{*, lang::*, color::*}; + +/// Drawable that supports dynamic dispatch. +/// +/// ``` +/// use tengri::draw::*; +/// struct TestScreen(bool); +/// struct TestWidget(bool); +/// impl Draw for TestWidget { +/// fn draw (&self, screen: &mut T) { +/// screen.0 |= self.0; +/// } +/// } +/// let mut screen = TestScreen(false); +/// TestWidget(false).draw(&mut screen); +/// TestWidget(true).draw(&mut screen); +/// TestWidget(false).draw(&mut screen); +/// ``` +pub trait Draw { + fn draw (&self, to: &mut T); +} +impl Draw for () { + fn draw (&self, __: &mut T) {} +} +impl Draw for F { + fn draw (&self, to: &mut T) { self(to) } +} +//impl> Draw for &D { + //fn draw (&self, to: &mut T) { (*self).draw(to) } +//} +impl> Draw for Arc { + fn draw (&self, to: &mut T) { (**self).draw(to) } +} +impl> Draw for RwLock { + fn draw (&self, to: &mut T) { self.read().unwrap().draw(to) } +} +impl> Draw for Option { + fn draw (&self, to: &mut T) { if let Some(draw) = self { draw.draw(to) } } +} + +/// Only render when condition is true. +/// +/// ``` +/// # fn test () -> impl tengri::Draw { +/// tengri::when(true, "Yes") +/// # } +/// ``` +pub fn when (condition: bool, draw: impl Draw) -> impl Draw { + move|to: &mut T|if condition { draw.draw(to); } +} + +/// Render one thing if a condition is true and another false. +/// +/// ``` +/// # fn test () -> impl tengri::Draw { +/// tengri::either(true, "Yes", "No") +/// # } +/// ``` +pub fn either (condition: bool, a: impl Draw, b: impl Draw) -> impl Draw { + move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) } +} + +/// Output target. +/// +/// ``` +/// use tengri::*; +/// +/// struct TestOut(impl TwoD); +/// +/// impl Screen for TestOut { +/// type Unit = u16; +/// fn area (&self) -> impl TwoD { self.0 } +/// fn area_mut (&mut self) -> &mut impl TwoD { &mut self.0 } +/// fn show + ?Sized> (&mut self, area: impl TwoD, _: &T) { +/// println!("show: {area:?}"); +/// () +/// } +/// } +/// +/// impl Draw for String { +/// fn draw (&self, to: &mut TestOut) { +/// to.area_mut().set_w(self.len() as u16); +/// } +/// } +/// ``` +pub trait Screen: Area + Aligned + Send + Sync + Sized { + type Unit: Coord; + /// Render drawable in area specified by `area` + fn show <'t, T: Draw + ?Sized> (&mut self, area: impl Area, content: &'t T); + + fn clipped_mut (&mut self, w: Option, h: Option) + -> &mut Self; + fn padded_mut (&mut self, w: Self::Unit, h: Self::Unit) + -> &mut Self; +} +impl X for O { fn x (&self) -> O::Unit { self.x() } } +impl Y for O { fn y (&self) -> O::Unit { self.y() } } +impl W for O { fn w (&self) -> O::Unit { self.w() } } +impl H for O { fn h (&self) -> O::Unit { self.h() } } + +/// Something that has area in 2D space. +pub trait Area: W + H { + fn wh (&self) -> WH { + WH(self.w(), self.h()) + } + fn clip_w (&self, w: N) -> [N;2] { + [self.w().min(w), self.h()] + } + fn clip_h (&self, h: N) -> [N;2] { + [self.w(), self.h().min(h)] + } + fn at_least (&self, w: N, h: N) -> Usually<&Self> { + if self.w() < w || self.h() < h { return Err(format!("min {w}x{h}").into()) } + Ok(self) + } + fn clip_wh (&self, w: Option, h: Option) -> [N;2] { + let w = w.map(|w|self.w().min(w)).unwrap_or(self.w()); + let h = h.map(|h|self.h().min(h)).unwrap_or(self.h()); + [w, h] + } +} + +/// Something that has length along horizontal axis. +pub trait W { + fn w (&self) -> N { N::zero() } + fn w_min (&self) -> N { self.w() } + fn w_max (&self) -> N { self.w() } +} +fn w_exact > (_w: N, _c: impl Draw) -> impl Draw { + move|_to: &mut T|{ todo!() } +} +fn w_fill > (_c: impl Draw) -> impl Draw { + move|_to: &mut T|{ todo!() } +} +fn w_min > (w: N, x: impl Draw) -> impl Draw { + min(Some(w), None, x) +} +fn w_max (w: N, x: impl Draw) -> impl Draw { + max(Some(w), None, x) +} + +/// Something that has length along vertical axis. +pub trait H { + fn h (&self) -> N { N::zero() } + fn h_min (&self) -> N { self.h() } + fn h_max (&self) -> N { self.h() } +} +fn h_exact > (_h: N, _c: impl Draw) -> impl Draw { + move|_to: &mut T|{ todo!() } +} +fn h_fill > (_c: impl Draw) -> impl Draw { + move|_to: &mut T|{ todo!() } +} +fn h_min > (h: N, x: impl Draw) -> impl Draw { + min(None, Some(h), x) +} +fn h_max (h: N, x: impl Draw) -> impl Draw { + max(None, Some(h), x) +} + +/// Something that has a [Measure] of its rendered size. +pub trait Measured {} + +/// Point along (X, Y). +pub trait Point: X + Y { + fn xy (&self) -> XY { XY(self.x(), self.y()) } +} + +/// Something that has `[0, 0]` at a particular point. +pub trait Anchored { fn anchor (&self) -> Align; } + +/// Something that has a bounding box +/// +/// ``` +/// use tengri::{Bounded, XYWH}; +/// let bounded: Bounded = Bounded(0, 0 ,0 ,0 ,""); +/// ``` +pub trait Bounding : Point + Area + Anchored { + /// Iterate over every covered X coordinate. + fn iter_x (&self) -> impl Iterator where N: std::iter::Step { + self.x_left()..self.x_right() + } + /// Iterate over every covered Y coordinate. + fn iter_y (&self) -> impl Iterator where N: std::iter::Step { + self.y_top()..self.y_bottom() + } + fn x_left (&self) -> N { + use Align::*; + let w = self.w(); + let a = self.anchor(); + let d = match a { NW|W|SW => 0.into(), N|X|C|Y|S => w/2.into(), NE|E|SE => w }; + self.x().minus(d) + } + fn x_right (&self) -> N { + use Align::*; + let w = self.w(); + let a = self.anchor(); + let d = match a { NW|W|SW => w, N|X|C|Y|S => w/2.into(), NE|E|SE => 0.into() }; + self.x().plus(d) + } + fn y_top (&self) -> N { + use Align::*; + let a = self.anchor(); + let h = self.h(); + let d = match a { NW|N|NE => 0.into(), W|X|C|Y|E => h/2.into(), SW|S|SE => h }; + self.y().minus(d) + } + fn y_bottom (&self) -> N { + use Align::*; + let a = self.anchor(); + let h = self.h(); + let d = match a { NW|N|NE => h, W|X|C|Y|E => h/2.into(), SW|S|SE => 0.into() }; + self.y().plus(d) + } +} /// A cardinal direction. /// @@ -10,16 +224,6 @@ use crate::*; North, South, East, West, Above, Below } -/// 9th of area to place. -/// -/// ``` -/// let alignment = tengri::Alignment::Center; -/// ``` -#[cfg_attr(test, derive(Arbitrary))] -#[derive(Debug, Copy, Clone, Default)] pub enum Alignment { - #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W -} - /// A numeric type that can be used as coordinate. /// /// FIXME: Replace with `num` crate? @@ -43,65 +247,79 @@ pub trait Coord: Send + Sync + Copy } /// Point along horizontal axis. -pub trait X { fn x (&self) -> N { N::zero() } } +pub trait X { + fn x (&self) -> N { N::zero() } + fn push (a: impl Draw) -> impl Draw { a } + fn pull (a: impl Draw) -> impl Draw { a } +} /// Point along vertical axis. -pub trait Y { fn y (&self) -> N { N::zero() } } - -/// Point along (X, Y). -pub trait Point: X + Y { fn xy (&self) -> XY { XY(self.x(), self.y()) } } - -/// Length along horizontal axis. -pub trait W { - fn w (&self) -> N { N::zero() } - fn w_min (&self) -> N { self.w() } - fn w_max (&self) -> N { self.w() } +pub trait Y { + fn y (&self) -> N { N::zero() } + fn push (a: impl Draw) -> impl Draw { a } + fn pull (a: impl Draw) -> impl Draw { a } } -/// Length along vertical axis. -pub trait H { - fn h (&self) -> N { N::zero() } - fn h_min (&self) -> N { self.h() } - fn h_max (&self) -> N { self.h() } -} -/// Area in 2D space. -pub trait Area: W + H { - fn wh (&self) -> WH { WH(self.w(), self.h()) } - fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] } - fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] } - fn at_least (&self, w: N, h: N) -> Usually<&Self> { - if self.w() < w || self.h() < h { return Err(format!("min {w}x{h}").into()) } - Ok(self) +//impl> Draw for Bounded { + //fn draw (&self, to: &mut O) { + //let area = to.area(); + //*to.area_mut() = self.0; + //self.1.draw(to); + //*to.area_mut() = area; + //} +//} + +/// A point (X, Y). +/// +/// ``` +/// let xy = tengri::XY(0u16, 0); +/// ``` +#[cfg_attr(test, derive(Arbitrary))] +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct XY(pub C, pub C); +impl X for XY { fn x (&self) -> N { self.0 } } +impl Y for XY { fn y (&self) -> N { self.1 } } + +/// A size (Width, Height). +/// +/// ``` +/// let wh = tengri::WH(0u16, 0); +/// ``` +#[cfg_attr(test, derive(Arbitrary))] +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct WH(pub C, pub C); +impl W for WH { fn w (&self) -> N { self.0 } } +impl H for WH { fn h (&self) -> N { self.1 } } +impl WH { + fn min > (&self, x: impl Draw) -> impl Draw { + min(Some(self.w()), Some(self.h()), x) + } + fn exact > (&self, c: impl Draw) -> impl Draw { + move|_to: &mut T|{ todo!() } + } + fn fill > (c: impl Draw) -> impl Draw { + move|_to: &mut T|{ todo!() } } } -/// Which corner/side of a box is 0, 0 -pub trait Anchor { fn anchor (&self) -> Alignment; } - -/// Bounding box -pub trait Bounding: Point + Area + Anchor { - fn x_left (&self) -> N { - use Alignment::*; - let w = self.w(); - self.x().minus(match self.anchor() { NW|W|SW => 0, N|X|C|Y|S => w / 2, NE|E|SE => w } - } - fn x_right (&self) -> N { - use Alignment::*; - let w = self.w(); - self.x().plus(match self.anchor() { NW|W|SW => w, N|X|C|Y|S => w/2, NE|E|SE => 0 }) - } - fn y_top (&self) -> N { - use Alignment::*; - let h = self.h(); - self.y().minus(match self.anchor() { NW|N|NE => 0, W|X|C|Y|E => h/2, SW|E|SE => h }) - } - fn y_bottom (&self) -> N { - use Alignment::*; - let h = self.h(); - self.y().plus(match self.anchor() { NW|N|NE => h, W|X|C|Y|E => h/2, SW|E|SE => 0 }) - } -} +/// Point with size. +/// +/// ``` +/// let xywh = tengri::XYWH(0u16, 0, 0, 0); +/// assert_eq!(tengri::XYWH(10u16, 10, 20, 20).center(), tengri::XY(20, 20)); +/// ``` +/// +/// * [ ] TODO: anchor field (determines at which corner/side is X0 Y0) +/// +#[cfg_attr(test, derive(Arbitrary))] +#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XYWH( + pub C, pub C, pub C, pub C +); +impl X for XYWH { fn x (&self) -> N { self.0 } } +impl Y for XYWH { fn y (&self) -> N { self.1 } } +impl W for XYWH { fn w (&self) -> N { self.0 } } +impl H for XYWH { fn h (&self) -> N { self.1 } } impl XYWH { pub fn zero () -> Self { @@ -116,18 +334,7 @@ impl XYWH { pub fn clipped (&self, wh: WH) -> XYWH { Self(self.x(), self.y(), wh.w(), wh.h()) } - /// Iterate over every covered X coordinate. - pub fn iter_x (&self) -> impl Iterator where N: std::iter::Step { - let Self(x, _, w, _) = *self; - x..(x+w) - } - /// Iterate over every covered Y coordinate. - pub fn iter_y (&self) -> impl Iterator where N: std::iter::Step { - let Self(_, y, _, h) = *self; - y..(y+h) - } pub fn center (&self) -> XY { - let Self(x, y, w, h) = self; XY(self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into())) } pub fn centered (&self) -> XY { @@ -148,291 +355,105 @@ impl XYWH { } } -/// A point (X, Y). -/// -/// ``` -/// let xy = tengri::XY(0u16, 0); -/// ``` -#[cfg_attr(test, derive(Arbitrary))] -#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XY(pub C, pub C); - -/// A size (Width, Height). -/// -/// ``` -/// let wh = tengri::WH(0u16, 0); -/// ``` -#[cfg_attr(test, derive(Arbitrary))] -#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct WH(pub C, pub C); - -/// Point with size. -/// -/// ``` -/// let xywh = tengri::XYWH(0u16, 0, 0, 0); -/// assert_eq!(tengri::XYWH(10u16, 10, 20, 20).center(), tengri::XY(20, 20)); -/// ``` -/// -/// * [ ] TODO: anchor field (determines at which corner/side is X0 Y0) -/// -#[cfg_attr(test, derive(Arbitrary))] -#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XYWH( - pub C, pub C, pub C, pub C -); - -/// Drawable with dynamic dispatch. -pub trait Draw: Fn(&mut T)->Usually<()> { fn draw (&self, to: &mut T) -> Usually<()>; } - -/// Drawable thunk. -impl()> Draw for F { fn draw (&self, to: &mut T) -> Usually<()> { self(to) } } - -impl Draw for () { fn draw (&self, _: &mut S) -> Usually<()> { Ok(()) } } -impl Draw for Box> { fn draw (&self, to: &mut S) -> Usually<()> { (**self).draw(to) } } - -impl> Draw for &D { fn draw (&self, to: &mut S) -> Usually<()> { (*self).draw(to) } } -impl> Draw for &mut D { fn draw (&self, to: &mut S) -> Usually<()> { (**self).draw(to) } } -impl> Draw for Arc { fn draw (&self, to: &mut S) -> Usually<()> { (**self).draw(to) } } -impl> Draw for RwLock { fn draw (&self, to: &mut S) -> Usually<()> { self.read().unwrap().draw(to) } } -impl> Draw for Option { fn draw (&self, to: &mut S) { if let Some(draw) = self { draw.draw(to) } } } - -/// Draw the content or its error message. -/// -/// ``` -/// let _ = tengri::Catcher::::new(Ok(Some("hello"))); -/// let _ = tengri::Catcher::::new(Ok(None)); -/// let _ = tengri::Catcher::::new(Err("draw fail".into())); -/// ``` -pub fn catcher (error: Perhaps, draw: impl Draw) -> impl Draw { - move|to|match self.0.as_ref() { - Ok(Some(content)) => draw(to), - Ok(None) => to.blit(&"", 0, 0, Some(Style::default().yellow())), - Err(e) => { - let err_fg = Color::Rgb(255,224,244); - let err_bg = Color::Rgb(96,24,24); - let title = Bsp::e(Tui::bold(true, "oopsie daisy. "), "rendering failed."); - let error = Bsp::e("\"why?\" ", Tui::bold(true, format!("{e}"))); - to.place(&Tui::fg_bg(err_fg, err_bg, Bsp::s(title, error))) - } - } -} -/// Only render when condition is true. -/// -/// ``` -/// fn test () -> impl tengri::Draw { -/// tengri::when(true, "Yes") -/// } -/// ``` -pub fn when (condition: bool, draw: impl Draw) -> impl Draw { - move|to|Ok(if condition { draw(to)? } }) -} - -/// Render one thing if a condition is true and another false. -/// -/// ``` -/// fn test () -> impl tengri::Draw { -/// tengri::either(true, "Yes", "No") -/// } -/// ``` -pub fn either (condition: bool, a: impl Draw, b: impl Draw) -> impl Draw { - move|to|Ok(if condition { a(to)? } else { b(to)? }) -} /// Set maximum width and/or height of drawing area. -pub fn clip (w: Option, h: Option, draw: impl Draw) -> impl Draw { - move|to|draw(to.clip(w, h)) -} - -pub fn align () { - //impl> Layout for Align { - //fn layout_x (&self, to: XYWH) -> O::Unit { - //use Alignment::*; - //match self.0 { - //NW | W | SW => to.x(), - //N | Center | 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 { - //use Alignment::*; - //match self.0 { - //NW | N | NE => to.y(), - //W | Center | 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!(), - //} - //} - //} +pub fn clip , N: Coord> (w: Option, h: Option, draw: impl Draw) -> impl Draw { + move|to: &mut T|draw.draw(to.clipped_mut(w, h)) } /// Shrink drawing area symmetrically. -pub fn pad (w: Option, h: Option, draw: impl Draw) -> impl Draw { - move|to|draw(to.pad(w, h)) - //impl> Layout for Pad { - //fn layout_x (&self, area: XYWH) -> O::Unit { area.x().plus(self.dx()) } - //fn layout_y (&self, area: XYWH) -> O::Unit { area.x().plus(self.dx()) } - //fn layout_w (&self, area: XYWH) -> O::Unit { area.w().minus(self.dx() * 2.into()) } - //fn layout_h (&self, area: XYWH) -> O::Unit { area.h().minus(self.dy() * 2.into()) } - //} +pub fn pad , N: Coord> (w: N, h: N, draw: impl Draw) -> impl Draw { + move|to: &mut T|draw.draw(to.padded_mut(w, h)) +} +pub fn pad_x , N: Coord> (w: N, draw: impl Draw) -> impl Draw { + pad(w, N::zero(), draw) +} +pub fn pad_y , N: Coord> (h: N, draw: impl Draw) -> impl Draw { + pad(N::zero(), h, draw) +} +pub fn pad_xy , N: Coord> (p: N, draw: impl Draw) -> impl Draw { + pad(p, p, draw) +} + +/// 9th of area to place. +/// +/// ``` +/// let alignment = tengri::Align::C; +/// use ::tengri::*; +/// let area = XYWH(10u16, 10, 20, 20); +/// fn test (area: XYWH, item: &impl Draw, expected: [u16;4]) { +/// //assert_eq!(Lay::layout(item, area), expected); +/// //assert_eq!(Draw::layout(item, area), expected); +/// }; +/// +/// let four = exact_xy(4, 4, "foo"); +/// 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 = exact_xy(4, 2, "foo"); +/// 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(pub Align, pub T); +/// ``` +#[cfg_attr(test, derive(Arbitrary))] +#[derive(Debug, Copy, Clone, Default)] pub enum Align { + #[default] C, X, Y, NW, N, NE, E, SE, S, SW, W +} +pub trait Aligned { + fn aligned (&mut self, align: Align) -> &mut Self; +} +/// Set 0, 0 of drawing subarea. +pub fn align (alignment: Align, draw: impl Draw) -> impl Draw { + move|to: &mut T|draw.draw(to.aligned(alignment)) +} +impl Align { + pub fn n (x: impl Draw) -> impl Draw { align(Align::N, x) } + pub fn s (x: impl Draw) -> impl Draw { align(Align::S, x) } + pub fn e (x: impl Draw) -> impl Draw { align(Align::E, x) } + pub fn w (x: impl Draw) -> impl Draw { align(Align::W, x) } + pub fn nw (x: impl Draw) -> impl Draw { align(Align::NW, x) } + pub fn ne (x: impl Draw) -> impl Draw { align(Align::NE, x) } + pub fn sw (x: impl Draw) -> impl Draw { align(Align::SW, x) } + pub fn se (x: impl Draw) -> impl Draw { align(Align::SE, x) } + pub fn c (x: impl Draw) -> impl Draw { align(Align::C, x) } } /// Only draw content if area is above a certain size. /// /// ``` -/// let minimum = tengri::Min::XY(3, 5, "Hello"); // 5x5 +/// let minimum = tengri::min_wh(3, 5, "Hello"); // 5x5 /// ``` -pub fn min (w: N, draw: impl Draw) -> impl Draw { - move|to|draw(to.min(w, h)) +pub fn min (w: Option, h: Option, draw: impl Draw) -> impl Draw { + move|to: &mut T| { todo!() }//draw.draw(to.min(w, h)) } /// Set the maximum width and/or height of the content. /// /// ``` -/// let maximum = tengri::Max::XY(3, 5, "Hello"); // 3x1 +/// let maximum = tengri::max_wh(3, 5, "Hello"); // 3x1 /// ``` -pub fn max (x: N, draw: impl Draw) -> impl Draw { - move|to|draw(to.max(w, h)) +pub fn max (w: Option, h: Option, draw: impl Draw) -> impl Draw { + move|to: &mut T| { todo!() }// { todo!() }//draw.draw(to.max(w, h)) } // pub fn displace ... -/// Shrink by amount on each axis. -/// -/// ``` -/// /// TODO -/// ``` -pub fn border (on: bool, style: S, draw: impl Draw) -> impl Draw { - fill_wh(above(when(on, |output|{/*TODO*/}), pad(Some(1), Some(1), draw))) -} - -/// Stackably padded. -/// -/// ``` -/// /// TODO -/// ``` -pub fn phat ( - w: N, h: N, [fg, bg, hi, lo]: [Color;4], draw: impl Draw +pub fn iter , F: Fn(U)->dyn Draw> ( + _items: impl Iterator, _cb: F ) -> impl Draw { - let top = exact_w(1, Self::lo(bg, hi)); - let low = exact_h(1, Self::hi(bg, lo)); - let draw = TuiOut::fg_bg(fg, bg, draw); - min_wh(w, h, bsp_s(top, bsp_n(low, fill_wh(draw)))) -} - -pub fn iter (items: impl Iterator>, callback: Fn(dyn Draw)->impl Draw) -> impl Draw { - //todo!() - - //impl<'a, O, A, B, I, F, G> Map where - //I: Iterator + 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 ( - //item_offset: S::Unit, item_height: S::Unit, item: impl Draw - //) -> impl Draw { - //Push::Y(item_offset, Fixed::Y(item_height, Fill::X(item))) - //} - //const fn to_south_west ( - //item_offset: S::Unit, item_height: S::Unit, item: impl Draw - //) -> impl Draw { - //Push::Y(item_offset, Align::nw(Fixed::Y(item_height, Fill::X(item)))) - //} - //const fn to_east ( - //item_offset: S::Unit, item_width: S::Unit, item: impl Draw - //) -> impl Draw { - //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>>>, I, F, fn(A, usize)->B - //> where - //O: Screen, - //B: Draw, - //I: Iterator + 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>>, - //I, F, - //impl Fn(A, usize)->Push>> + 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 for Map where - //O: Screen, - //B: Layout, - //I: Iterator + Send + Sync + 'a, - //F: Fn() -> I + Send + Sync + 'a, - //G: Fn(A, usize)->B + Send + Sync - //{ - //fn layout (&self, area: XYWH) -> XYWH { - //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 for Map where - //O: Screen, - //B: Draw, - //I: Iterator + 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; - //} - //} - //} + move|_to: &mut T|{ todo!() } } /// Split screen between two items, or layer them atop each other. @@ -446,163 +467,26 @@ pub fn iter (items: impl Iterator>, callback: Fn(dyn Draw /// let _ = tengri::draw::bsp(East, (), ()); /// let _ = tengri::draw::bsp(West, (), ()); /// ``` -pub fn bsp (dir: Direction, a: impl Draw, b: impl Draw) -> impl Draw { - move|to|{ - //fn split_cardinal ( - //direction: Direction, area: XYWH, a: N - //) -> (XYWH, XYWH) { - //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 , B: TwoD> ( - //area: [N;4], - //direction: Direction, - //a: &A, - //b: &B, - //) -> [XYWH;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, Tail: Layout> Layout for Bsp { - //fn layout_w (&self, area: XYWH) -> 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 { - //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 { - //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 { - //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 { - //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 { - //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) -> XYWH { - //bsp_areas(area, self.0, &self.1, &self.2)[2] - //} - //} - } +pub fn bsp (_dir: Direction, a: impl Draw, b: impl Draw) -> impl Draw { + move|_to: &mut T|{ todo!() } } - -/// 3-column layout with center priority. -/// -/// ``` -/// /// TODO -/// ``` -pub fn tryptich ( - top: boolean, [(w_a, ref a), (w_b, ref b), (w_c, ref c)]: [(N, impl Draw);3] -) -> impl Draw { - let Self { top, h, left: (w_a, ref a), middle: (w_b, ref b), right: (w_c, ref c) } = *self; - let a = exact_w(w_a, a); - let b = exact_w(w_b, align_w(b)); - let c = exact_w(w_c, c); - exact_h(h, if top { - bsp_above(fill_w(align_n(b)), bsp_above(fill_w(align_nw(a)), fill_w(align_ne(c)))) - } else { - bsp_above(fill_wh(align_c(b)), bsp_above(fill_wh(align_w(a)), fill_wh(align_e(c)))) - }) +pub fn bsp_n (a: impl Draw, b: impl Draw) -> impl Draw { + bsp(Direction::North, a, b) } - -impl> Draw for Measure { - 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); - } +pub fn bsp_s (a: impl Draw, b: impl Draw) -> impl Draw { + bsp(Direction::South, a, b) } - -impl> Draw for Bounded { - fn draw (&self, to: &mut O) { - let area = to.area(); - *to.area_mut() = self.0; - self.1.draw(to); - *to.area_mut() = area; - } +pub fn bsp_e (a: impl Draw, b: impl Draw) -> impl Draw { + bsp(Direction::East, a, b) } - -impl, Tail: Draw> Draw for Bsp { - 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); - } - } +pub fn bsp_w (a: impl Draw, b: impl Draw) -> impl Draw { + bsp(Direction::West, a, b) +} +pub fn bsp_a (a: impl Draw, b: impl Draw) -> impl Draw { + bsp(Direction::Above, a, b) +} +pub fn bsp_b (a: impl Draw, b: impl Draw) -> impl Draw { + bsp(Direction::Below, a, b) } /// Clear a pre-allocated buffer, then write into it. @@ -638,205 +522,24 @@ impl, Tail: Draw> Draw for Bsp { /// Stack things on top of each other, #[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) => {{ - let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)* bsp + let bsp = (); $(let bsp = bsp_b(bsp, $expr);)* bsp }}); /// Stack southward. #[macro_export] macro_rules! col (($($expr:expr),* $(,)?) => {{ - let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)* bsp + let bsp = (); $(let bsp = bsp_s(bsp, $expr);)* bsp }}); /// Stack northward. #[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) => {{ - let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)* bsp + let bsp = (); $(let bsp = bsp_n(bsp, $expr);)* bsp }}); /// Stack eastward. #[macro_export] macro_rules! row (($($expr:expr),* $(,)?) => {{ - let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)* bsp + let bsp = (); $(let bsp = bsp_e(bsp, $expr);)* bsp }}); -/// Interpret layout operation. -/// -/// ``` -/// # use tengri::*; -/// struct Target { xywh: XYWH /*platform-specific*/} -/// impl tengri::Out for Target { -/// type Unit = u16; -/// fn area (&self) -> XYWH { self.xywh } -/// fn area_mut (&mut self) -> &mut XYWH { &mut self.xywh } -/// fn show <'t, T: Draw + ?Sized> (&mut self, area: XYWH, content: &'t T) {} -/// } -/// -/// struct State {/*app-specific*/} -/// impl<'b> Namespace<'b, bool> for State {} -/// impl<'b> Namespace<'b, u16> for State {} -/// impl Understand 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 where - S: Understand - + 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 Perhapss remain in scope. - match frags.next() { - - Some("when") => output.place(&When::new( - state.namespace(arg0?)?.unwrap(), - Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap()) - )), - - Some("either") => output.place(&Either::new( - state.namespace(arg0?)?.unwrap(), - Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap()), - Thunk::new(move|output: &mut O|state.understand(output, &arg2).unwrap()) - )), - - Some("bsp") => output.place(&{ - let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap()); - let b = Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap()); - match frags.next() { - Some("n") => Bsp::n(a, b), - Some("s") => Bsp::s(a, b), - Some("e") => Bsp::e(a, b), - Some("w") => Bsp::w(a, b), - Some("a") => Bsp::a(a, b), - Some("b") => Bsp::b(a, b), - frag => unimplemented!("bsp/{frag:?}") - } - }), - - Some("align") => output.place(&{ - let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap()); - match frags.next() { - Some("n") => Align::n(a), - Some("s") => Align::s(a), - Some("e") => Align::e(a), - Some("w") => Align::w(a), - Some("x") => Align::x(a), - Some("y") => Align::y(a), - Some("c") => Align::c(a), - frag => unimplemented!("align/{frag:?}") - } - }), - - Some("fill") => output.place(&{ - let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap()); - match frags.next() { - Some("xy") | None => Fill::XY(a), - Some("x") => Fill::X(a), - Some("y") => Fill::Y(a), - frag => unimplemented!("fill/{frag:?}") - } - }), - - Some("fixed") => output.place(&{ - let axis = frags.next(); - let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") }; - let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap()); - match axis { - Some("xy") | None => Fixed::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb), - Some("x") => Fixed::X(state.namespace(arg0?)?.unwrap(), cb), - Some("y") => Fixed::Y(state.namespace(arg0?)?.unwrap(), cb), - frag => unimplemented!("fixed/{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!("fixed: unsupported axis {axis:?}") }; - let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap()); - match axis { - Some("xy") | None => Min::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb), - Some("x") => Min::X(state.namespace(arg0?)?.unwrap(), cb), - Some("y") => Min::Y(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!("fixed: unsupported axis {axis:?}") }; - let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap()); - match axis { - Some("xy") | None => Max::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb), - Some("x") => Max::X(state.namespace(arg0?)?.unwrap(), cb), - Some("y") => Max::Y(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!("fixed: unsupported axis {axis:?}") }; - let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap()); - 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) -} - -/// Trim string with [unicode_width]. -pub fn trim_string (max_width: usize, input: impl AsRef) -> 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) -> 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 -} - /// Memoize a rendering. /// /// ``` @@ -847,29 +550,6 @@ pub(crate) fn width_chars_max (max: u16, text: impl AsRef) -> u16 { pub view: Arc> } -impl<'a, T: AsRef> TrimString { - fn to_ref (&self) -> TrimStringRef<'_, T> { TrimStringRef(self.0, &self.1) } -} -impl Field { - pub fn new (direction: Direction) -> Field { - Field:: { - direction, - label: None, label_fg: None, label_bg: None, label_align: None, - value: None, value_fg: None, value_bg: None, value_align: None, - } - } - pub fn label ( - self, label: Option, align: Option, fg: Option, bg: Option - ) -> Field { - Field:: { label, label_fg: fg, label_bg: bg, label_align: align, ..self } - } - pub fn value ( - self, value: Option, align: Option, fg: Option, bg: Option - ) -> Field { - Field:: { value, value_fg: fg, value_bg: bg, value_align: align, ..self } - } -} -impl Clone for Measure { fn clone (&self) -> Self { Self { __: Default::default(), x: self.x.clone(), y: self.y.clone(), } } } impl Memo { pub fn new (value: T, view: U) -> Self { Self { value, view: Arc::new(view.into()) } @@ -883,93 +563,3 @@ impl Memo { None } } -impl PartialEq for Measure { fn eq (&self, other: &Self) -> bool { self.w() == other.w() && self.h() == other.h() } } -impl W for Measure { fn w (&self) -> O::Unit { (self.x.load(Relaxed) as u16).into() } } -impl H for Measure { fn h (&self) -> O::Unit { (self.y.load(Relaxed) as u16).into() } } -impl Debug for Measure { 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 Measure { - pub fn set_w (&self, w: impl Into) -> &Self { self.x.store(w.into(), Relaxed); self } - pub fn set_h (&self, h: impl Into) -> &Self { self.y.store(h.into(), Relaxed); self } - pub fn set_wh (&self, w: impl Into, h: impl Into) -> &Self { self.set_w(w); self.set_h(h); self } - pub fn format (&self) -> Arc { format!("{}x{}", self.w(), self.h()).into() } - pub fn of > (&self, item: T) -> Bsp, T> { Bsp::b(Fill::XY(self), item) } - pub fn new (x: O::Unit, y: O::Unit) -> Self { - Self { __: PhantomData::default(), x: Arc::new(x.atomic()), y: Arc::new(y.atomic()), } - } -} -impl From> for Measure { fn from (WH(x, y): WH) -> Self { Self::new(x, y) } } -// Implement layout op that increments X and/or Y by fixed amount. -macro_rules! push_pull(($T:ident: $method: ident)=>{ - layout_op_xy!(1: $T); - impl> Layout for $T { - fn layout_x (&self, area: XYWH) -> O::Unit { area.x().$method(self.dx()) } - fn layout_y (&self, area: XYWH) -> O::Unit { area.y().$method(self.dy()) } - } -}); -push_pull!(Push: plus); -push_pull!(Pull: minus); - -layout_op_xy!(0: Fill); - -impl> Layout for Fill { - fn layout_x (&self, area: XYWH) -> O::Unit { if self.dx() { area.x() } else { self.inner().layout_x(area) } } - fn layout_y (&self, area: XYWH) -> O::Unit { if self.dy() { area.y() } else { self.inner().layout_y(area) } } - fn layout_w (&self, area: XYWH) -> O::Unit { if self.dx() { area.w() } else { self.inner().layout_w(area) } } - fn layout_w_min (&self, area: XYWH) -> O::Unit { if self.dx() { area.w() } else { self.inner().layout_w_min(area) } } - fn layout_w_max (&self, area: XYWH) -> O::Unit { if self.dx() { area.w() } else { self.inner().layout_w_max(area) } } - fn layout_h (&self, area: XYWH) -> O::Unit { if self.dy() { area.h() } else { self.inner().layout_h(area) } } - fn layout_h_min (&self, area: XYWH) -> O::Unit { if self.dy() { area.h() } else { self.inner().layout_h_min(area) } } - fn layout_h_max (&self, area: XYWH) -> O::Unit { if self.dy() { area.h() } else { self.inner().layout_h_max(area) } } -} - -impl Fill { - #[inline] pub const fn dx (&self) -> bool { matches!(self, Self::X(_) | Self::XY(_)) } - #[inline] pub const fn dy (&self) -> bool { matches!(self, Self::Y(_) | Self::XY(_)) } -} - - -impl> Layout for Fixed { - fn layout_w (&self, area: XYWH) -> O::Unit { self.dx().unwrap_or(self.inner().layout_w(area)) } - fn layout_w_min (&self, area: XYWH) -> O::Unit { self.dx().unwrap_or(self.inner().layout_w_min(area)) } - fn layout_w_max (&self, area: XYWH) -> O::Unit { self.dx().unwrap_or(self.inner().layout_w_max(area)) } - fn layout_h (&self, area: XYWH) -> O::Unit { self.dy().unwrap_or(self.inner().layout_h(area)) } - fn layout_h_min (&self, area: XYWH) -> O::Unit { self.dy().unwrap_or(self.inner().layout_h_min(area)) } - fn layout_h_max (&self, area: XYWH) -> O::Unit { self.dy().unwrap_or(self.inner().layout_h_max(area)) } -} - - -impl> Layout for Max { - fn layout (&self, area: XYWH) -> XYWH { - 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> Layout for Min { - fn layout (&self, area: XYWH) -> XYWH { - 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 X for XY { fn x (&self) -> N { self.0 } } -impl Y for XY { fn y (&self) -> N { self.1 } } -impl W for WH { fn w (&self) -> N { self.0 } } -impl H for WH { fn h (&self) -> N { self.1 } } -impl X for XYWH { fn x (&self) -> N { self.0 } } -impl Y for XYWH { fn y (&self) -> N { self.1 } } -impl W for XYWH { fn w (&self) -> N { self.2 } } -impl H for XYWH { fn h (&self) -> N { self.3 } } -impl X for O { fn x (&self) -> O::Unit { self.area().x() } } -impl Y for O { fn y (&self) -> O::Unit { self.area().y() } } -impl W for O { fn w (&self) -> O::Unit { self.area().w() } } -impl H for O { fn h (&self) -> O::Unit { self.area().h() } } diff --git a/src/eval.rs b/src/eval.rs new file mode 100644 index 0000000..d47aae6 --- /dev/null +++ b/src/eval.rs @@ -0,0 +1,220 @@ +use crate::*; + +/// Interpret layout operation. +/// +/// ``` +/// # use tengri::*; +/// struct Target { xywh: XYWH /*platform-specific*/} +/// impl tengri::Out for Target { +/// type Unit = u16; +/// fn area (&self) -> XYWH { self.xywh } +/// fn area_mut (&mut self) -> &mut XYWH { &mut self.xywh } +/// fn show <'t, T: Draw + ?Sized> (&mut self, area: XYWH, content: &'t T) {} +/// } +/// +/// struct State {/*app-specific*/} +/// impl<'b> Namespace<'b, bool> for State {} +/// impl<'b> Namespace<'b, u16> for State {} +/// impl Understand 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 where + S: Understand + + 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 Perhapss 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 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 where + S: Understand + + 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) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ecde0e6 --- /dev/null +++ b/src/lib.rs @@ -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),*) })* + } + }; +); diff --git a/src/play.rs b/src/play.rs index f4b932c..b608106 100644 --- a/src/play.rs +++ b/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); impl Exit { - pub fn run (run: impl Fn()->Usually) -> Usually { + pub fn run (run: impl Fn(Self)->Usually) -> Usually { 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. pub fn new_sleep ( exit: Arc, time: Duration, call: F @@ -44,9 +46,9 @@ impl Thread { Self::new(exit, move |perf| { let _ = call(perf); std::thread::sleep(time); }) } - /// Spawn a TUI thread that runs `callt least one, then repeats - /// until `exit`, using polling to run every `time` msec. - pub fn new_poll ( + /// Spawn a thread that uses [crossterm::event::poll] + /// to run `call` every `time` msec. + #[cfg(feature = "tui")]pub fn new_poll ( exit: Arc, time: Duration, call: F ) -> Result where F: Fn(&PerfModel)->() + Send + Sync + 'static @@ -98,48 +100,3 @@ impl Thread { } } } -impl> Action for Option { fn action (&self, _: &mut S) -> Perhaps { 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 { - if self.enabled { - Some(self.clock.raw()) - } else { - None - } - } - pub fn get_t1 (&self, t0: Option) -> Option { - 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, 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 { - 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 - } - } -} diff --git a/src/sing.rs b/src/sing.rs index 08c0bcb..9775585 100644 --- a/src/sing.rs +++ b/src/sing.rs @@ -1,7 +1,27 @@ -use crate::*; +use crate::{*, time::PerfModel}; use ::jack::*; 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>, 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. /// /// ``` @@ -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 (&self, op: impl FnOnce(&Client)->T) -> T { + self.jack().with_client(op) + } + fn port_by_name (&self, name: &str) -> Option> { + self.with_client(|client|client.port_by_name(name)) + } + fn port_by_id (&self, id: u32) -> Option> { + self.with_client(|c|c.port_by_id(id)) + } + fn register_port (&self, name: impl AsRef) -> Usually> { + 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, 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) + } + } + }; + +} diff --git a/src/tengri.rs b/src/tengri.rs deleted file mode 100644 index 997b9bd..0000000 --- a/src/tengri.rs +++ /dev/null @@ -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}}; diff --git a/src/tengri_impl.rs b/src/tengri_impl.rs deleted file mode 100644 index 3154266..0000000 --- a/src/tengri_impl.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::*; -use Direction::*; -use rand::{thread_rng, distributions::uniform::UniformSampler}; - -mod xywh { - use crate::*; -} diff --git a/src/tengri_struct.rs b/src/tengri_struct.rs deleted file mode 100644 index 4224d0c..0000000 --- a/src/tengri_struct.rs +++ /dev/null @@ -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::::default(); - /// ``` - #[derive(Default)] pub struct Measure { - pub x: Arc, - pub y: Arc, - pub __: PhantomData, - } - - /// Increment X and/or Y coordinate. - /// - /// ``` - /// let pushed = tengri::Push::XY(2, 2, "Hello"); - /// ``` - pub enum Push { 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 { 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 { X(A), Y(A), XY(A) } - - /// Set fixed size for content. - /// - /// ``` - /// let fixed = tengri::Fixed::XY(3, 5, "Hello"); // 3x5 - /// ``` - pub enum Fixed { 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 { 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 { 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, item: &impl Draw, 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(pub Alignment, pub T); - - // TODO DOCUMENTME - pub enum Pad { X(U, A), Y(U, A), XY(U, U, A), } - - // TODO DOCUMENTME - pub struct Bordered(pub bool, pub S, pub W); - - // TODO DOCUMENTME - pub struct Border(pub bool, pub S); - - /// TODO DOCUMENTME - /// - /// ``` - /// use tengri::{Bounded, XYWH}; - /// let bounded: Bounded = Bounded(0, 0 ,0 ,0 ,""); - /// ``` - pub struct Bounded(N, N, N, N, pub D); - - /// Draws items from an iterator. - /// - /// ``` - /// // FIXME let map = tengri::Map(||[].iter(), |_|{}); - /// ``` - pub struct Map - where - I: Iterator + 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, - #[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(pub Theme, pub Label, pub Value); - // TODO DOCUMENTME - pub struct FieldV(pub Theme, pub Label, pub Value); - // TODO: - pub struct Field { - pub direction: Direction, - pub label: Option, - pub label_fg: Option, - pub label_bg: Option, - pub label_align: Option, - pub value: Option, - pub value_fg: Option, - pub value_bg: Option, - pub value_align: Option, - } -} -: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, pub KeyModifiers); - /// TUI buffer sized by `usize` instead of `u16`. - #[derive(Default)] pub struct BigBuffer { - pub width: usize, - pub height: usize, - pub content: Vec - } - pub struct Foreground(pub Color, pub Item); - pub struct Background(pub Color, pub Item); - pub struct Modify(pub bool, pub Modifier, pub T); - pub struct Styled(pub Option