Compare commits

...

4 commits

Author SHA1 Message Date
i do not exist
b0fb9f013d fix build environment
Some checks are pending
/ build (push) Waiting to run
2026-04-23 02:24:33 +03:00
i do not exist
c9b9ff1519 fix signatures of iter_ helpers 2026-04-17 04:07:14 +03:00
i do not exist
6c382e2627 re-add tui_main, fixing examples 2026-04-15 11:11:36 +03:00
i do not exist
a93fe92a59 fix Size, begin fixing View 2026-04-13 17:30:43 +03:00
13 changed files with 176 additions and 98 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use nix

5
.gitignore vendored
View file

@ -1,3 +1,4 @@
target
cov
*.profraw *.profraw
.direnv
cov
target

View file

@ -37,7 +37,7 @@ winit = { optional = true, version = "0.30.4", features = [ "x11" ]}
[dev-dependencies] [dev-dependencies]
proptest = { version = "^1" } proptest = { version = "^1" }
proptest-derive = { version = "^0.5.1" } proptest-derive = { version = "^0.5.1" }
tengri = { path = ".", features = [ "dsl" ] } tengri = { path = "." }
#tengri_proc = { path = "./proc" } #tengri_proc = { path = "./proc" }
[profile.coverage] [profile.coverage]

View file

@ -2,8 +2,9 @@ export LLVM_PROFILE_FILE := "cov/cargo-test-%p-%m.profraw"
grcov-binary := "--binary-path ./target/coverage/deps/" grcov-binary := "--binary-path ./target/coverage/deps/"
grcov-ignore := "--ignore-not-existing --ignore '../*' --ignore \"/*\" --ignore 'target/*'" grcov-ignore := "--ignore-not-existing --ignore '../*' --ignore \"/*\" --ignore 'target/*'"
[private]
default: default:
just -l @just -l
bacon: bacon:
bacon -s bacon -s
@ -29,6 +30,7 @@ doc:
cargo doc cargo doc
example-tui-00: example-tui-00:
cargo run -p tengri_tui --example tui_00 cargo run --example tui_00
example-tui-01: example-tui-01:
cargo run -p tengri_tui --example tui_01 cargo run --example tui_01

View file

@ -1,46 +1,74 @@
use ::{std::{io::stdout, sync::{Arc, RwLock}}, ratatui::style::Color, tengri::*}; use ::{
tui_main!(State { cursor: 10, ..Default::default() }); std::{io::stdout, sync::{Arc, RwLock}},
namespace!(State: bool {}); tengri::{*, term::*, lang::*, keys::*, draw::*, space::*},
namespace!(State: u16 {}); ratatui::style::Color,
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 ]); tui_main!(State { cursor: 10, ..Default::default() });
draw!(State: TuiOut: [ draw_example ]);
#[derive(Debug, Default)] struct State { impl Apply<TuiEvent, Usually<Self>> for State {
/** Rendered window size */ size: Measure<TuiOut>, fn apply (&mut self, input: &TuiEvent) -> Usually<Self> {
/** Command history (undo/redo) */ history: Vec<Action>, todo!()
/** User-controllable value */ cursor: usize, }
} }
impl_from!(Action: |input: &TuiIn| todo!());
#[derive(Debug)] enum Action { impl View<Tui> for State {
/** Increment cursor */ Next, fn view (&self) -> impl Draw<Tui> {
/** Decrement cursor */ Prev let index = self.cursor + 1;
} let wh = (self.size.w(), self.size.h());
fn draw_example (state: &State, to: &mut TuiOut) { let src = VIEWS.get(self.cursor).unwrap_or(&"");
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 heading = format!("State {}/{} in {:?}", index, VIEWS.len(), &wh);
let title = Tui::bg(Color::Rgb(60, 10, 10), Push::Y(1, Align::n(heading))); let title = 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 code = 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))); //let content = ;//();//bg(Color::Rgb(10, 10, 60), View(self, CstIter::new(src)));
state.size.of(Bsp::s(title, Bsp::n(code, state.understand(to, &src).unwrap()))).draw(to) self.size.of(bsp_s(title, bsp_n(code, self.understand(to, &src).unwrap())))
}
} }
#[derive(Debug, Default)] struct State {
/** Command history (undo/redo). */
history: Vec<Action>,
/** User-controllable value. */
cursor: usize,
/** Rendered window size. */
size: crate::space::Size,
}
//impl_from!(Action: |input: &TuiIn| todo!());
#[derive(Debug)] enum Action {
/** Increment cursor */
Next,
/** Decrement cursor */
Prev
}
fn draw_example (state: &State, to: &mut Tui) {
}
impl Action { 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<Self> { fn eval (&self, state: &mut State) -> Perhaps<Self> {
use Action::*; use Action::*;
match self { Next => Self::next(state), Prev => Self::prev(state), } match self { Next => Self::next(state), Prev => Self::prev(state), }
} }
fn next (state: &mut State) -> Perhaps<Self> { fn next (state: &mut State) -> Perhaps<Self> {
state.cursor = (state.cursor + 1) % VIEWS.len(); state.cursor = (state.cursor + 1) % VIEWS.len();
Ok(Some(Self::Prev)) Ok(Some(Self::Prev))
} }
fn prev (state: &mut State) -> Perhaps<Self> { fn prev (state: &mut State) -> Perhaps<Self> {
state.cursor = if state.cursor > 0 { state.cursor - 1 } else { VIEWS.len() - 1 }; state.cursor = if state.cursor > 0 { state.cursor - 1 } else { VIEWS.len() - 1 };
Ok(Some(Self::Next)) Ok(Some(Self::Next))
} }
} }
const VIEWS: &'static [&'static str] = &[ const VIEWS: &'static [&'static str] = &[
stringify! { :hello-world }, 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 ]);

