diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..1d953f4 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use nix diff --git a/.gitignore b/.gitignore index 8899d91..4f90c40 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -target -cov *.profraw +.direnv +cov +target diff --git a/Cargo.toml b/Cargo.toml index b0fd6fa..8657a68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ winit = { optional = true, version = "0.30.4", features = [ "x11" ]} [dev-dependencies] proptest = { version = "^1" } proptest-derive = { version = "^0.5.1" } -tengri = { path = ".", features = [ "dsl" ] } +tengri = { path = "." } #tengri_proc = { path = "./proc" } [profile.coverage] diff --git a/Justfile b/Justfile index 1d5f794..8d7c790 100644 --- a/Justfile +++ b/Justfile @@ -2,8 +2,9 @@ export LLVM_PROFILE_FILE := "cov/cargo-test-%p-%m.profraw" grcov-binary := "--binary-path ./target/coverage/deps/" grcov-ignore := "--ignore-not-existing --ignore '../*' --ignore \"/*\" --ignore 'target/*'" +[private] default: - just -l + @just -l bacon: bacon -s @@ -29,6 +30,7 @@ doc: cargo doc example-tui-00: - cargo run -p tengri_tui --example tui_00 + cargo run --example tui_00 + example-tui-01: - cargo run -p tengri_tui --example tui_01 + cargo run --example tui_01 diff --git a/examples/tui_00.rs b/examples/tui_00.rs index 80e7acd..929c56d 100644 --- a/examples/tui_00.rs +++ b/examples/tui_00.rs @@ -1,46 +1,74 @@ -use ::{std::{io::stdout, sync::{Arc, RwLock}}, ratatui::style::Color, tengri::*}; - tui_main!(State { cursor: 10, ..Default::default() }); -namespace!(State: bool {}); -namespace!(State: u16 {}); -namespace!(State: Color {}); - handle!(TuiIn: |self: State, input|Action::from(input).eval(self).map(|_|None)); - view!(State: TuiOut: [ evaluate_output_expression, evaluate_output_expression_tui ]); - draw!(State: TuiOut: [ draw_example ]); +use ::{ + std::{io::stdout, sync::{Arc, RwLock}}, + tengri::{*, term::*, lang::*, keys::*, draw::*, space::*}, + ratatui::style::Color, +}; + +tui_main!(State { cursor: 10, ..Default::default() }); + +impl Apply> for State { + fn apply (&mut self, input: &TuiEvent) -> Usually { + todo!() + } +} + +impl View for State { + fn view (&self) -> impl Draw { + let index = self.cursor + 1; + let wh = (self.size.w(), self.size.h()); + let src = VIEWS.get(self.cursor).unwrap_or(&""); + let heading = format!("State {}/{} in {:?}", index, VIEWS.len(), &wh); + let title = bg(Color::Rgb(60, 10, 10), push_y(1, align_n(heading))); + let code = bg(Color::Rgb(10, 60, 10), push_y(2, align_n(format!("{}", src)))); + //let content = ;//();//bg(Color::Rgb(10, 10, 60), View(self, CstIter::new(src))); + self.size.of(bsp_s(title, bsp_n(code, self.understand(to, &src).unwrap()))) + } +} + #[derive(Debug, Default)] struct State { - /** Rendered window size */ size: Measure, - /** Command history (undo/redo) */ history: Vec, - /** User-controllable value */ cursor: usize, + /** Command history (undo/redo). */ + history: Vec, + /** User-controllable value. */ + cursor: usize, + /** Rendered window size. */ + size: crate::space::Size, } -impl_from!(Action: |input: &TuiIn| todo!()); + +//impl_from!(Action: |input: &TuiIn| todo!()); #[derive(Debug)] enum Action { - /** Increment cursor */ Next, - /** Decrement cursor */ Prev + /** Increment cursor */ + Next, + /** Decrement cursor */ + Prev } -fn draw_example (state: &State, to: &mut TuiOut) { - let index = state.cursor + 1; - let wh = state.size.wh(); - let src = VIEWS.get(state.cursor).unwrap_or(&""); - let heading = format!("State {}/{} in {:?}", index, VIEWS.len(), &wh); - let title = Tui::bg(Color::Rgb(60, 10, 10), Push::Y(1, Align::n(heading))); - let code = Tui::bg(Color::Rgb(10, 60, 10), Push::Y(2, Align::n(format!("{}", src)))); - //let content = ;//();//Tui::bg(Color::Rgb(10, 10, 60), View(state, CstIter::new(src))); - state.size.of(Bsp::s(title, Bsp::n(code, state.understand(to, &src).unwrap()))).draw(to) + +fn draw_example (state: &State, to: &mut Tui) { } + impl Action { - const BINDS: &'static str = stringify! { (@left prev) (@right next) }; + + const BINDS: &'static str = stringify! { + (@left prev) + (@right next) + }; + fn eval (&self, state: &mut State) -> Perhaps { use Action::*; match self { Next => Self::next(state), Prev => Self::prev(state), } } + fn next (state: &mut State) -> Perhaps { state.cursor = (state.cursor + 1) % VIEWS.len(); Ok(Some(Self::Prev)) } + fn prev (state: &mut State) -> Perhaps { state.cursor = if state.cursor > 0 { state.cursor - 1 } else { VIEWS.len() - 1 }; Ok(Some(Self::Next)) } + } + const VIEWS: &'static [&'static str] = &[ stringify! { :hello-world }, @@ -121,3 +149,10 @@ const VIEWS: &'static [&'static str] = &[ }, ]; + +//namespace!(State: bool {}); +//namespace!(State: u16 {}); +//namespace!(State: Color {}); + //handle!(TuiIn: |self: State, input|Action::from(input).eval(self).map(|_|None)); + //view!(State: Tui: [ evaluate_output_expression, evaluate_output_expression_tui ]); + //draw!(State: Tui: [ draw_example ]); diff --git a/shell.nix b/shell.nix index a4c3248..f34dc1f 100644 --- a/shell.nix +++ b/shell.nix @@ -2,8 +2,8 @@ {pkgs?import{}}:let stdenv = pkgs.clang19Stdenv; name = "tengri"; - nativeBuildInputs = [ pkgs.pkg-config pkgs.libclang pkgs.mold ]; - buildInputs = [ pkgs.libclang ]; + nativeBuildInputs = [ pkgs.pkg-config pkgs.clang pkgs.libclang pkgs.mold ]; + buildInputs = [ pkgs.libclang pkgs.jack2 ]; LIBCLANG_PATH = "${pkgs.libclang.lib.outPath}/lib"; LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath []; in pkgs.mkShell.override { diff --git a/src/color.rs b/src/color.rs index 13caf71..f16358a 100644 --- a/src/color.rs +++ b/src/color.rs @@ -11,6 +11,10 @@ pub fn rgb (r: u8, g: u8, b: u8) -> ItemColor { ItemColor { okhsl: rgb_to_okhsl(term), term } } +pub fn g (g: u8) -> Color { + Color::Rgb(g, g, g) +} + pub fn okhsl_to_rgb (color: Okhsl) -> Color { let Srgb { red, green, blue, .. }: Srgb = Srgb::from_color_unclamped(color); Color::Rgb((red * 255.0) as u8, (green * 255.0) as u8, (blue * 255.0) as u8,) diff --git a/src/draw.rs b/src/draw.rs index 9b8ebdb..ad47922 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -7,6 +7,10 @@ use crate::{*, lang::*, color::*, space::*}; /// /// Drawables are consumable, i.e. the [Draw::draw] method receives an /// owned `self` and does not return it, consuming the drawable. +/// +/// To draw a thing multiple times, instead of explicitly constructing it +/// every time, implement the [View] trait instead, which will construct +/// a [Draw]able. /// /// ``` /// use tengri::draw::*; @@ -26,17 +30,22 @@ use crate::{*, lang::*, color::*, space::*}; pub trait Draw { fn draw (self, to: &mut T) -> Usually>; } - -impl Draw for () { - fn draw (self, __: &mut T) -> Usually> { - Ok(Default::default()) +impl> Draw for &D { + fn draw (self, to: &mut T) -> Usually> { + todo!() + } +} +impl> Draw for Option { + fn draw (self, to: &mut T) -> Usually> { + todo!() } } -impl> Draw for Option { - fn draw (self, to: &mut T) -> Usually> { - Ok(self.map(|draw|draw.draw(to)).transpose()?.unwrap_or_default()) - } +/// Emit a [Draw]able. +/// +/// Speculative. How to avoid conflicts with [Draw] proper? +pub trait View { + fn view (&self) -> impl Draw; } pub const fn thunk Usually>> (draw: F) -> Thunk { @@ -48,7 +57,6 @@ pub struct ThunkUsually>>( pub F, std::marker::PhantomData ); - implUsually>> Draw for Thunk { fn draw (self, to: &mut T) -> Usually> { (self.0)(to) @@ -111,14 +119,3 @@ pub trait Screen: Space + Send + Sync + Sized { unimplemented!() } } - -/// Emit a [Draw]able. -pub trait View { - fn view (&self) -> impl Draw; -} - -impl> Draw for &V { - fn draw (self, to: &mut T) -> Usually> { - self.view().draw(to) - } -} diff --git a/src/exit.rs b/src/exit.rs index 2d07816..0b79708 100644 --- a/src/exit.rs +++ b/src/exit.rs @@ -8,3 +8,10 @@ impl Exit { run(Self(Arc::new(AtomicBool::new(false)))) } } + +impl AsRef> for Exit { + fn as_ref (&self) -> &Arc { + &self.0 + } +} + diff --git a/src/keys.rs b/src/keys.rs index 2d2037c..9cc54e1 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -2,13 +2,13 @@ use crate::task::Task; use ::std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}; use ::std::time::Duration; -use ::dizzle::{Usually, Do, impl_from}; +use ::dizzle::{Usually, Apply, impl_from}; use ::crossterm::event::{ read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState }; /// Spawn the TUI input thread which reads keys from the terminal. -pub fn tui_input > + Send + Sync + 'static> ( +pub fn tui_input > + Send + Sync + 'static> ( exited: &Arc, state: &Arc>, poll: Duration ) -> Result { let exited = exited.clone(); diff --git a/src/space.rs b/src/space.rs index b942f74..0bb159a 100644 --- a/src/space.rs +++ b/src/space.rs @@ -1,4 +1,5 @@ use crate::{*, draw::*}; +#[cfg(test)] use proptest_derive::Arbitrary; /// Point with size. /// @@ -423,22 +424,26 @@ pub const fn w_full (a: impl Draw) -> impl Draw { a } pub const fn h_full (a: impl Draw) -> impl Draw { a } #[macro_export] macro_rules! north { - ($($tt:tt)*) => { unimplemented!() }; + ($head:expr $(,)?) => { $head }; + ($head:expr, $($tail:expr),* $(,)?) => { north($head, north!($($tail,)*)) }; } #[macro_export] macro_rules! south { - ($($tt:tt)*) => { unimplemented!() }; + ($head:expr $(,)?) => { $head }; + ($head:expr, $($tail:expr),* $(,)?) => { south($head, south!($($tail,)*)) }; } #[macro_export] macro_rules! east { - ($($tt:tt)*) => { unimplemented!() }; + ($head:expr $(,)?) => { $head }; + ($head:expr, $($tail:expr),* $(,)?) => { east($head, east!($($tail,)*)) }; } #[macro_export] macro_rules! west { - ($($tt:tt)*) => { unimplemented!() }; + ($head:expr $(, $tail:expr)* $(,)?) => { west($head, west!($($tail,)*)) }; } #[macro_export] macro_rules! above { - ($($tt:tt)*) => { unimplemented!() }; + ($head:expr $(, $tail:expr)* $(,)?) => { above($head, above!($($tail,)*)) }; } #[macro_export] macro_rules! below { - ($($tt:tt)*) => { unimplemented!() }; + ($head:expr $(,)?) => { $head }; + ($head:expr, $($tail:expr),* $(,)?) => { below($head, below!($($tail,)*)) }; } /// Iterate over a collection of renderables: @@ -452,42 +457,56 @@ pub const fn h_full (a: impl Draw) -> impl Draw { a } /// ].iter(), |x|x); /// ``` pub fn iter < - T: Screen, + S: Screen, + D: Draw, V: Fn()->I, - I: Iterator>, - F: Fn(&dyn Draw)->dyn Draw, -> (_items: V, _cb: F) -> impl Draw { - thunk(move|_to: &mut T|{ todo!() }) + I: Iterator, + F: Fn(&D)->dyn Draw, +> (_items: V, _cb: F) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) } -pub fn iter_north < - T: Screen, - V: Fn()->I, - I: Iterator>, - F: Fn(&dyn Draw)->dyn Draw, -> (_items: V, _cb: F) -> impl Draw { - thunk(move|_to: &mut T|{ todo!() }) + +pub fn iter_north <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( + _iter: impl Fn()->I, _draw: impl Fn(D)->U, +) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) } -pub fn iter_east < - T: Screen, - V: Fn()->I, - I: Iterator>, - F: Fn(&dyn Draw)->dyn Draw, -> (_items: V, _cb: F) -> impl Draw { - thunk(move|_to: &mut T|{ todo!() }) + +pub fn iter_east <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( + _iter: impl Fn()->I, _draw: impl Fn(D)->U, +) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) } -pub fn iter_south < - T: Screen, - V: Fn()->I, - I: Iterator>, - F: Fn(&dyn Draw)->dyn Draw, -> (_items: V, _cb: F) -> impl Draw { - thunk(move|_to: &mut T|{ todo!() }) + +pub fn iter_south <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( + _iter: impl Fn()->I, _draw: impl Fn(D)->U, +) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) } -pub fn iter_west < - T: Screen, - V: Fn()->I, - I: Iterator>, - F: Fn(&dyn Draw)->dyn Draw, -> (_items: V, _cb: F) -> impl Draw { - thunk(move|_to: &mut T|{ todo!() }) + +pub fn iter_west <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( + _iter: impl Fn()->I, _draw: impl Fn(D)->U, +) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) +} + +#[derive(Default, Debug)] +pub struct Size(AtomicUsize, AtomicUsize); +impl X for Size { + fn x (&self) -> u16 { 0 } + fn w (&self) -> u16 { self.0.load(Relaxed) as u16 } +} +impl Y for Size { + fn y (&self) -> u16 { 0 } + fn h (&self) -> u16 { self.1.load(Relaxed) as u16 } +} +impl Size { + pub const fn of (&self, of: impl Draw) -> impl Draw { + thunk(move|to: &mut T|{ + let area = of.draw(to)?; + self.0.store(area.w().into(), Relaxed); + self.1.store(area.h().into(), Relaxed); + Ok(area) + }) + } } diff --git a/src/term.rs b/src/term.rs index 32081c4..ff8496d 100644 --- a/src/term.rs +++ b/src/term.rs @@ -21,6 +21,23 @@ use ::{ event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}, } }; +#[macro_export] macro_rules! tui_main { + ($state:expr) => { + pub fn main () -> Usually<()> { + tengri::exit::Exit::run(|exit|{ + let state = Arc::new(RwLock::new($state)); + Ok(( + ::tengri::keys::tui_input( + exit.as_ref(), &state, std::time::Duration::from_millis(100) + )?, + ::tengri::term::tui_output( + stdout(), exit.as_ref(), &state, std::time::Duration::from_millis(10) + )? + )) + }) + } + } +} pub struct Tui(pub Buffer, pub XYWH); impl Screen for Tui { type Unit = u16; } impl Deref for Tui { type Target = Buffer; fn deref (&self) -> &Buffer { &self.0 } } diff --git a/src/text.rs b/src/text.rs index 9fe1dd4..c9752a4 100644 --- a/src/text.rs +++ b/src/text.rs @@ -17,11 +17,6 @@ pub(crate) use ::unicode_width::*; self.as_str().draw(to) } } - impl Draw for std::sync::Arc { - fn draw (self, to: &mut Tui) -> Usually> { - self.as_ref().draw(to) - } - } impl Draw for &std::sync::Arc { fn draw (self, to: &mut Tui) -> Usually> { self.as_ref().draw(to)