logical, temporal, spatial, terminal
Some checks are pending
/ build (push) Waiting to run

it's a song!
This commit is contained in:
same mf who else 2026-02-23 17:47:40 +02:00
parent 0d8503cc05
commit 5d0dc40fdc
11 changed files with 846 additions and 828 deletions

5
Cargo.lock generated
View file

@ -1164,8 +1164,9 @@ dependencies = [
[[package]] [[package]]
name = "tengri" name = "tengri"
version = "0.14.0" version = "0.15.0"
dependencies = [ dependencies = [
"anyhow",
"atomic_float", "atomic_float",
"better-panic", "better-panic",
"bumpalo", "bumpalo",
@ -1184,7 +1185,7 @@ dependencies = [
[[package]] [[package]]
name = "tengri_proc" name = "tengri_proc"
version = "0.14.0" version = "0.15.0"
dependencies = [ dependencies = [
"dizzle", "dizzle",
"heck", "heck",

View file

@ -18,14 +18,10 @@ atomic_float = { version = "1" }
better-panic = { version = "0.3.0" } better-panic = { version = "0.3.0" }
bumpalo = { version = "3.19.0", optional = true } bumpalo = { version = "3.19.0", optional = true }
crossterm = { version = "0.29.0" } crossterm = { version = "0.29.0" }
heck = { version = "0.5" }
palette = { version = "0.7.6", features = [ "random" ] } palette = { version = "0.7.6", features = [ "random" ] }
proc-macro2 = { version = "1", features = ["span-locations"] }
quanta = { version = "0.12.3" } quanta = { version = "0.12.3" }
quote = { version = "1" }
rand = { version = "0.8.5" } rand = { version = "0.8.5" }
ratatui = { version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] } ratatui = { version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] }
syn = { version = "2", features = ["full", "extra-traits"] }
unicode-width = { version = "0.2" } unicode-width = { version = "0.2" }
dizzle = { path = "../dizzle" } dizzle = { path = "../dizzle" }

View file

@ -1,5 +1,4 @@
use ::{std::sync::{Arc, RwLock}, ratatui::style::Color, use ::{std::{io::stdout, sync::{Arc, RwLock}}, ratatui::style::Color, tengri::*};
tengri::{*, input::*, output::*}, tengri_tui::*};
tui_main!(State { cursor: 10, ..Default::default() }); tui_main!(State { cursor: 10, ..Default::default() });
namespace!(State: bool {}); namespace!(State: bool {});
namespace!(State: u16 {}); namespace!(State: u16 {});
@ -25,7 +24,7 @@ fn draw_example (state: &State, to: &mut TuiOut) {
let title = Tui::bg(Color::Rgb(60, 10, 10), Push::Y(1, Align::n(heading))); 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 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))); //let content = ;//();//Tui::bg(Color::Rgb(10, 10, 60), View(state, CstIter::new(src)));
state.size.of(Bsp::s(title, Bsp::n(code, state.view(to, &src).unwrap()))).draw(to) state.size.of(Bsp::s(title, Bsp::n(code, state.understand(to, &src).unwrap()))).draw(to)
} }
impl Action { impl Action {
const BINDS: &'static str = stringify! { (@left prev) (@right next) }; const BINDS: &'static str = stringify! { (@left prev) (@right next) };

View file

@ -1,3 +1,4 @@
use ::{std::sync::{Arc, RwLock}, ratatui::style::Color, tengri::*};
fn main () {} fn main () {}
//#[tengri_proc::expose] //#[tengri_proc::expose]

View file

@ -1,18 +1,18 @@
[package] [package]
name = "tengri_proc" name = "tengri_proc"
description = "UI metaframework, procedural macros." description = "UI metaframework, procedural macros."
version = { workspace = true } version = "0.15.0"
edition = { workspace = true } edition = "2024"
[lib] [lib]
proc-macro = true proc-macro = true
[dependencies] [dependencies]
dizzle = { path = "../../dizzle" } dizzle = { path = "../../dizzle" }
syn = { workspace = true } quote = { version = "1" }
quote = { workspace = true } syn = { version = "2", features = ["full", "extra-traits"] }
proc-macro2 = { workspace = true } heck = { version = "0.5" }
heck = { workspace = true } proc-macro2 = { version = "1", features = ["span-locations"] }
[target.'cfg(target_os = "linux")'] [target.'cfg(target_os = "linux")']
rustflags = ["-C", "link-arg=-fuse-ld=mold"] rustflags = ["-C", "link-arg=-fuse-ld=mold"]

1
proc/README.md Normal file
View file

@ -0,0 +1 @@
currently unused.

View file

@ -4,18 +4,20 @@
#![feature(const_option_ops)] #![feature(const_option_ops)]
#![feature(const_precise_live_drops)] #![feature(const_precise_live_drops)]
#![feature(const_trait_impl)] #![feature(const_trait_impl)]
#![feature(if_let_guard)]
#![feature(impl_trait_in_assoc_type)] #![feature(impl_trait_in_assoc_type)]
#![feature(step_trait)] #![feature(step_trait)]
#![feature(trait_alias)] #![feature(trait_alias)]
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
#![feature(type_changing_struct_update)] #![feature(type_changing_struct_update)]
mod tengri_impl;
//pub(crate) use quanta::Clock; mod tengri_trait; pub use self::tengri_trait::*;
mod tengri_struct; pub use self::tengri_struct::*;
pub extern crate atomic_float; #[cfg(test)] pub(crate) use proptest_derive::Arbitrary;
pub(crate) use atomic_float::AtomicF64; pub extern crate dizzle; pub use dizzle::*;
pub extern crate atomic_float; pub(crate) use atomic_float::AtomicF64;
pub extern crate palette; pub(crate) use ::palette::{*, convert::*, okhsl::*};
pub extern crate better_panic; pub(crate) use better_panic::{Settings, Verbosity};
pub extern crate unicode_width; pub(crate) use unicode_width::*;
pub extern crate ratatui; pub(crate) use ::ratatui::{ pub extern crate ratatui; pub(crate) use ::ratatui::{
prelude::{Color, Style, Buffer, Position}, prelude::{Color, Style, Buffer, Position},
style::{Stylize, Modifier, Color::*}, style::{Stylize, Modifier, Color::*},
@ -23,39 +25,19 @@ pub extern crate ratatui; pub(crate) use ::ratatui::{
layout::{Size, Rect}, layout::{Size, Rect},
buffer::Cell buffer::Cell
}; };
pub extern crate crossterm; pub(crate) use ::crossterm::{
pub extern crate crossterm;
pub(crate) use ::crossterm::{
ExecutableCommand, ExecutableCommand,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}, terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}, event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
}; };
pub extern crate palette;
pub(crate) use ::palette::{*, convert::*, okhsl::*};
pub extern crate better_panic;
pub(crate) use better_panic::{Settings, Verbosity};
pub extern crate unicode_width;
pub(crate) use unicode_width::*;
//#[cfg(test)] extern crate tengri_proc;
mod tengri_impl;
mod tengri_type; pub use self::tengri_type::*;
mod tengri_trait; pub use self::tengri_trait::*;
mod tengri_struct; pub use self::tengri_struct::*;
#[macro_export] pub extern crate dizzle;
pub use dizzle::*;
use std::{time::Duration, thread::{spawn, JoinHandle}, io::Write};
pub(crate) use ::std::{ pub(crate) use ::std::{
io::{stdout, Stdout}, io::{stdout, Stdout, Write},
sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}}, sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}},
fmt::{Debug, Display}, fmt::{Debug, Display},
ops::{Add, Sub, Mul, Div}, ops::{Add, Sub, Mul, Div},
marker::PhantomData, marker::PhantomData,
time::Duration,
thread::{spawn, JoinHandle}
}; };
// Define macros first, so that private macros are available in private modules: // Define macros first, so that private macros are available in private modules:
@ -92,16 +74,24 @@ pub(crate) use ::std::{
} }
/// Stack things on top of each other, /// Stack things on top of each other,
#[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }}); #[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) => {{
let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp
}});
/// Stack southward. /// Stack southward.
#[macro_export] macro_rules! col (($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }}); #[macro_export] macro_rules! col (($($expr:expr),* $(,)?) => {{
let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp
}});
/// Stack northward. /// Stack northward.
#[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }}); #[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) => {{
let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp
}});
/// Stack eastward. /// Stack eastward.
#[macro_export] macro_rules! row (($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }}); #[macro_export] macro_rules! row (($($expr:expr),* $(,)?) => {{
let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp
}});
/// Implement [Command] for given `State` and `handler` /// Implement [Command] for given `State` and `handler`
#[macro_export] macro_rules! command { #[macro_export] macro_rules! command {
@ -154,7 +144,9 @@ pub(crate) use ::std::{
#[macro_export] macro_rules! tui_main { #[macro_export] macro_rules! tui_main {
($expr:expr) => { ($expr:expr) => {
fn main () -> Usually<()> { fn main () -> Usually<()> {
tengri::Tui::new(stdout()).run(true, $expr)?; let engine = ::tengri::Tui::new(Box::new(stdout()))?;
let state = ::std::sync::Arc::new(std::sync::RwLock::new($expr));
engine.run(true, &state)?;
Ok(()) Ok(())
} }
}; };
@ -328,20 +320,28 @@ pub fn tui_input <T: Handle<TuiIn> + Send + Sync + 'static> (
/// Define layout operation. /// Define layout operation.
/// ///
/// ``` /// ```
/// struct Target; /// # use tengri::*;
/// impl tengri::Output for Target { type Unit = u16; } /// struct Target { xywh: XYWH<u16> /*platform-specific*/}
/// impl tengri::Out for Target {
/// type Unit = u16;
/// fn area (&self) -> XYWH<u16> { self.xywh }
/// fn area_mut (&mut self) -> &mut XYWH<u16> { &mut self.xywh }
/// fn place_at <'t, T: Draw<Self> + ?Sized> (&mut self, area: XYWH<u16>, content: &'t T) {}
/// }
/// ///
/// struct State; /// struct State {/*app-specific*/}
/// impl<'b> Namespace<'b, bool> for State {} /// impl<'b> Namespace<'b, bool> for State {}
/// impl<'b> Namespace<'b, u16> for State {} /// impl<'b> Namespace<'b, u16> for State {}
/// impl Understand<Target, ()> for State {} /// impl Understand<Target, ()> for State {}
/// ///
/// let state = State; /// # fn main () -> tengri::Usually<()> {
/// let target = Target::default(); /// let state = State {};
/// tengri::evaluate_output_expression(&state, &mut out, "")?; /// let mut target = Target { xywh: Default::default() };
/// tengri::evaluate_output_expression(&state, &mut out, "(when true (text hello))")?; /// evaluate_output_expression(&state, &mut target, &"")?;
/// tengri::evaluate_output_expression(&state, &mut out, "(either true (text hello) (text world)"))?; /// evaluate_output_expression(&state, &mut target, &"(when true (text hello))")?;
/// evaluate_output_expression(&state, &mut target, &"(either true (text hello) (text world))")?;
/// // TODO test all /// // TODO test all
/// # Ok(()) }
/// ``` /// ```
#[cfg(feature = "dsl")] pub fn evaluate_output_expression <'a, O: Out + 'a, S> ( #[cfg(feature = "dsl")] pub fn evaluate_output_expression <'a, O: Out + 'a, S> (
state: &S, output: &mut O, expr: &'a impl Expression state: &S, output: &mut O, expr: &'a impl Expression
@ -473,18 +473,22 @@ pub fn tui_input <T: Handle<TuiIn> + Send + Sync + 'static> (
/// Should be impl something or other... /// Should be impl something or other...
/// ///
/// ``` /// ```
/// use tengri::{Namespace, Understand, TuiOut, ratatui::prelude::Color};
///
/// struct State; /// struct State;
/// impl<'b> Namespace<'b, bool> for State {} /// impl<'b> Namespace<'b, bool> for State {}
/// impl<'b> Namespace<'b, u16> for State {} /// impl<'b> Namespace<'b, u16> for State {}
/// impl<'b> Namespace<'b, Color> for State {} /// impl<'b> Namespace<'b, Color> for State {}
/// impl Understand<TuiOut, ()> for State {} /// impl Understand<TuiOut, ()> for State {}
/// # fn main () -> tengri::Usually<()> {
/// let state = State; /// let state = State;
/// let out = TuiOut::default(); /// let mut out = TuiOut::default();
/// tengri::evaluate_output_expression_tui(&state, &mut out, "")?; /// tengri::evaluate_output_expression_tui(&state, &mut out, "")?;
/// tengri::evaluate_output_expression_tui(&state, &mut out, "text Hello world!")?; /// tengri::evaluate_output_expression_tui(&state, &mut out, "text Hello world!")?;
/// tengri::evaluate_output_expression_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?; /// tengri::evaluate_output_expression_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?;
/// tengri::evaluate_output_expression_tui(&state, &mut out, "bg (g 2) (text Hello world!)")?; /// tengri::evaluate_output_expression_tui(&state, &mut out, "bg (g 2) (text Hello world!)")?;
/// tengri::evaluate_output_expression_tui(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?; /// tengri::evaluate_output_expression_tui(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?;
/// # Ok(()) }
/// ``` /// ```
#[cfg(feature = "dsl")] pub fn evaluate_output_expression_tui <'a, S> ( #[cfg(feature = "dsl")] pub fn evaluate_output_expression_tui <'a, S> (
state: &S, output: &mut TuiOut, expr: impl Expression + 'a state: &S, output: &mut TuiOut, expr: impl Expression + 'a
@ -577,8 +581,8 @@ pub fn named_key (token: &str) -> Option<KeyCode> {
} }
/// ``` /// ```
/// let _ = button_2("", "", true); /// let _ = tengri::button_2("", "", true);
/// let _ = button_2("", "", false); /// let _ = tengri::button_2("", "", false);
/// ``` /// ```
pub fn button_2 <'a> (key: impl Content<TuiOut>, label: impl Content<TuiOut>, editing: bool) -> impl Content<TuiOut> { pub fn button_2 <'a> (key: impl Content<TuiOut>, label: impl Content<TuiOut>, editing: bool) -> impl Content<TuiOut> {
Tui::bold(true, Bsp::e( Tui::bold(true, Bsp::e(
@ -587,8 +591,8 @@ pub fn button_2 <'a> (key: impl Content<TuiOut>, label: impl Content<TuiOut>, ed
} }
/// ``` /// ```
/// let _ = button_3("", "", "", true); /// let _ = tengri::button_3("", "", "", true);
/// let _ = button_3("", "", "", false); /// let _ = tengri::button_3("", "", "", false);
/// ``` /// ```
pub fn button_3 <'a> ( pub fn button_3 <'a> (
key: impl Content<TuiOut>, label: impl Content<TuiOut>, value: impl Content<TuiOut>, editing: bool, key: impl Content<TuiOut>, label: impl Content<TuiOut>, value: impl Content<TuiOut>, editing: bool,
@ -776,6 +780,7 @@ pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
use proptest::{prelude::*, option::of}; use proptest::{prelude::*, option::of};
use proptest_derive::Arbitrary; use proptest_derive::Arbitrary;
use crate::*; use crate::*;
use Direction::*;
proptest! { proptest! {
#[test] fn proptest_direction ( #[test] fn proptest_direction (
@ -902,22 +907,18 @@ pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
} }
} }
#[test] fn test_tui_engine () -> Usually<()> { //#[test] fn test_tui_engine () -> Usually<()> {
//use std::sync::{Arc, RwLock}; ////use std::sync::{Arc, RwLock};
struct TestComponent(String); //struct TestComponent(String);
impl Draw<TuiOut> for TestComponent { //impl Draw<TuiOut> for TestComponent {
fn draw (&self, _to: &mut TuiOut) { //fn draw (&self, _to: &mut TuiOut) {}
} //}
} //impl Handle<TuiIn> for TestComponent {
impl Handle<TuiIn> for TestComponent { //fn handle (&mut self, _from: &TuiIn) -> Perhaps<bool> { Ok(None) }
fn handle (&mut self, _from: &TuiIn) -> Perhaps<bool> { //}
Ok(None) //let engine = Tui::new(Box::<&mut Vec<u8>>::new(vec![0u8;0].as_mut()))?;
} //let state = engine.run(false, &Arc::new(RwLock::new(TestComponent("hello world".into()))))?;
} //state.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed);
let mut output = String::new(); //Ok(())
let engine = Tui::new(&mut output).run(false, TestComponent("hello world".into()))?; //}
engine.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed);
//engine.run(&state)?;
Ok(())
}
} }