View file

@ -2,8 +2,8 @@
{pkgs?import<nixpkgs>{}}:let {pkgs?import<nixpkgs>{}}:let
stdenv = pkgs.clang19Stdenv; stdenv = pkgs.clang19Stdenv;
name = "tengri"; name = "tengri";
nativeBuildInputs = [ pkgs.pkg-config pkgs.libclang pkgs.mold ]; nativeBuildInputs = [ pkgs.pkg-config pkgs.clang pkgs.libclang pkgs.mold ];
buildInputs = [ pkgs.libclang ]; buildInputs = [ pkgs.libclang pkgs.jack2 ];
LIBCLANG_PATH = "${pkgs.libclang.lib.outPath}/lib"; LIBCLANG_PATH = "${pkgs.libclang.lib.outPath}/lib";
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath []; LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [];
in pkgs.mkShell.override { in pkgs.mkShell.override {

View file

@ -11,6 +11,10 @@ pub fn rgb (r: u8, g: u8, b: u8) -> ItemColor {
ItemColor { okhsl: rgb_to_okhsl(term), term } 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<f32>) -> Color { pub fn okhsl_to_rgb (color: Okhsl<f32>) -> Color {
let Srgb { red, green, blue, .. }: Srgb<f32> = Srgb::from_color_unclamped(color); let Srgb { red, green, blue, .. }: Srgb<f32> = Srgb::from_color_unclamped(color);
Color::Rgb((red * 255.0) as u8, (green * 255.0) as u8, (blue * 255.0) as u8,) Color::Rgb((red * 255.0) as u8, (green * 255.0) as u8, (blue * 255.0) as u8,)

View file

@ -8,6 +8,10 @@ use crate::{*, lang::*, color::*, space::*};
/// Drawables are consumable, i.e. the [Draw::draw] method receives an /// Drawables are consumable, i.e. the [Draw::draw] method receives an
/// owned `self` and does not return it, consuming the drawable. /// 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::*; /// use tengri::draw::*;
/// struct TestScreen(bool); /// struct TestScreen(bool);
@ -26,17 +30,22 @@ use crate::{*, lang::*, color::*, space::*};
pub trait Draw<T: Screen> { pub trait Draw<T: Screen> {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>>; fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>>;
} }
impl<T: Screen, D: Draw<T>> Draw<T> for &D {
impl<T: Screen> Draw<T> for () { fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
fn draw (self, __: &mut T) -> Usually<XYWH<T::Unit>> { todo!()
Ok(Default::default()) }
}
impl<T: Screen, D: Draw<T>> Draw<T> for Option<D> {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
todo!()
} }
} }
impl<T: Screen, D: Draw<T>> Draw<T> for Option<D> { /// Emit a [Draw]able.
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> { ///
Ok(self.map(|draw|draw.draw(to)).transpose()?.unwrap_or_default()) /// Speculative. How to avoid conflicts with [Draw] proper?
} pub trait View<T: Screen> {
fn view (&self) -> impl Draw<T>;
} }
pub const fn thunk <T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> (draw: F) -> Thunk<T, F> { pub const fn thunk <T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> (draw: F) -> Thunk<T, F> {
@ -48,7 +57,6 @@ pub struct Thunk<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>>(
pub F, pub F,
std::marker::PhantomData<T> std::marker::PhantomData<T>
); );
impl<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> Draw<T> for Thunk<T, F> { impl<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> Draw<T> for Thunk<T, F> {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> { fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
(self.0)(to) (self.0)(to)
@ -111,14 +119,3 @@ pub trait Screen: Space<Self::Unit> + Send + Sync + Sized {
unimplemented!() unimplemented!()
} }
} }
/// Emit a [Draw]able.
pub trait View<T: Screen> {
fn view (&self) -> impl Draw<T>;
}
impl<T: Screen, V: View<T>> Draw<T> for &V {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
self.view().draw(to)
}
}

View file

@ -8,3 +8,10 @@ impl Exit {
run(Self(Arc::new(AtomicBool::new(false)))) run(Self(Arc::new(AtomicBool::new(false))))
} }
} }
impl AsRef<Arc<AtomicBool>> for Exit {
fn as_ref (&self) -> &Arc<AtomicBool> {
&self.0
}
}

