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]]
name = "tengri"
version = "0.14.0"
version = "0.15.0"
dependencies = [
"anyhow",
"atomic_float",
"better-panic",
"bumpalo",
@ -1184,7 +1185,7 @@ dependencies = [
[[package]]
name = "tengri_proc"
version = "0.14.0"
version = "0.15.0"
dependencies = [
"dizzle",
"heck",

View file

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

View file

@ -1,5 +1,4 @@
use ::{std::sync::{Arc, RwLock}, ratatui::style::Color,
tengri::{*, input::*, output::*}, tengri_tui::*};
use ::{std::{io::stdout, sync::{Arc, RwLock}}, ratatui::style::Color, tengri::*};
tui_main!(State { cursor: 10, ..Default::default() });
namespace!(State: bool {});
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 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.view(to, &src).unwrap()))).draw(to)
state.size.of(Bsp::s(title, Bsp::n(code, state.understand(to, &src).unwrap()))).draw(to)
}
impl Action {
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 () {}
//#[tengri_proc::expose]

View file

@ -1,18 +1,18 @@
[package]
name = "tengri_proc"
description = "UI metaframework, procedural macros."
version = { workspace = true }
edition = { workspace = true }
version = "0.15.0"
edition = "2024"
[lib]
proc-macro = true
[dependencies]
dizzle = { path = "../../dizzle" }
syn = { workspace = true }
quote = { workspace = true }
proc-macro2 = { workspace = true }
heck = { workspace = true }
quote = { version = "1" }
syn = { version = "2", features = ["full", "extra-traits"] }
heck = { version = "0.5" }
proc-macro2 = { version = "1", features = ["span-locations"] }
[target.'cfg(target_os = "linux")']
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_precise_live_drops)]
#![feature(const_trait_impl)]
#![feature(if_let_guard)]
#![feature(impl_trait_in_assoc_type)]
#![feature(step_trait)]
#![feature(trait_alias)]
#![feature(type_alias_impl_trait)]
#![feature(type_changing_struct_update)]
//pub(crate) use quanta::Clock;
pub extern crate atomic_float;
pub(crate) use atomic_float::AtomicF64;
mod tengri_impl;
mod tengri_trait; pub use self::tengri_trait::*;
mod tengri_struct; pub use self::tengri_struct::*;
#[cfg(test)] pub(crate) use proptest_derive::Arbitrary;
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::{
prelude::{Color, Style, Buffer, Position},
style::{Stylize, Modifier, Color::*},
@ -23,39 +25,19 @@ pub extern crate ratatui; pub(crate) use ::ratatui::{
layout::{Size, Rect},
buffer::Cell
};
pub extern crate crossterm;
pub(crate) use ::crossterm::{
pub extern crate crossterm; pub(crate) use ::crossterm::{
ExecutableCommand,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
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::{
io::{stdout, Stdout},
io::{stdout, Stdout, Write},
sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}},
fmt::{Debug, Display},
ops::{Add, Sub, Mul, Div},
marker::PhantomData,
time::Duration,
thread::{spawn, JoinHandle}
};
// 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,
#[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.
#[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.
#[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.
#[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`
#[macro_export] macro_rules! command {
@ -154,7 +144,9 @@ pub(crate) use ::std::{
#[macro_export] macro_rules! tui_main {
($expr:expr) => {
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(())
}
};
@ -328,20 +320,28 @@ pub fn tui_input <T: Handle<TuiIn> + Send + Sync + 'static> (
/// Define layout operation.
///
/// ```
/// struct Target;
/// impl tengri::Output for Target { type Unit = u16; }
/// # use tengri::*;
/// struct Target { xywh: XYWH<u16> /*platform-specific*/}
/// impl tengri::Out for Target {
/// type Unit = u16;
/// fn area (&self) -> XYWH<u16> { self.xywh }
/// fn area_mut (&mut self) -> &mut XYWH<u16> { &mut self.xywh }
/// fn 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, u16> for State {}
/// impl Understand<Target, ()> for State {}
///
/// let state = State;
/// let target = Target::default();
/// tengri::evaluate_output_expression(&state, &mut out, "")?;
/// tengri::evaluate_output_expression(&state, &mut out, "(when true (text hello))")?;
/// tengri::evaluate_output_expression(&state, &mut out, "(either true (text hello) (text world)"))?;
/// # fn main () -> tengri::Usually<()> {
/// let state = State {};
/// let mut target = Target { xywh: Default::default() };
/// evaluate_output_expression(&state, &mut target, &"")?;
/// 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
/// # Ok(()) }
/// ```
#[cfg(feature = "dsl")] pub fn evaluate_output_expression <'a, O: Out + 'a, S> (
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...
///
/// ```
/// use tengri::{Namespace, Understand, TuiOut, 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<TuiOut, ()> for State {}
/// # fn main () -> tengri::Usually<()> {
/// 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, "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 3) (fg (g 4) (text Hello world!)))")?;
/// # Ok(()) }
/// ```
#[cfg(feature = "dsl")] pub fn evaluate_output_expression_tui <'a, S> (
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 _ = button_2("", "", false);
/// let _ = tengri::button_2("", "", true);
/// let _ = tengri::button_2("", "", false);
/// ```
pub fn button_2 <'a> (key: impl Content<TuiOut>, label: impl Content<TuiOut>, editing: bool) -> impl Content<TuiOut> {
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 _ = button_3("", "", "", false);
/// let _ = tengri::button_3("", "", "", true);
/// let _ = tengri::button_3("", "", "", false);
/// ```
pub fn button_3 <'a> (
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_derive::Arbitrary;
use crate::*;
use Direction::*;
proptest! {
#[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<()> {
//use std::sync::{Arc, RwLock};
struct TestComponent(String);
impl Draw<TuiOut> for TestComponent {
fn draw (&self, _to: &mut TuiOut) {
}
}
impl Handle<TuiIn> for TestComponent {
fn handle (&mut self, _from: &TuiIn) -> Perhaps<bool> {
Ok(None)
}
}
let mut output = String::new();
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(())
}
//#[test] fn test_tui_engine () -> Usually<()> {
////use std::sync::{Arc, RwLock};
//struct TestComponent(String);
//impl Draw<TuiOut> for TestComponent {
//fn draw (&self, _to: &mut TuiOut) {}
//}
//impl Handle<TuiIn> for TestComponent {
//fn handle (&mut self, _from: &TuiIn) -> Perhaps<bool> { Ok(None) }
//}
//let engine = Tui::new(Box::<&mut Vec<u8>>::new(vec![0u8;0].as_mut()))?;
//let state = engine.run(false, &Arc::new(RwLock::new(TestComponent("hello world".into()))))?;
//state.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed);
//Ok(())
//}
}

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> {
pub const fn new (thunk: F) -> Self {
Self(thunk, PhantomData)
}
pub const fn new (thunk: F) -> Self { Self(thunk, PhantomData) }
}
impl<O: Out, F: Fn(&mut O)> Thunk<O, F> {
pub const fn new (draw: F) -> Self {
Self(PhantomData, draw)
}
pub const fn new (draw: F) -> Self { Self(PhantomData, draw) }
}
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> {
fn draw (&self, to: &mut O) {
match self.0 {
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);
let [a, b, _] = bsp_areas(to.area(), self.0, &self.1, &self.2);
//panic!("{a:?} {b:?}");
//if self.0 == Below {
//to.place_at(a, &self.1);
//to.place_at(b, &self.2);
//} else {
//to.place_at(b, &self.2);
//to.place_at(a, &self.1);
//}
if self.0 == Below {
to.place_at(a, &self.1);
to.place_at(b, &self.2);
} else {
to.place_at(b, &self.2);
to.place_at(a, &self.1);
}
}
}
@ -1011,7 +965,7 @@ impl PerfModel {
}
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 Size { width, height } = backend.size()?;
Ok(Self {
@ -1061,6 +1015,8 @@ impl Input for TuiIn {
type Event = TuiEvent;
type Handled = bool;
fn event (&self) -> &TuiEvent { &self.event }
}
impl Done for TuiIn {
fn done (&self) { self.exited.store(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))
}
}
impl From<Event> for TuiEvent {
fn from (e: Event) -> Self {
Self(e)
}
}
impl From<char> for TuiEvent {
fn from (c: char) -> Self {
Self(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE)))

View file

@ -1,84 +1,65 @@
#[cfg(test)] use proptest_derive::Arbitrary;
pub use self::logical::*; mod logical {
use crate::*;
/// The `Tui` struct (the *engine*) implements the
/// `tengri_input::Input` and `tengri_output::Out` traits.
/// At launch, the `Tui` engine spawns two threads, the render thread and the input thread.
/// the application may further spawn other threads. All threads communicate using shared ownership:
/// `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>,
pub backend: CrosstermBackend<Stdout>,
pub buffer: Buffer,
pub area: [u16;4],
pub perf: PerfModel,
}
#[derive(Debug, Clone)] pub struct TuiIn {
/// Input event
pub event: TuiEvent,
/// Exit flag
pub exited: Arc<AtomicBool>,
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent(
pub Event
);
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiKey(
pub Option<KeyCode>,
pub KeyModifiers
);
#[derive(Default)] pub struct TuiOut {
pub buffer: Buffer,
pub area: XYWH<u16>,
}
/// TUI buffer sized by `usize` instead of `u16`.
#[derive(Default)] pub struct BigBuffer {
pub width: usize,
pub height: usize,
pub content: Vec<Cell>
}
/// A color in OKHSL and RGB representations.
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor {
pub okhsl: Okhsl<f32>,
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,
}
pub struct Modify<T>(pub bool, pub Modifier, pub T);
pub struct Styled<T>(pub Option<Style>, pub T);
/// Displays an owned [str]-like with fixed maximum width.
///
/// Width is computed using [unicode_width].
pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
/// Displays a borrowed [str]-like with fixed maximum width
///
/// Width is computed using [unicode_width].
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
/// Thunks can be natural error boundaries!
pub struct ErrorBoundary<O: Out, T: Draw<O>>(
pub std::marker::PhantomData<O>,
pub Perhaps<T>
);
// 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>>
}
}
pub use self::temporal::*; mod temporal {
use crate::*;
/// Performance counter
#[derive(Debug)]
pub struct PerfModel {
pub enabled: bool,
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,
}
}
pub use self::spatial::*; mod spatial {
use crate::*;
// TODO DOCUMENTME
pub struct Bordered<S, W>(pub bool, pub S, pub W);
// TODO DOCUMENTME
pub struct Border<S>(pub bool, pub S);
/// 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,
);
/// A point (X, Y).
///
@ -104,7 +85,7 @@ pub struct ErrorBoundary<O: Out, T: Draw<O>>(
///
/// ```
/// let xywh = tengri::XYWH(0u16, 0, 0, 0);
/// assert_eq!(XYWH(10u16, 10, 20, 20).center(), XY(20, 20));
/// assert_eq!(tengri::XYWH(10u16, 10, 20, 20).center(), tengri::XY(20, 20));
/// ```
///
/// * [ ] TODO: anchor field (determines at which corner/side is X0 Y0)
@ -137,7 +118,7 @@ pub struct ErrorBoundary<O: Out, T: Draw<O>>(
/// A widget that tracks its rendered width and height.
///
/// ```
/// let measure = tengri::Measure::<tengri::tui::TuiOut>::default();
/// let measure = tengri::Measure::<tengri::TuiOut>::default();
/// ```
#[derive(Default)] pub struct Measure<O: Out> {
pub __: PhantomData<O>,
@ -148,7 +129,7 @@ pub struct ErrorBoundary<O: Out, T: Draw<O>>(
/// Show an item only when a condition is true.
///
/// ```
/// fn test () -> impl tengri::Draw<tengri::tui::TuiOut> {
/// fn test () -> impl tengri::Draw<tengri::TuiOut> {
/// tengri::when(true, "Yes")
/// }
/// ```
@ -160,7 +141,7 @@ pub const fn when<O, T>(condition: bool, content: T) -> When<O, T> {
/// Show one item if a condition is true and another if the condition is false.
///
/// ```
/// fn test () -> impl tengri::Draw<tengri::tui::TuiOut> {
/// fn test () -> impl tengri::Draw<tengri::TuiOut> {
/// tengri::either(true, "Yes", "No")
/// }
/// ```
@ -229,7 +210,7 @@ pub enum Expand<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
///
///
/// ```
/// use ::tengri::{output::*, tui::*};
/// 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);
@ -267,7 +248,7 @@ pub enum Pad<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// use tengri::{Bounded, XYWH};
/// let area = XYWH(0, 0, 0, 0);
/// let content = "";
/// let bounded: Bounded<tengri::tui::TuiOut, _> = Bounded(area, content);
/// let bounded: Bounded<tengri::TuiOut, _> = Bounded(area, content);
/// ```
pub struct Bounded<O: Out, D>(pub XYWH<O::Unit>, pub D);
@ -289,39 +270,79 @@ where
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 color in OKHSL and RGB representations.
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor {
pub okhsl: Okhsl<f32>,
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,
}
}
/// 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,
pub use self::terminal::*; mod terminal {
use crate::*;
/// 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,
}
/// The TUI input event source.
///
/// ```
/// 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>,
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent(
pub Event
);
// TODO DOCUMENTME
pub struct Bordered<S, W>(pub bool, pub S, pub W);
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiKey(
pub Option<KeyCode>,
pub KeyModifiers
);
// TODO DOCUMENTME
pub struct Border<S>(pub bool, pub S);
/// The TUI output render target.
///
/// ```
/// let tui_out = tengri::TuiOut::default();
/// ```
#[derive(Default)] pub struct TuiOut {
pub buffer: Buffer,
pub area: XYWH<u16>,
}
/// TUI buffer sized by `usize` instead of `u16`.
#[derive(Default)] pub struct BigBuffer {
pub width: usize,
pub height: usize,
pub content: Vec<Cell>
}
// TODO DOCUMENTME
pub struct Foreground<Color, Item>(pub Color, pub Item);
@ -329,11 +350,28 @@ 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);
pub struct Modify<T>(pub bool, pub Modifier, pub T);
// TODO DOCUMENTME
pub struct FieldV<Theme, Label, Value>(pub Theme, pub Label, pub Value);
pub struct Styled<T>(pub Option<Style>, pub T);
/// Displays an owned [str]-like with fixed maximum width.
///
/// Width is computed using [unicode_width].
pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
/// Displays a borrowed [str]-like with fixed maximum width
///
/// Width is computed using [unicode_width].
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
/// A cell that takes up 3 rows on its own,
/// but stacks, giving (N+1)*2 rows per N cells.
pub struct Phat<T> {
pub width: u16,
pub height: u16,
pub content: T,
pub colors: [Color;4],
}
/// A three-column layout.
pub struct Tryptich<A, B, C> {
@ -357,17 +395,11 @@ pub struct Field<C, T, U> {
pub value_align: Option<Direction>,
}
/// Performance counter
#[derive(Debug)]
pub struct PerfModel {
pub enabled: bool,
// TODO DOCUMENTME
pub struct FieldH<Theme, Label, Value>(pub Theme, pub Label, pub Value);
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,
}
// TODO DOCUMENTME
pub struct FieldV<Theme, Label, Value>(pub Theme, pub Label, pub Value);
/// Repeat a string, e.g. for background
pub enum Repeat<'a> {
@ -383,12 +415,4 @@ pub enum Scrollbar {
/// Vertical scrollbar
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,39 +1,65 @@
use crate::*;
pub use self::input::*;
mod input {
use crate::*;
/// Something that will exit and not resume, e.g. the main input loop.
/// ```
/// use ::tengri::Done;
/// 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) }
/// }
///
/// assert!(Example(true.into()).is_done());
/// assert!(!Example(false.into()).is_done());
///
/// let state = Example(false.into());
/// while !state.is_done() {
/// state.done(); // exit immediately
/// }
/// ```
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) }
/// }
///
/// use crate::*;
/// struct TestInput(bool);
/// enum TestEvent { Test1 }
/// impl Input for TestInput {
/// impl Input for Example {
/// type Event = TestEvent;
/// type Handled = ();
/// fn event (&self) -> &Self::Event {
/// &TestEvent::Test1
/// }
/// fn is_done (&self) -> bool {
/// self.0
/// }
/// fn done (&self) {}
/// }
/// let _ = TestInput(true).event();
/// assert!(TestInput(true).is_done());
/// assert!(!TestInput(false).is_done());
/// Ok(())
///
/// let _ = Example(true.into()).event();
/// ```
pub trait Input: Sized {
pub trait Input: Done + Sized {
/// Type of input event
type Event;
/// Result of handling input
type Handled; // TODO: make this an Option<Box dyn Command<Self>> containing the undo
/// Currently handled event
fn event (&self) -> &Self::Event;
/// Whether component should exit
fn is_done (&self) -> bool;
/// Mark component as done
fn done (&self);
}
/// State mutation.
@ -46,12 +72,56 @@ pub trait Command<S>: Send + Sync + Sized {
}
}
/// 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)
}
});
}
pub use self::output::*;
mod output {
use crate::*;
/// Drawing target.
///
/// ```
/// use tengri::output::*;
/// use tengri::*;
///
/// struct TestOut(XYWH<u16>);
/// impl Out for TestOut {
///
/// impl tengri::Out for TestOut {
/// type Unit = u16;
/// fn area (&self) -> XYWH<u16> { self.0 }
/// fn area_mut (&mut self) -> &mut XYWH<u16> { &mut self.0 }
@ -60,7 +130,8 @@ pub trait Command<S>: Send + Sync + Sized {
/// ()
/// }
/// }
/// impl Draw<TestOut> for String {
///
/// impl tengri::Draw<TestOut> for String {
/// fn draw (&self, to: &mut TestOut) {
/// //to.area_mut().set_w(self.len() as u16);
/// }
@ -178,16 +249,6 @@ pub trait HasPerf {
fn perf (&self) -> &PerfModel;
}
pub trait TuiDraw = Draw<TuiOut>;
pub trait TuiLayout = Layout<TuiOut>;
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 {
@ -283,41 +344,14 @@ pub trait BorderStyle: Content<TuiOut> + Copy {
#[inline] fn style_vertical (&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. */
#[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)*
pub use self::tui::*;
mod tui {
use crate::*;
pub trait TuiDraw = Draw<TuiOut>;
pub trait TuiLayout = Layout<TuiOut>;
pub trait TuiContent = Content<TuiOut>;
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