View file

@ -270,15 +270,11 @@ impl<O: Out, D: Draw<O>> Draw<O> for Option<D> {
//} //}
impl<O: Out, T: Content<O>, F: Fn()->T> Lazy<O, T, F> { impl<O: Out, T: Content<O>, F: Fn()->T> Lazy<O, T, F> {
pub const fn new (thunk: F) -> Self { pub const fn new (thunk: F) -> Self { Self(thunk, PhantomData) }
Self(thunk, PhantomData)
}
} }
impl<O: Out, F: Fn(&mut O)> Thunk<O, F> { impl<O: Out, F: Fn(&mut O)> Thunk<O, F> {
pub const fn new (draw: F) -> Self { pub const fn new (draw: F) -> Self { Self(PhantomData, draw) }
Self(PhantomData, draw)
}
} }
impl<O: Out, F: Fn(&mut O)> Layout<O> for Thunk<O, F> {} impl<O: Out, F: Fn(&mut O)> Layout<O> for Thunk<O, F> {}
@ -672,57 +668,15 @@ impl<Head, Tail> Bsp<Head, Tail> {
impl<O: Out, Head: Content<O>, Tail: Content<O>> Draw<O> for Bsp<Head, Tail> { impl<O: Out, Head: Content<O>, Tail: Content<O>> Draw<O> for Bsp<Head, Tail> {
fn draw (&self, to: &mut O) { fn draw (&self, to: &mut O) {
match self.0 { let [a, b, _] = bsp_areas(to.area(), self.0, &self.1, &self.2);
North => { // FIXME
let area_1 = self.1.layout(to.area());
let area_2 = self.2.layout(XYWH(
to.area().x(), to.area().y().plus(area_1.h()),
to.area().w(), to.area().h().minus(area_1.h())
));
to.place_at(area_1, &self.1);
to.place_at(area_2, &self.2);
},
South => {
let area_1 = self.1.layout(to.area());
let area_2 = self.2.layout(XYWH(
to.area().x(), to.area().y().plus(area_1.h()),
to.area().w(), to.area().h().minus(area_1.h())
));
to.place_at(area_1, &self.1);
to.place_at(area_2, &self.2);
},
East => {
let area_1 = self.1.layout(to.area());
let area_2 = self.2.layout(XYWH(
to.area().x().plus(area_1.w()), to.area().y(),
to.area().w().minus(area_1.w()), to.area().h()
));
to.place_at(area_1, &self.1);
to.place_at(area_2, &self.2);
},
Above => {
let area_1 = self.1.layout(to.area());
let area_2 = self.2.layout(to.area());
to.place_at(area_2, &self.2);
to.place_at(area_1, &self.1);
},
Below => {
let area_1 = self.1.layout(to.area());
let area_2 = self.2.layout(to.area());
to.place_at(area_1, &self.1);
to.place_at(area_2, &self.2);
},
_ => todo!("{:?}", self.0)
}
//let [a, b, _] = bsp_areas(to.area(), self.0, &self.1, &self.2);
//panic!("{a:?} {b:?}"); //panic!("{a:?} {b:?}");
//if self.0 == Below { if self.0 == Below {
//to.place_at(a, &self.1); to.place_at(a, &self.1);
//to.place_at(b, &self.2); to.place_at(b, &self.2);
//} else { } else {
//to.place_at(b, &self.2); to.place_at(b, &self.2);
//to.place_at(a, &self.1); to.place_at(a, &self.1);
//} }
} }
} }
@ -1011,7 +965,7 @@ impl PerfModel {
} }
impl Tui { impl Tui {
pub fn new (output: Stdout) -> Usually<Self> { pub fn new (output: Box<dyn Write + Send + Sync>) -> Usually<Self> {
let backend = CrosstermBackend::new(output); let backend = CrosstermBackend::new(output);
let Size { width, height } = backend.size()?; let Size { width, height } = backend.size()?;
Ok(Self { Ok(Self {
@ -1061,6 +1015,8 @@ impl Input for TuiIn {
type Event = TuiEvent; type Event = TuiEvent;
type Handled = bool; type Handled = bool;
fn event (&self) -> &TuiEvent { &self.event } fn event (&self) -> &TuiEvent { &self.event }
}
impl Done for TuiIn {
fn done (&self) { self.exited.store(true, Relaxed); } fn done (&self) { self.exited.store(true, Relaxed); }
fn is_done (&self) -> bool { self.exited.fetch_and(true, Relaxed) } fn is_done (&self) -> bool { self.exited.fetch_and(true, Relaxed) }
} }
@ -1076,6 +1032,11 @@ impl TuiEvent {
Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self)) Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self))
} }
} }
impl From<Event> for TuiEvent {
fn from (e: Event) -> Self {
Self(e)
}
}
impl From<char> for TuiEvent { impl From<char> for TuiEvent {
fn from (c: char) -> Self { fn from (c: char) -> Self {
Self(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE))) Self(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE)))

View file

@ -1,55 +1,282 @@
#[cfg(test)] use proptest_derive::Arbitrary; pub use self::logical::*; mod logical {
use crate::*; use crate::*;
/// The `Tui` struct (the *engine*) implements the /// Thunks can be natural error boundaries!
/// `tengri_input::Input` and `tengri_output::Out` traits. pub struct ErrorBoundary<O: Out, T: Draw<O>>(
/// At launch, the `Tui` engine spawns two threads, the render thread and the input thread. pub std::marker::PhantomData<O>,
/// the application may further spawn other threads. All threads communicate using shared ownership: pub Perhaps<T>
/// `Arc<RwLock<T>>` and `Arc<AtomicT>`. Thus, at launch the engine and application instances are expected to be wrapped in `Arc<RwLock>`. );
pub struct Tui {
pub exited: Arc<AtomicBool>, // TODO DOCUMENTME
pub backend: CrosstermBackend<Stdout>, pub struct Lazy<O, T, F>(
pub buffer: Buffer, pub F,
pub area: [u16;4], pub PhantomData<(O, T)>
pub perf: PerfModel, );
// TODO DOCUMENTME
pub struct Thunk<O: Out, F: Fn(&mut O)>(
pub PhantomData<O>,
pub F
);
// TODO DOCUMENTME
#[derive(Debug, Default)] pub struct Memo<T, U> {
pub value: T,
pub view: Arc<RwLock<U>>
}
} }
pub use self::temporal::*; mod temporal {
use crate::*;
#[derive(Debug, Clone)] pub struct TuiIn { /// Performance counter
/// Input event #[derive(Debug)]
pub event: TuiEvent, pub struct PerfModel {
/// Exit flag pub enabled: bool,
pub exited: Arc<AtomicBool>,
pub clock: quanta::Clock,
// In nanoseconds. Time used by last iteration.
pub used: AtomicF64,
// In microseconds. Max prescribed time for iteration (frame, chunk...).
pub window: AtomicF64,
}
} }
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent( pub use self::spatial::*; mod spatial {
pub Event use crate::*;
);
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiKey( // TODO DOCUMENTME
pub Option<KeyCode>, pub struct Bordered<S, W>(pub bool, pub S, pub W);
pub KeyModifiers
);
#[derive(Default)] pub struct TuiOut { // TODO DOCUMENTME
pub buffer: Buffer, pub struct Border<S>(pub bool, pub S);
pub area: XYWH<u16>,
}
/// TUI buffer sized by `usize` instead of `u16`. /// A binary split or layer.
#[derive(Default)] pub struct BigBuffer { pub struct Bsp<Head, Tail>(
pub width: usize, /// Direction of split
pub height: usize, pub(crate) Direction,
pub content: Vec<Cell> /// First element.
} pub(crate) Head,
/// Second element.
pub(crate) Tail,
);
/// A color in OKHSL and RGB representations. /// A point (X, Y).
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor { ///
/// ```
/// let xy = tengri::XY(0u16, 0);
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XY<C: Coord>(
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<C: Coord>(
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<C: Coord>(
pub C, pub C, pub C, pub C
);
/// A cardinal direction.
///
/// ```
/// let direction = tengri::Direction::Above;
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, PartialEq, Debug)] pub enum Direction {
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 widget that tracks its rendered width and height.
///
/// ```
/// let measure = tengri::Measure::<tengri::TuiOut>::default();
/// ```
#[derive(Default)] pub struct Measure<O: Out> {
pub __: PhantomData<O>,
pub x: Arc<AtomicUsize>,
pub y: Arc<AtomicUsize>,
}
/// Show an item only when a condition is true.
///
/// ```
/// fn test () -> impl tengri::Draw<tengri::TuiOut> {
/// tengri::when(true, "Yes")
/// }
/// ```
pub struct When<O, T>(pub bool, pub T, pub PhantomData<O>);
pub const fn when<O, T>(condition: bool, content: T) -> When<O, T> {
When(condition, content, PhantomData)
}
/// Show one item if a condition is true and another if the condition is false.
///
/// ```
/// fn test () -> impl tengri::Draw<tengri::TuiOut> {
/// tengri::either(true, "Yes", "No")
/// }
/// ```
pub struct Either<E, A, B>(pub bool, pub A, pub B, pub PhantomData<E>);
pub const fn either<E, A, B>(condition: bool, content_a: A, content_b: B) -> Either<E, A, B> {
Either(condition, content_a, content_b, PhantomData)
}
/// Increment X and/or Y coordinate.
///
/// ```
/// let pushed = tengri::Push::XY(2, 2, "Hello");
/// ```
pub enum Push<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Decrement X and/or Y coordinate.
///
/// ```
/// let pulled = tengri::Pull::XY(2, 2, "Hello");
/// ```
pub enum Pull<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Set the content to fill the container.
///
/// ```
/// let filled = tengri::Fill::XY("Hello");
/// ```
pub enum Fill<A> { X(A), Y(A), XY(A) }
/// Set fixed size for content.
///
/// ```
/// let fixed = tengri::Fixed::XY(3, 5, "Hello"); // 3x5
/// ```
pub enum Fixed<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Set the maximum width and/or height of the content.
///
/// ```
/// let maximum = tengri::Min::XY(3, 5, "Hello"); // 3x1
/// ```
pub enum Max<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Set the minimum width and/or height of the content.
///
/// ```
/// let minimam = tengri::Min::XY(3, 5, "Hello"); // 5x5
/// ```
pub enum Min<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Decrease the width and/or height of the content.
///
/// ```
/// let shrunk = tengri::Shrink::XY(2, 0, "Hello"); // 1x1
/// ```
pub enum Shrink<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Increaase the width and/or height of the content.
///
/// ```
/// let expanded = tengri::Expand::XY(5, 3, "HELLO"); // 15x3
/// ```
pub enum Expand<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Align position of inner area to middle, side, or corner of outer area.
///
///
/// ```
/// use ::tengri::*;
/// let area = XYWH(10u16, 10, 20, 20);
/// fn test (area: XYWH<u16>, item: &impl Draw<TuiOut>, expected: [u16;4]) {
/// //assert_eq!(Lay::layout(item, area), expected);
/// //assert_eq!(Draw::layout(item, area), expected);
/// };
///
/// let four = ||Fixed::XY(4, 4, "");
/// test(area, &Align::nw(four()), [10, 10, 4, 4]);
/// test(area, &Align::n(four()), [18, 10, 4, 4]);
/// test(area, &Align::ne(four()), [26, 10, 4, 4]);
/// test(area, &Align::e(four()), [26, 18, 4, 4]);
/// test(area, &Align::se(four()), [26, 26, 4, 4]);
/// test(area, &Align::s(four()), [18, 26, 4, 4]);
/// test(area, &Align::sw(four()), [10, 26, 4, 4]);
/// test(area, &Align::w(four()), [10, 18, 4, 4]);
///
/// let two_by_four = ||Fixed::XY(4, 2, "");
/// test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]);
/// test(area, &Align::n(two_by_four()), [18, 10, 4, 2]);
/// test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]);
/// test(area, &Align::e(two_by_four()), [26, 19, 4, 2]);
/// test(area, &Align::se(two_by_four()), [26, 28, 4, 2]);
/// test(area, &Align::s(two_by_four()), [18, 28, 4, 2]);
/// test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]);
/// test(area, &Align::w(two_by_four()), [10, 19, 4, 2]);
/// ```
pub struct Align<T>(pub Alignment, pub T);
// TODO DOCUMENTME
pub enum Pad<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// TODO DOCUMENTME
///
/// ```
/// use tengri::{Bounded, XYWH};
/// let area = XYWH(0, 0, 0, 0);
/// let content = "";
/// let bounded: Bounded<tengri::TuiOut, _> = Bounded(area, content);
/// ```
pub struct Bounded<O: Out, D>(pub XYWH<O::Unit>, pub D);
/// Draws items from an iterator.
///
/// ```
/// // FIXME let map = tengri::Map(||[].iter(), |_|{});
/// ```
pub struct Map<O, A, B, I, F, G>
where
I: Iterator<Item = A> + Send + Sync,
F: Fn() -> I + Send + Sync,
{
/// Function that returns iterator over stacked components
pub get_iter: F,
/// Function that returns each stacked component
pub get_item: G,
pub __: PhantomData<(O, B)>,
}
/// A color in OKHSL and RGB representations.
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor {
pub okhsl: Okhsl<f32>, pub okhsl: Okhsl<f32>,
pub rgb: Color, pub rgb: Color,
} }
/// A color in OKHSL and RGB with lighter and darker variants. /// A color in OKHSL and RGB with lighter and darker variants.
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemTheme { #[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemTheme {
pub base: ItemColor, pub base: ItemColor,
pub light: ItemColor, pub light: ItemColor,
pub lighter: ItemColor, pub lighter: ItemColor,
@ -57,295 +284,106 @@ pub struct Tui {
pub dark: ItemColor, pub dark: ItemColor,
pub darker: ItemColor, pub darker: ItemColor,
pub darkest: ItemColor, pub darkest: ItemColor,
}
} }
pub struct Modify<T>(pub bool, pub Modifier, pub T); pub use self::terminal::*; mod terminal {
use crate::*;
pub struct Styled<T>(pub Option<Style>, pub T); /// The TUI engine.
///
/// ```
/// # fn main () -> tengri::Usually<()> {
/// let tui = tengri::Tui::new(Box::new(vec![0u8;0]))?;
/// # Ok(()) }
/// ```
pub struct Tui {
pub exited: Arc<AtomicBool>,
pub backend: CrosstermBackend<Box<dyn Write + Send + Sync>>,
pub buffer: Buffer,
pub area: [u16;4],
pub perf: PerfModel,
}
/// Displays an owned [str]-like with fixed maximum width. /// The TUI input event source.
/// ///
/// Width is computed using [unicode_width]. /// ```
pub struct TrimString<T: AsRef<str>>(pub u16, pub T); /// use ::{tengri::{TuiIn, TuiEvent}, std::sync::Arc};
/// let tui_in = TuiIn { event: 'f'.into(), exited: Arc::new(false.into()) };
/// ```
#[derive(Debug, Clone)] pub struct TuiIn {
/// Input event
pub event: TuiEvent,
/// Exit flag
pub exited: Arc<AtomicBool>,
}
/// Displays a borrowed [str]-like with fixed maximum width #[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent(
/// pub Event
/// Width is computed using [unicode_width]. );
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
/// Thunks can be natural error boundaries! #[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiKey(
pub struct ErrorBoundary<O: Out, T: Draw<O>>( pub Option<KeyCode>,
pub std::marker::PhantomData<O>, pub KeyModifiers
pub Perhaps<T> );
);
/// The TUI output render target.
///
/// ```
/// let tui_out = tengri::TuiOut::default();
/// ```
#[derive(Default)] pub struct TuiOut {
pub buffer: Buffer,
pub area: XYWH<u16>,
}
/// A point (X, Y). /// TUI buffer sized by `usize` instead of `u16`.
/// #[derive(Default)] pub struct BigBuffer {
/// ``` pub width: usize,
/// let xy = tengri::XY(0u16, 0); pub height: usize,
/// ``` pub content: Vec<Cell>
#[cfg_attr(test, derive(Arbitrary))] }
#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XY<C: Coord>(
pub C, pub C
);
/// A size (Width, Height). // TODO DOCUMENTME
/// pub struct Foreground<Color, Item>(pub Color, pub Item);
/// ```
/// let wh = tengri::WH(0u16, 0);
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct WH<C: Coord>(
pub C, pub C
);
/// Point with size. // TODO DOCUMENTME
/// pub struct Background<Color, Item>(pub Color, pub Item);
/// ```
/// let xywh = tengri::XYWH(0u16, 0, 0, 0);
/// assert_eq!(XYWH(10u16, 10, 20, 20).center(), 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<C: Coord>(
pub C, pub C, pub C, pub C
);
/// A cardinal direction. pub struct Modify<T>(pub bool, pub Modifier, pub T);
///
/// ```
/// let direction = tengri::Direction::Above;
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, PartialEq, Debug)] pub enum Direction {
North, South, East, West, Above, Below
}
/// 9th of area to place. pub struct Styled<T>(pub Option<Style>, pub T);
///
/// ```
/// 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 widget that tracks its rendered width and height. /// Displays an owned [str]-like with fixed maximum width.
/// ///
/// ``` /// Width is computed using [unicode_width].
/// let measure = tengri::Measure::<tengri::tui::TuiOut>::default(); pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
/// ```
#[derive(Default)] pub struct Measure<O: Out> {
pub __: PhantomData<O>,
pub x: Arc<AtomicUsize>,
pub y: Arc<AtomicUsize>,
}
/// Show an item only when a condition is true. /// Displays a borrowed [str]-like with fixed maximum width
/// ///
/// ``` /// Width is computed using [unicode_width].
/// fn test () -> impl tengri::Draw<tengri::tui::TuiOut> { pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
/// tengri::when(true, "Yes")
/// }
/// ```
pub struct When<O, T>(pub bool, pub T, pub PhantomData<O>);
pub const fn when<O, T>(condition: bool, content: T) -> When<O, T> {
When(condition, content, PhantomData)
}
/// Show one item if a condition is true and another if the condition is false. /// A cell that takes up 3 rows on its own,
/// /// but stacks, giving (N+1)*2 rows per N cells.
/// ``` pub struct Phat<T> {
/// fn test () -> impl tengri::Draw<tengri::tui::TuiOut> { pub width: u16,
/// tengri::either(true, "Yes", "No") pub height: u16,
/// } pub content: T,
/// ``` pub colors: [Color;4],
pub struct Either<E, A, B>(pub bool, pub A, pub B, pub PhantomData<E>); }
pub const fn either<E, A, B>(condition: bool, content_a: A, content_b: B) -> Either<E, A, B> {
Either(condition, content_a, content_b, PhantomData)
}
/// Increment X and/or Y coordinate. /// A three-column layout.
/// pub struct Tryptich<A, B, C> {
/// ```
/// let pushed = tengri::Push::XY(2, 2, "Hello");
/// ```
pub enum Push<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Decrement X and/or Y coordinate.
///
/// ```
/// let pulled = tengri::Pull::XY(2, 2, "Hello");
/// ```
pub enum Pull<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Set the content to fill the container.
///
/// ```
/// let filled = tengri::Fill::XY("Hello");
/// ```
pub enum Fill<A> { X(A), Y(A), XY(A) }
/// Set fixed size for content.
///
/// ```
/// let fixed = tengri::Fixed::XY(3, 5, "Hello"); // 3x5
/// ```
pub enum Fixed<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Set the maximum width and/or height of the content.
///
/// ```
/// let maximum = tengri::Min::XY(3, 5, "Hello"); // 3x1
/// ```
pub enum Max<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Set the minimum width and/or height of the content.
///
/// ```
/// let minimam = tengri::Min::XY(3, 5, "Hello"); // 5x5
/// ```
pub enum Min<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Decrease the width and/or height of the content.
///
/// ```
/// let shrunk = tengri::Shrink::XY(2, 0, "Hello"); // 1x1
/// ```
pub enum Shrink<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Increaase the width and/or height of the content.
///
/// ```
/// let expanded = tengri::Expand::XY(5, 3, "HELLO"); // 15x3
/// ```
pub enum Expand<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Align position of inner area to middle, side, or corner of outer area.
///
///
/// ```
/// use ::tengri::{output::*, tui::*};
/// let area = XYWH(10u16, 10, 20, 20);
/// fn test (area: XYWH<u16>, item: &impl Draw<TuiOut>, expected: [u16;4]) {
/// //assert_eq!(Lay::layout(item, area), expected);
/// //assert_eq!(Draw::layout(item, area), expected);
/// };
///
/// let four = ||Fixed::XY(4, 4, "");
/// test(area, &Align::nw(four()), [10, 10, 4, 4]);
/// test(area, &Align::n(four()), [18, 10, 4, 4]);
/// test(area, &Align::ne(four()), [26, 10, 4, 4]);
/// test(area, &Align::e(four()), [26, 18, 4, 4]);
/// test(area, &Align::se(four()), [26, 26, 4, 4]);
/// test(area, &Align::s(four()), [18, 26, 4, 4]);
/// test(area, &Align::sw(four()), [10, 26, 4, 4]);
/// test(area, &Align::w(four()), [10, 18, 4, 4]);
///
/// let two_by_four = ||Fixed::XY(4, 2, "");
/// test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]);
/// test(area, &Align::n(two_by_four()), [18, 10, 4, 2]);
/// test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]);
/// test(area, &Align::e(two_by_four()), [26, 19, 4, 2]);
/// test(area, &Align::se(two_by_four()), [26, 28, 4, 2]);
/// test(area, &Align::s(two_by_four()), [18, 28, 4, 2]);
/// test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]);
/// test(area, &Align::w(two_by_four()), [10, 19, 4, 2]);
/// ```
pub struct Align<T>(pub Alignment, pub T);
// TODO DOCUMENTME
pub enum Pad<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// TODO DOCUMENTME
///
/// ```
/// use tengri::{Bounded, XYWH};
/// let area = XYWH(0, 0, 0, 0);
/// let content = "";
/// let bounded: Bounded<tengri::tui::TuiOut, _> = Bounded(area, content);
/// ```
pub struct Bounded<O: Out, D>(pub XYWH<O::Unit>, pub D);
/// Draws items from an iterator.
///
/// ```
/// // FIXME let map = tengri::Map(||[].iter(), |_|{});
/// ```
pub struct Map<O, A, B, I, F, G>
where
I: Iterator<Item = A> + Send + Sync,
F: Fn() -> I + Send + Sync,
{
/// Function that returns iterator over stacked components
pub get_iter: F,
/// Function that returns each stacked component
pub get_item: G,
pub __: PhantomData<(O, B)>,
}
// TODO DOCUMENTME
pub struct Lazy<O, T, F>(
pub F,
pub PhantomData<(O, T)>
);
// TODO DOCUMENTME
pub struct Thunk<O: Out, F: Fn(&mut O)>(
pub PhantomData<O>,
pub F
);
// TODO DOCUMENTME
#[derive(Debug, Default)] pub struct Memo<T, U> {
pub value: T,
pub view: Arc<RwLock<U>>
}
/// A binary split or layer.
pub struct Bsp<Head, Tail>(
/// Direction of split
pub(crate) Direction,
/// First element.
pub(crate) Head,
/// Second element.
pub(crate) Tail,
);
// TODO DOCUMENTME
pub struct Bordered<S, W>(pub bool, pub S, pub W);
// TODO DOCUMENTME
pub struct Border<S>(pub bool, pub S);
// TODO DOCUMENTME
pub struct Foreground<Color, Item>(pub Color, pub Item);
// TODO DOCUMENTME
pub struct Background<Color, Item>(pub Color, pub Item);
// TODO DOCUMENTME
pub struct FieldH<Theme, Label, Value>(pub Theme, pub Label, pub Value);
// TODO DOCUMENTME
pub struct FieldV<Theme, Label, Value>(pub Theme, pub Label, pub Value);
/// A three-column layout.
pub struct Tryptich<A, B, C> {
pub top: bool, pub top: bool,
pub h: u16, pub h: u16,
pub left: (u16, A), pub left: (u16, A),
pub middle: (u16, B), pub middle: (u16, B),
pub right: (u16, C), pub right: (u16, C),
} }
// TODO: // TODO:
pub struct Field<C, T, U> { pub struct Field<C, T, U> {
pub direction: Direction, pub direction: Direction,
pub label: Option<T>, pub label: Option<T>,
pub label_fg: Option<C>, pub label_fg: Option<C>,
@ -355,40 +393,26 @@ pub struct Field<C, T, U> {
pub value_fg: Option<C>, pub value_fg: Option<C>,
pub value_bg: Option<C>, pub value_bg: Option<C>,
pub value_align: Option<Direction>, pub value_align: Option<Direction>,
} }
/// Performance counter // TODO DOCUMENTME
#[derive(Debug)] pub struct FieldH<Theme, Label, Value>(pub Theme, pub Label, pub Value);
pub struct PerfModel {
pub enabled: bool,
pub clock: quanta::Clock, // TODO DOCUMENTME
// In nanoseconds. Time used by last iteration. pub struct FieldV<Theme, Label, Value>(pub Theme, pub Label, pub Value);
pub used: AtomicF64,
// In microseconds. Max prescribed time for iteration (frame, chunk...).
pub window: AtomicF64,
}
/// Repeat a string, e.g. for background /// Repeat a string, e.g. for background
pub enum Repeat<'a> { pub enum Repeat<'a> {
X(&'a str), X(&'a str),
Y(&'a str), Y(&'a str),
XY(&'a str) XY(&'a str)
} }
/// Scroll indicator /// Scroll indicator
pub enum Scrollbar { pub enum Scrollbar {
/// Horizontal scrollbar /// Horizontal scrollbar
X { offset: usize, length: usize, total: usize, }, X { offset: usize, length: usize, total: usize, },
/// Vertical scrollbar /// Vertical scrollbar
Y { offset: usize, length: usize, total: usize, } Y { offset: usize, length: usize, total: usize, }
} }
/// A cell that takes up 3 rows on its own,
/// but stacks, giving (N+1)*2 rows per N cells.
pub struct Phat<T> {
pub width: u16,
pub height: u16,
pub content: T,
pub colors: [Color;4],
} }