View file

@ -2,13 +2,13 @@ use crate::task::Task;
use ::std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}; use ::std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}};
use ::std::time::Duration; use ::std::time::Duration;
use ::dizzle::{Usually, Do, impl_from}; use ::dizzle::{Usually, Apply, impl_from};
use ::crossterm::event::{ use ::crossterm::event::{
read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState
}; };
/// Spawn the TUI input thread which reads keys from the terminal. /// Spawn the TUI input thread which reads keys from the terminal.
pub fn tui_input <T: Do<TuiEvent, Usually<T>> + Send + Sync + 'static> ( pub fn tui_input <T: Apply<TuiEvent, Usually<T>> + Send + Sync + 'static> (
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
) -> Result<Task, std::io::Error> { ) -> Result<Task, std::io::Error> {
let exited = exited.clone(); let exited = exited.clone();

View file

@ -1,4 +1,5 @@
use crate::{*, draw::*}; use crate::{*, draw::*};
#[cfg(test)] use proptest_derive::Arbitrary;
/// Point with size. /// Point with size.
/// ///
@ -423,22 +424,26 @@ pub const fn w_full <T: Screen> (a: impl Draw<T>) -> impl Draw<T> { a }
pub const fn h_full <T: Screen> (a: impl Draw<T>) -> impl Draw<T> { a } pub const fn h_full <T: Screen> (a: impl Draw<T>) -> impl Draw<T> { a }
#[macro_export] macro_rules! north { #[macro_export] macro_rules! north {
($($tt:tt)*) => { unimplemented!() }; ($head:expr $(,)?) => { $head };
($head:expr, $($tail:expr),* $(,)?) => { north($head, north!($($tail,)*)) };
} }
#[macro_export] macro_rules! south { #[macro_export] macro_rules! south {
($($tt:tt)*) => { unimplemented!() }; ($head:expr $(,)?) => { $head };
($head:expr, $($tail:expr),* $(,)?) => { south($head, south!($($tail,)*)) };
} }
#[macro_export] macro_rules! east { #[macro_export] macro_rules! east {
($($tt:tt)*) => { unimplemented!() }; ($head:expr $(,)?) => { $head };
($head:expr, $($tail:expr),* $(,)?) => { east($head, east!($($tail,)*)) };
} }
#[macro_export] macro_rules! west { #[macro_export] macro_rules! west {
($($tt:tt)*) => { unimplemented!() }; ($head:expr $(, $tail:expr)* $(,)?) => { west($head, west!($($tail,)*)) };
} }
#[macro_export] macro_rules! above { #[macro_export] macro_rules! above {
($($tt:tt)*) => { unimplemented!() }; ($head:expr $(, $tail:expr)* $(,)?) => { above($head, above!($($tail,)*)) };
} }
#[macro_export] macro_rules! below { #[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: /// Iterate over a collection of renderables:
@ -452,42 +457,56 @@ pub const fn h_full <T: Screen> (a: impl Draw<T>) -> impl Draw<T> { a }
/// ].iter(), |x|x); /// ].iter(), |x|x);
/// ``` /// ```
pub fn iter < pub fn iter <
T: Screen, S: Screen,
D: Draw<S>,
V: Fn()->I, V: Fn()->I,
I: Iterator<Item = dyn Draw<T>>, I: Iterator<Item = D>,
F: Fn(&dyn Draw<T>)->dyn Draw<T>, F: Fn(&D)->dyn Draw<S>,
> (_items: V, _cb: F) -> impl Draw<T> { > (_items: V, _cb: F) -> impl Draw<S> {
thunk(move|_to: &mut T|{ todo!() }) thunk(move|_to: &mut S|{ todo!() })
} }
pub fn iter_north <
T: Screen, pub fn iter_north <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
V: Fn()->I, _iter: impl Fn()->I, _draw: impl Fn(D)->U,
I: Iterator<Item = dyn Draw<T>>, ) -> impl Draw<S> {
F: Fn(&dyn Draw<T>)->dyn Draw<T>, thunk(move|_to: &mut S|{ todo!() })
> (_items: V, _cb: F) -> impl Draw<T> {
thunk(move|_to: &mut T|{ todo!() })
} }
pub fn iter_east <
T: Screen, pub fn iter_east <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
V: Fn()->I, _iter: impl Fn()->I, _draw: impl Fn(D)->U,
I: Iterator<Item = dyn Draw<T>>, ) -> impl Draw<S> {
F: Fn(&dyn Draw<T>)->dyn Draw<T>, thunk(move|_to: &mut S|{ todo!() })
> (_items: V, _cb: F) -> impl Draw<T> {
thunk(move|_to: &mut T|{ todo!() })
} }
pub fn iter_south <
T: Screen, pub fn iter_south <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
V: Fn()->I, _iter: impl Fn()->I, _draw: impl Fn(D)->U,
I: Iterator<Item = dyn Draw<T>>, ) -> impl Draw<S> {
F: Fn(&dyn Draw<T>)->dyn Draw<T>, thunk(move|_to: &mut S|{ todo!() })
> (_items: V, _cb: F) -> impl Draw<T> {
thunk(move|_to: &mut T|{ todo!() })
} }
pub fn iter_west <
T: Screen, pub fn iter_west <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
V: Fn()->I, _iter: impl Fn()->I, _draw: impl Fn(D)->U,
I: Iterator<Item = dyn Draw<T>>, ) -> impl Draw<S> {
F: Fn(&dyn Draw<T>)->dyn Draw<T>, thunk(move|_to: &mut S|{ todo!() })
> (_items: V, _cb: F) -> impl Draw<T> { }
thunk(move|_to: &mut T|{ todo!() })
#[derive(Default, Debug)]
pub struct Size(AtomicUsize, AtomicUsize);
impl X<u16> for Size {
fn x (&self) -> u16 { 0 }
fn w (&self) -> u16 { self.0.load(Relaxed) as u16 }
}
impl Y<u16> for Size {
fn y (&self) -> u16 { 0 }
fn h (&self) -> u16 { self.1.load(Relaxed) as u16 }
}
impl Size {
pub const fn of <T: Screen> (&self, of: impl Draw<T>) -> impl Draw<T> {
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)
})
}
} }

View file

@ -21,6 +21,23 @@ use ::{
event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}, 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<u16>); pub struct Tui(pub Buffer, pub XYWH<u16>);
impl Screen for Tui { type Unit = u16; } impl Screen for Tui { type Unit = u16; }
impl Deref for Tui { type Target = Buffer; fn deref (&self) -> &Buffer { &self.0 } } impl Deref for Tui { type Target = Buffer; fn deref (&self) -> &Buffer { &self.0 } }

View file

@ -17,11 +17,6 @@ pub(crate) use ::unicode_width::*;
self.as_str().draw(to) self.as_str().draw(to)
} }
} }
impl Draw<Tui> for std::sync::Arc<str> {
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
self.as_ref().draw(to)
}
}
impl Draw<Tui> for &std::sync::Arc<str> { impl Draw<Tui> for &std::sync::Arc<str> {
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> { fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
self.as_ref().draw(to) self.as_ref().draw(to)