View file

@ -1,72 +1,143 @@
use crate::*; use crate::*;
/// Source of [Input::Event]s: keyboard, mouse... pub use self::input::*;
/// mod input {
/// ``` use crate::*;
///
/// use crate::*; /// Something that will exit and not resume, e.g. the main input loop.
/// struct TestInput(bool); /// ```
/// enum TestEvent { Test1 } /// use ::tengri::Done;
/// impl Input for TestInput { /// use ::std::sync::atomic::{AtomicBool, Ordering};
/// type Event = TestEvent; /// use Ordering::Relaxed;
/// type Handled = (); ///
/// fn event (&self) -> &Self::Event { /// struct Example(AtomicBool);
/// &TestEvent::Test1 /// impl Done for Example {
/// } /// fn is_done (&self) -> bool { self.0.load(Relaxed) }
/// fn is_done (&self) -> bool { /// fn done (&self) { self.0.store(true, Relaxed) }
/// self.0 /// }
/// } ///
/// fn done (&self) {} /// assert!(Example(true.into()).is_done());
/// } /// assert!(!Example(false.into()).is_done());
/// let _ = TestInput(true).event(); ///
/// assert!(TestInput(true).is_done()); /// let state = Example(false.into());
/// assert!(!TestInput(false).is_done()); /// while !state.is_done() {
/// Ok(()) /// state.done(); // exit immediately
/// ``` /// }
pub trait Input: Sized { /// ```
pub trait Done {
fn is_done (&self) -> bool;
fn done (&self);
}
/// Source of [Input::Event]s: keyboard, mouse...
///
/// ```
/// use ::tengri::{Done, Input};
/// use ::std::sync::atomic::{AtomicBool, Ordering};
/// use Ordering::Relaxed;
///
/// struct Example(AtomicBool);
/// impl Done for Example {
/// fn is_done (&self) -> bool { self.0.load(Relaxed) }
/// fn done (&self) { self.0.store(true, Relaxed) }
/// }
///
/// enum TestEvent { Test1 }
/// impl Input for Example {
/// type Event = TestEvent;
/// type Handled = ();
/// fn event (&self) -> &Self::Event {
/// &TestEvent::Test1
/// }
/// }
///
/// let _ = Example(true.into()).event();
/// ```
pub trait Input: Done + Sized {
/// Type of input event /// Type of input event
type Event; type Event;
/// Result of handling input /// Result of handling input
type Handled; // TODO: make this an Option<Box dyn Command<Self>> containing the undo type Handled; // TODO: make this an Option<Box dyn Command<Self>> containing the undo
/// Currently handled event /// Currently handled event
fn event (&self) -> &Self::Event; fn event (&self) -> &Self::Event;
/// Whether component should exit }
fn is_done (&self) -> bool;
/// Mark component as done
fn done (&self);
}
/// State mutation. /// State mutation.
pub trait Command<S>: Send + Sync + Sized { pub trait Command<S>: Send + Sync + Sized {
fn execute (&self, state: &mut S) -> Perhaps<Self>; fn execute (&self, state: &mut S) -> Perhaps<Self>;
fn delegate <T> (&self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps<T> fn delegate <T> (&self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps<T>
where Self: Sized where Self: Sized
{ {
Ok(self.execute(state)?.map(wrap)) Ok(self.execute(state)?.map(wrap))
} }
}
/// 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),*) })*
}
};
);
flex_trait_mut!(Handle <E: Input> {
fn handle (&mut self, _input: &E) -> Perhaps<E::Handled> {
Ok(None)
}
});
} }
/// Drawing target. pub use self::output::*;
/// mod output {
/// ``` use crate::*;
/// use tengri::output::*;
/// struct TestOut(XYWH<u16>); /// Drawing target.
/// impl Out for TestOut { ///
/// type Unit = u16; /// ```
/// fn area (&self) -> XYWH<u16> { self.0 } /// use tengri::*;
/// fn area_mut (&mut self) -> &mut XYWH<u16> { &mut self.0 } ///
/// fn place_at <T: Draw<Self> + ?Sized> (&mut self, area: XYWH<u16>, _: &T) { /// struct TestOut(XYWH<u16>);
/// println!("place_at: {area:?}"); ///
/// () /// impl tengri::Out for TestOut {
/// } /// type Unit = u16;
/// } /// fn area (&self) -> XYWH<u16> { self.0 }
/// impl Draw<TestOut> for String { /// fn area_mut (&mut self) -> &mut XYWH<u16> { &mut self.0 }
/// fn draw (&self, to: &mut TestOut) { /// fn place_at <T: Draw<Self> + ?Sized> (&mut self, area: XYWH<u16>, _: &T) {
/// //to.area_mut().set_w(self.len() as u16); /// println!("place_at: {area:?}");
/// } /// ()
/// } /// }
/// ``` /// }
pub trait Out: Send + Sync + Sized { ///
/// impl tengri::Draw<TestOut> for String {
/// fn draw (&self, to: &mut TestOut) {
/// //to.area_mut().set_w(self.len() as u16);
/// }
/// }
/// ```
pub trait Out: Send + Sync + Sized {
/// Unit of length /// Unit of length
type Unit: Coord; type Unit: Coord;
/// Current output area /// Current output area
@ -79,12 +150,12 @@ pub trait Out: Send + Sync + Sized {
#[inline] fn place <'t, T: Content<Self> + ?Sized> (&mut self, content: &'t T) { #[inline] fn place <'t, T: Content<Self> + ?Sized> (&mut self, content: &'t T) {
self.place_at(content.layout(self.area()), content) self.place_at(content.layout(self.area()), content)
} }
} }
/// A numeric type that can be used as coordinate. /// A numeric type that can be used as coordinate.
/// ///
/// FIXME: Replace this ad-hoc trait with `num` crate. /// FIXME: Replace this ad-hoc trait with `num` crate.
pub trait Coord: Send + Sync + Copy pub trait Coord: Send + Sync + Copy
+ Add<Self, Output=Self> + Add<Self, Output=Self>
+ Sub<Self, Output=Self> + Sub<Self, Output=Self>
+ Mul<Self, Output=Self> + Mul<Self, Output=Self>
@ -94,7 +165,7 @@ pub trait Coord: Send + Sync + Copy
+ From<u16> + Into<u16> + From<u16> + Into<u16>
+ Into<usize> + Into<usize>
+ Into<f64> + Into<f64>
{ {
fn plus (self, other: Self) -> Self; fn plus (self, other: Self) -> Self;
fn minus (self, other: Self) -> Self { fn minus (self, other: Self) -> Self {
if self >= other { self - other } else { 0.into() } if self >= other { self - other } else { 0.into() }
@ -105,18 +176,18 @@ pub trait Coord: Send + Sync + Copy
fn zero () -> Self { fn zero () -> Self {
0.into() 0.into()
} }
} }
/// Drawable with dynamic dispatch. /// Drawable with dynamic dispatch.
pub trait Draw<O: Out> { pub trait Draw<O: Out> {
fn draw (&self, to: &mut O); fn draw (&self, to: &mut O);
} }
/// Outputs combinator. /// Outputs combinator.
pub trait Lay<O: Out>: Sized {} pub trait Lay<O: Out>: Sized {}
/// Drawable area of display. /// Drawable area of display.
pub trait Layout<O: Out> { pub trait Layout<O: Out> {
fn layout_x (&self, to: XYWH<O::Unit>) -> O::Unit { to.x() } fn layout_x (&self, to: XYWH<O::Unit>) -> O::Unit { to.x() }
fn layout_y (&self, to: XYWH<O::Unit>) -> O::Unit { to.y() } fn layout_y (&self, to: XYWH<O::Unit>) -> O::Unit { to.y() }
fn layout_w_min (&self, _t: XYWH<O::Unit>) -> O::Unit { 0.into() } fn layout_w_min (&self, _t: XYWH<O::Unit>) -> O::Unit { 0.into() }
@ -128,33 +199,33 @@ pub trait Layout<O: Out> {
fn layout (&self, to: XYWH<O::Unit>) -> XYWH<O::Unit> { fn layout (&self, to: XYWH<O::Unit>) -> XYWH<O::Unit> {
XYWH(self.layout_x(to), self.layout_y(to), self.layout_w(to), self.layout_h(to)) XYWH(self.layout_x(to), self.layout_y(to), self.layout_w(to), self.layout_h(to))
} }
} }
pub trait HasContent<O: Out> { pub trait HasContent<O: Out> {
fn content (&self) -> impl Content<O>; fn content (&self) -> impl Content<O>;
} }
// TODO DOCUMENTME // TODO DOCUMENTME
pub trait Content<O: Out>: Draw<O> + Layout<O> {} pub trait Content<O: Out>: Draw<O> + Layout<O> {}
// Something that has an origin point (X, Y). // Something that has an origin point (X, Y).
pub trait HasXY<N: Coord> { pub trait HasXY<N: Coord> {
fn x (&self) -> N; fn x (&self) -> N;
fn y (&self) -> N; fn y (&self) -> N;
fn xy (&self) -> XY<N> { XY(self.x(), self.y()) } fn xy (&self) -> XY<N> { XY(self.x(), self.y()) }
} }
// Something that has a size (W, H). // Something that has a size (W, H).
pub trait HasWH<N: Coord> { pub trait HasWH<N: Coord> {
fn w (&self) -> N; fn w (&self) -> N;
fn h (&self) -> N; fn h (&self) -> N;
fn wh (&self) -> WH<N> { WH(self.w(), self.h()) } fn wh (&self) -> WH<N> { WH(self.w(), self.h()) }
} }
// Something that has a 2D bounding box (X, Y, W, H). // Something that has a 2D bounding box (X, Y, W, H).
// //
// FIXME: The other way around? // FIXME: The other way around?
pub trait HasXYWH<N: Coord>: HasXY<N> + HasWH<N> { pub trait HasXYWH<N: Coord>: HasXY<N> + HasWH<N> {
fn x2 (&self) -> N { self.x().plus(self.w()) } fn x2 (&self) -> N { self.x().plus(self.w()) }
fn y2 (&self) -> N { self.y().plus(self.h()) } fn y2 (&self) -> N { self.y().plus(self.h()) }
fn xywh (&self) -> XYWH<N> { XYWH(self.x(), self.y(), self.w(), self.h()) } fn xywh (&self) -> XYWH<N> { XYWH(self.x(), self.y(), self.w(), self.h()) }
@ -165,32 +236,22 @@ pub trait HasXYWH<N: Coord>: HasXY<N> + HasWH<N> {
Ok(self) Ok(self)
} }
} }
} }
// Something that has a [Measure] of its rendered size. // Something that has a [Measure] of its rendered size.
pub trait Measured<O: Out> { pub trait Measured<O: Out> {
fn measure (&self) -> &Measure<O>; fn measure (&self) -> &Measure<O>;
fn measure_width (&self) -> O::Unit { self.measure().w() } fn measure_width (&self) -> O::Unit { self.measure().w() }
fn measure_height (&self) -> O::Unit { self.measure().h() } fn measure_height (&self) -> O::Unit { self.measure().h() }
} }
pub trait HasPerf { pub trait HasPerf {
fn perf (&self) -> &PerfModel; fn perf (&self) -> &PerfModel;
} }
pub trait TuiDraw = Draw<TuiOut>; pub trait HasColor { fn color (&self) -> ItemColor; }
pub trait TuiLayout = Layout<TuiOut>; pub trait BorderStyle: Content<TuiOut> + Copy {
pub trait TuiContent = Content<TuiOut>;
pub trait TuiHandle = Handle<TuiIn>;
pub trait TuiWidget = TuiDraw + TuiHandle;
pub trait HasColor { fn color (&self) -> ItemColor; }
pub trait BorderStyle: Content<TuiOut> + Copy {
fn enabled (&self) -> bool; fn enabled (&self) -> bool;
fn enclose (self, w: impl Content<TuiOut>) -> impl Content<TuiOut> { fn enclose (self, w: impl Content<TuiOut>) -> impl Content<TuiOut> {
Bsp::b(Fill::XY(Border(self.enabled(), self)), w) Bsp::b(Fill::XY(Border(self.enabled(), self)), w)
@ -282,42 +343,15 @@ pub trait BorderStyle: Content<TuiOut> + Copy {
#[inline] fn style_horizontal (&self) -> Option<Style> { self.style() } #[inline] fn style_horizontal (&self) -> Option<Style> { self.style() }
#[inline] fn style_vertical (&self) -> Option<Style> { self.style() } #[inline] fn style_vertical (&self) -> Option<Style> { self.style() }
#[inline] fn style_corners (&self) -> Option<Style> { self.style() } #[inline] fn style_corners (&self) -> Option<Style> { self.style() }
}
} }
/// Define a trait an implement it for various mutation-enabled wrapper types. */ pub use self::tui::*;
#[macro_export] macro_rules! flex_trait_mut ( mod tui {
($Trait:ident $(<$($A:ident:$T:ident),+>)? { use crate::*;
$(fn $fn:ident (&mut $self:ident $(, $arg:ident:$ty:ty)*) -> $ret:ty $body:block)* pub trait TuiDraw = Draw<TuiOut>;
})=>{ pub trait TuiLayout = Layout<TuiOut>;
pub trait $Trait $(<$($A: $T),+>)? { pub trait TuiContent = Content<TuiOut>;
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret $body)* pub trait TuiHandle = Handle<TuiIn>;
} pub trait TuiWidget = TuiDraw + TuiHandle;
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),*) })*
}
};
);
flex_trait_mut!(Handle <E: Input> {
fn handle (&mut self, _input: &E) -> Perhaps<E::Handled> {
Ok(None)
}
});

View file