mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-03-13 12:10:44 +01:00
it's a song!
This commit is contained in:
parent
0d8503cc05
commit
5d0dc40fdc
11 changed files with 846 additions and 828 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
||||
|
|
|
|||
|
|
@ -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) };
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use ::{std::sync::{Arc, RwLock}, ratatui::style::Color, tengri::*};
|
||||
fn main () {}
|
||||
|
||||
//#[tengri_proc::expose]
|
||||
|
|
|
|||
|
|
@ -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
1
proc/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
currently unused.
|
||||
135
src/tengri.rs
135
src/tengri.rs
|
|
@ -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(())
|
||||
//}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -1,394 +1,418 @@
|
|||
#[cfg(test)] use proptest_derive::Arbitrary;
|
||||
use crate::*;
|
||||
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,
|
||||
/// 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::*;
|
||||
|
||||
#[derive(Debug, Clone)] pub struct TuiIn {
|
||||
/// Input event
|
||||
pub event: TuiEvent,
|
||||
/// Exit flag
|
||||
pub exited: Arc<AtomicBool>,
|
||||
/// 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,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent(
|
||||
pub Event
|
||||
);
|
||||
pub use self::spatial::*; mod spatial {
|
||||
use crate::*;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiKey(
|
||||
pub Option<KeyCode>,
|
||||
pub KeyModifiers
|
||||
);
|
||||
// TODO DOCUMENTME
|
||||
pub struct Bordered<S, W>(pub bool, pub S, pub W);
|
||||
|
||||
#[derive(Default)] pub struct TuiOut {
|
||||
pub buffer: Buffer,
|
||||
pub area: XYWH<u16>,
|
||||
// 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).
|
||||
///
|
||||
/// ```
|
||||
/// 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 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,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>
|
||||
);
|
||||
|
||||
|
||||
/// A point (X, Y).
|
||||
///
|
||||
/// ```
|
||||
/// 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!(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.
|
||||
///
|
||||
/// ```
|
||||
/// 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::tui::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::tui::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::tui::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::{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 h: u16,
|
||||
pub left: (u16, A),
|
||||
pub middle: (u16, B),
|
||||
pub right: (u16, C),
|
||||
}
|
||||
|
||||
// TODO:
|
||||
pub struct Field<C, T, U> {
|
||||
pub direction: Direction,
|
||||
pub label: Option<T>,
|
||||
pub label_fg: Option<C>,
|
||||
pub label_bg: Option<C>,
|
||||
pub label_align: Option<Direction>,
|
||||
pub value: Option<U>,
|
||||
pub value_fg: Option<C>,
|
||||
pub value_bg: Option<C>,
|
||||
pub value_align: Option<Direction>,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// Repeat a string, e.g. for background
|
||||
pub enum Repeat<'a> {
|
||||
X(&'a str),
|
||||
Y(&'a str),
|
||||
XY(&'a str)
|
||||
}
|
||||
|
||||
/// Scroll indicator
|
||||
pub enum Scrollbar {
|
||||
/// Horizontal scrollbar
|
||||
X { offset: usize, length: usize, total: usize, },
|
||||
/// 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],
|
||||
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
|
||||
);
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiKey(
|
||||
pub Option<KeyCode>,
|
||||
pub KeyModifiers
|
||||
);
|
||||
|
||||
/// 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);
|
||||
|
||||
// TODO DOCUMENTME
|
||||
pub struct Background<Color, Item>(pub Color, pub Item);
|
||||
|
||||
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);
|
||||
|
||||
/// 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> {
|
||||
pub top: bool,
|
||||
pub h: u16,
|
||||
pub left: (u16, A),
|
||||
pub middle: (u16, B),
|
||||
pub right: (u16, C),
|
||||
}
|
||||
|
||||
// TODO:
|
||||
pub struct Field<C, T, U> {
|
||||
pub direction: Direction,
|
||||
pub label: Option<T>,
|
||||
pub label_fg: Option<C>,
|
||||
pub label_bg: Option<C>,
|
||||
pub label_align: Option<Direction>,
|
||||
pub value: Option<U>,
|
||||
pub value_fg: Option<C>,
|
||||
pub value_bg: Option<C>,
|
||||
pub value_align: Option<Direction>,
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
/// Repeat a string, e.g. for background
|
||||
pub enum Repeat<'a> {
|
||||
X(&'a str),
|
||||
Y(&'a str),
|
||||
XY(&'a str)
|
||||
}
|
||||
|
||||
/// Scroll indicator
|
||||
pub enum Scrollbar {
|
||||
/// Horizontal scrollbar
|
||||
X { offset: usize, length: usize, total: usize, },
|
||||
/// Vertical scrollbar
|
||||
Y { offset: usize, length: usize, total: usize, }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,323 +1,357 @@
|
|||
use crate::*;
|
||||
|
||||
/// Source of [Input::Event]s: keyboard, mouse...
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// use crate::*;
|
||||
/// struct TestInput(bool);
|
||||
/// enum TestEvent { Test1 }
|
||||
/// impl Input for TestInput {
|
||||
/// 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(())
|
||||
/// ```
|
||||
pub trait Input: 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);
|
||||
}
|
||||
pub use self::input::*;
|
||||
mod input {
|
||||
use crate::*;
|
||||
|
||||
/// State mutation.
|
||||
pub trait Command<S>: Send + Sync + Sized {
|
||||
fn execute (&self, state: &mut S) -> Perhaps<Self>;
|
||||
fn delegate <T> (&self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps<T>
|
||||
where Self: Sized
|
||||
{
|
||||
Ok(self.execute(state)?.map(wrap))
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// Drawing target.
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::output::*;
|
||||
/// struct TestOut(XYWH<u16>);
|
||||
/// impl Out for TestOut {
|
||||
/// type Unit = u16;
|
||||
/// fn area (&self) -> XYWH<u16> { self.0 }
|
||||
/// fn area_mut (&mut self) -> &mut XYWH<u16> { &mut self.0 }
|
||||
/// fn place_at <T: Draw<Self> + ?Sized> (&mut self, area: XYWH<u16>, _: &T) {
|
||||
/// println!("place_at: {area:?}");
|
||||
/// ()
|
||||
/// }
|
||||
/// }
|
||||
/// impl 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
|
||||
type Unit: Coord;
|
||||
/// Current output area
|
||||
fn area (&self) -> XYWH<Self::Unit>;
|
||||
/// Mutable pointer to area.
|
||||
fn area_mut (&mut self) -> &mut XYWH<Self::Unit>;
|
||||
/// Render drawable in area specified by `area`
|
||||
fn place_at <'t, T: Draw<Self> + ?Sized> (&mut self, area: XYWH<Self::Unit>, content: &'t T);
|
||||
/// Render drawable in area specified by `T::layout(self.area())`
|
||||
#[inline] fn place <'t, T: Content<Self> + ?Sized> (&mut self, content: &'t T) {
|
||||
self.place_at(content.layout(self.area()), content)
|
||||
/// 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 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// A numeric type that can be used as coordinate.
|
||||
///
|
||||
/// FIXME: Replace this ad-hoc trait with `num` crate.
|
||||
pub trait Coord: Send + Sync + Copy
|
||||
+ Add<Self, Output=Self>
|
||||
+ Sub<Self, Output=Self>
|
||||
+ Mul<Self, Output=Self>
|
||||
+ Div<Self, Output=Self>
|
||||
+ Ord + PartialEq + Eq
|
||||
+ Debug + Display + Default
|
||||
+ From<u16> + Into<u16>
|
||||
+ Into<usize>
|
||||
+ Into<f64>
|
||||
{
|
||||
fn plus (self, other: Self) -> Self;
|
||||
fn minus (self, other: Self) -> Self {
|
||||
if self >= other { self - other } else { 0.into() }
|
||||
}
|
||||
fn atomic (self) -> AtomicUsize {
|
||||
AtomicUsize::new(self.into())
|
||||
}
|
||||
fn zero () -> Self {
|
||||
0.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Drawable with dynamic dispatch.
|
||||
pub trait Draw<O: Out> {
|
||||
fn draw (&self, to: &mut O);
|
||||
}
|
||||
|
||||
/// Outputs combinator.
|
||||
pub trait Lay<O: Out>: Sized {}
|
||||
|
||||
/// Drawable area of display.
|
||||
pub trait Layout<O: Out> {
|
||||
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_w_min (&self, _t: XYWH<O::Unit>) -> O::Unit { 0.into() }
|
||||
fn layout_w_max (&self, to: XYWH<O::Unit>) -> O::Unit { to.w() }
|
||||
fn layout_w (&self, to: XYWH<O::Unit>) -> O::Unit { to.w().max(self.layout_w_min(to)).min(self.layout_w_max(to)) }
|
||||
fn layout_h_min (&self, _t: XYWH<O::Unit>) -> O::Unit { 0.into() }
|
||||
fn layout_h_max (&self, to: XYWH<O::Unit>) -> O::Unit { to.h() }
|
||||
fn layout_h (&self, to: XYWH<O::Unit>) -> O::Unit { to.h().max(self.layout_h_min(to)).min(self.layout_h_max(to)) }
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasContent<O: Out> {
|
||||
fn content (&self) -> impl Content<O>;
|
||||
}
|
||||
|
||||
// TODO DOCUMENTME
|
||||
pub trait Content<O: Out>: Draw<O> + Layout<O> {}
|
||||
|
||||
// Something that has an origin point (X, Y).
|
||||
pub trait HasXY<N: Coord> {
|
||||
fn x (&self) -> N;
|
||||
fn y (&self) -> N;
|
||||
fn xy (&self) -> XY<N> { XY(self.x(), self.y()) }
|
||||
}
|
||||
|
||||
// Something that has a size (W, H).
|
||||
pub trait HasWH<N: Coord> {
|
||||
fn w (&self) -> N;
|
||||
fn h (&self) -> N;
|
||||
fn wh (&self) -> WH<N> { WH(self.w(), self.h()) }
|
||||
}
|
||||
|
||||
// Something that has a 2D bounding box (X, Y, W, H).
|
||||
//
|
||||
// FIXME: The other way around?
|
||||
pub trait HasXYWH<N: Coord>: HasXY<N> + HasWH<N> {
|
||||
fn x2 (&self) -> N { self.x().plus(self.w()) }
|
||||
fn y2 (&self) -> N { self.y().plus(self.h()) }
|
||||
fn xywh (&self) -> XYWH<N> { XYWH(self.x(), self.y(), self.w(), self.h()) }
|
||||
fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
|
||||
if self.w() < w || self.h() < h {
|
||||
Err(format!("min {w}x{h}").into())
|
||||
} else {
|
||||
Ok(self)
|
||||
/// State mutation.
|
||||
pub trait Command<S>: Send + Sync + Sized {
|
||||
fn execute (&self, state: &mut S) -> Perhaps<Self>;
|
||||
fn delegate <T> (&self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps<T>
|
||||
where Self: Sized
|
||||
{
|
||||
Ok(self.execute(state)?.map(wrap))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Something that has a [Measure] of its rendered size.
|
||||
pub trait Measured<O: Out> {
|
||||
fn measure (&self) -> &Measure<O>;
|
||||
fn measure_width (&self) -> O::Unit { self.measure().w() }
|
||||
fn measure_height (&self) -> O::Unit { self.measure().h() }
|
||||
}
|
||||
|
||||
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 {
|
||||
fn enabled (&self) -> bool;
|
||||
fn enclose (self, w: impl Content<TuiOut>) -> impl Content<TuiOut> {
|
||||
Bsp::b(Fill::XY(Border(self.enabled(), self)), w)
|
||||
}
|
||||
fn enclose2 (self, w: impl Content<TuiOut>) -> impl Content<TuiOut> {
|
||||
Bsp::b(Pad::XY(1, 1, Fill::XY(Border(self.enabled(), self))), w)
|
||||
}
|
||||
fn enclose_bg (self, w: impl Content<TuiOut>) -> impl Content<TuiOut> {
|
||||
Tui::bg(self.style().unwrap().bg.unwrap_or(Color::Reset),
|
||||
Bsp::b(Fill::XY(Border(self.enabled(), self)), w))
|
||||
}
|
||||
const NW: &'static str = "";
|
||||
const N: &'static str = "";
|
||||
const NE: &'static str = "";
|
||||
const E: &'static str = "";
|
||||
const SE: &'static str = "";
|
||||
const S: &'static str = "";
|
||||
const SW: &'static str = "";
|
||||
const W: &'static str = "";
|
||||
|
||||
const N0: &'static str = "";
|
||||
const S0: &'static str = "";
|
||||
const W0: &'static str = "";
|
||||
const E0: &'static str = "";
|
||||
|
||||
fn border_n (&self) -> &str { Self::N }
|
||||
fn border_s (&self) -> &str { Self::S }
|
||||
fn border_e (&self) -> &str { Self::E }
|
||||
fn border_w (&self) -> &str { Self::W }
|
||||
fn border_nw (&self) -> &str { Self::NW }
|
||||
fn border_ne (&self) -> &str { Self::NE }
|
||||
fn border_sw (&self) -> &str { Self::SW }
|
||||
fn border_se (&self) -> &str { Self::SE }
|
||||
#[inline] fn draw <'a> (
|
||||
&self, to: &mut TuiOut
|
||||
) -> Usually<()> {
|
||||
if self.enabled() {
|
||||
self.draw_horizontal(to, None)?;
|
||||
self.draw_vertical(to, None)?;
|
||||
self.draw_corners(to, None)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[inline] fn draw_horizontal (
|
||||
&self, to: &mut TuiOut, style: Option<Style>
|
||||
) -> Usually<XYWH<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_horizontal());
|
||||
let [x, x2, y, y2] = area.lrtb();
|
||||
for x in x..x2.saturating_sub(1) {
|
||||
to.blit(&Self::N, x, y, style);
|
||||
to.blit(&Self::S, x, y2.saturating_sub(1), style)
|
||||
}
|
||||
Ok(area)
|
||||
}
|
||||
#[inline] fn draw_vertical (
|
||||
&self, to: &mut TuiOut, style: Option<Style>
|
||||
) -> Usually<XYWH<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_vertical());
|
||||
let [x, x2, y, y2] = area.lrtb();
|
||||
let h = y2 - y;
|
||||
if h > 1 {
|
||||
for y in y..y2.saturating_sub(1) {
|
||||
to.blit(&Self::W, x, y, style);
|
||||
to.blit(&Self::E, x2.saturating_sub(1), y, 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)*
|
||||
}
|
||||
} else if h > 0 {
|
||||
to.blit(&Self::W0, x, y, style);
|
||||
to.blit(&Self::E0, x2.saturating_sub(1), y, style);
|
||||
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)
|
||||
}
|
||||
Ok(area)
|
||||
}
|
||||
#[inline] fn draw_corners (
|
||||
&self, to: &mut TuiOut, style: Option<Style>
|
||||
) -> Usually<XYWH<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_corners());
|
||||
let XYWH(x, y, width, height) = area;
|
||||
if width > 1 && height > 1 {
|
||||
to.blit(&Self::NW, x, y, style);
|
||||
to.blit(&Self::NE, x + width - 1, y, style);
|
||||
to.blit(&Self::SW, x, y + height - 1, style);
|
||||
to.blit(&Self::SE, x + width - 1, y + height - 1, style);
|
||||
}
|
||||
Ok(area)
|
||||
}
|
||||
#[inline] fn style (&self) -> Option<Style> { None }
|
||||
#[inline] fn style_horizontal (&self) -> Option<Style> { self.style() }
|
||||
#[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)*
|
||||
}
|
||||
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),*) })*
|
||||
}
|
||||
};
|
||||
);
|
||||
pub use self::output::*;
|
||||
mod output {
|
||||
use crate::*;
|
||||
|
||||
flex_trait_mut!(Handle <E: Input> {
|
||||
fn handle (&mut self, _input: &E) -> Perhaps<E::Handled> {
|
||||
Ok(None)
|
||||
/// Drawing target.
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::*;
|
||||
///
|
||||
/// struct TestOut(XYWH<u16>);
|
||||
///
|
||||
/// 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 }
|
||||
/// fn place_at <T: Draw<Self> + ?Sized> (&mut self, area: XYWH<u16>, _: &T) {
|
||||
/// println!("place_at: {area:?}");
|
||||
/// ()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// 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
|
||||
type Unit: Coord;
|
||||
/// Current output area
|
||||
fn area (&self) -> XYWH<Self::Unit>;
|
||||
/// Mutable pointer to area.
|
||||
fn area_mut (&mut self) -> &mut XYWH<Self::Unit>;
|
||||
/// Render drawable in area specified by `area`
|
||||
fn place_at <'t, T: Draw<Self> + ?Sized> (&mut self, area: XYWH<Self::Unit>, content: &'t T);
|
||||
/// Render drawable in area specified by `T::layout(self.area())`
|
||||
#[inline] fn place <'t, T: Content<Self> + ?Sized> (&mut self, content: &'t T) {
|
||||
self.place_at(content.layout(self.area()), content)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/// A numeric type that can be used as coordinate.
|
||||
///
|
||||
/// FIXME: Replace this ad-hoc trait with `num` crate.
|
||||
pub trait Coord: Send + Sync + Copy
|
||||
+ Add<Self, Output=Self>
|
||||
+ Sub<Self, Output=Self>
|
||||
+ Mul<Self, Output=Self>
|
||||
+ Div<Self, Output=Self>
|
||||
+ Ord + PartialEq + Eq
|
||||
+ Debug + Display + Default
|
||||
+ From<u16> + Into<u16>
|
||||
+ Into<usize>
|
||||
+ Into<f64>
|
||||
{
|
||||
fn plus (self, other: Self) -> Self;
|
||||
fn minus (self, other: Self) -> Self {
|
||||
if self >= other { self - other } else { 0.into() }
|
||||
}
|
||||
fn atomic (self) -> AtomicUsize {
|
||||
AtomicUsize::new(self.into())
|
||||
}
|
||||
fn zero () -> Self {
|
||||
0.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Drawable with dynamic dispatch.
|
||||
pub trait Draw<O: Out> {
|
||||
fn draw (&self, to: &mut O);
|
||||
}
|
||||
|
||||
/// Outputs combinator.
|
||||
pub trait Lay<O: Out>: Sized {}
|
||||
|
||||
/// Drawable area of display.
|
||||
pub trait Layout<O: Out> {
|
||||
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_w_min (&self, _t: XYWH<O::Unit>) -> O::Unit { 0.into() }
|
||||
fn layout_w_max (&self, to: XYWH<O::Unit>) -> O::Unit { to.w() }
|
||||
fn layout_w (&self, to: XYWH<O::Unit>) -> O::Unit { to.w().max(self.layout_w_min(to)).min(self.layout_w_max(to)) }
|
||||
fn layout_h_min (&self, _t: XYWH<O::Unit>) -> O::Unit { 0.into() }
|
||||
fn layout_h_max (&self, to: XYWH<O::Unit>) -> O::Unit { to.h() }
|
||||
fn layout_h (&self, to: XYWH<O::Unit>) -> O::Unit { to.h().max(self.layout_h_min(to)).min(self.layout_h_max(to)) }
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasContent<O: Out> {
|
||||
fn content (&self) -> impl Content<O>;
|
||||
}
|
||||
|
||||
// TODO DOCUMENTME
|
||||
pub trait Content<O: Out>: Draw<O> + Layout<O> {}
|
||||
|
||||
// Something that has an origin point (X, Y).
|
||||
pub trait HasXY<N: Coord> {
|
||||
fn x (&self) -> N;
|
||||
fn y (&self) -> N;
|
||||
fn xy (&self) -> XY<N> { XY(self.x(), self.y()) }
|
||||
}
|
||||
|
||||
// Something that has a size (W, H).
|
||||
pub trait HasWH<N: Coord> {
|
||||
fn w (&self) -> N;
|
||||
fn h (&self) -> N;
|
||||
fn wh (&self) -> WH<N> { WH(self.w(), self.h()) }
|
||||
}
|
||||
|
||||
// Something that has a 2D bounding box (X, Y, W, H).
|
||||
//
|
||||
// FIXME: The other way around?
|
||||
pub trait HasXYWH<N: Coord>: HasXY<N> + HasWH<N> {
|
||||
fn x2 (&self) -> N { self.x().plus(self.w()) }
|
||||
fn y2 (&self) -> N { self.y().plus(self.h()) }
|
||||
fn xywh (&self) -> XYWH<N> { XYWH(self.x(), self.y(), self.w(), self.h()) }
|
||||
fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
|
||||
if self.w() < w || self.h() < h {
|
||||
Err(format!("min {w}x{h}").into())
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Something that has a [Measure] of its rendered size.
|
||||
pub trait Measured<O: Out> {
|
||||
fn measure (&self) -> &Measure<O>;
|
||||
fn measure_width (&self) -> O::Unit { self.measure().w() }
|
||||
fn measure_height (&self) -> O::Unit { self.measure().h() }
|
||||
}
|
||||
|
||||
pub trait HasPerf {
|
||||
fn perf (&self) -> &PerfModel;
|
||||
}
|
||||
|
||||
pub trait HasColor { fn color (&self) -> ItemColor; }
|
||||
|
||||
pub trait BorderStyle: Content<TuiOut> + Copy {
|
||||
fn enabled (&self) -> bool;
|
||||
fn enclose (self, w: impl Content<TuiOut>) -> impl Content<TuiOut> {
|
||||
Bsp::b(Fill::XY(Border(self.enabled(), self)), w)
|
||||
}
|
||||
fn enclose2 (self, w: impl Content<TuiOut>) -> impl Content<TuiOut> {
|
||||
Bsp::b(Pad::XY(1, 1, Fill::XY(Border(self.enabled(), self))), w)
|
||||
}
|
||||
fn enclose_bg (self, w: impl Content<TuiOut>) -> impl Content<TuiOut> {
|
||||
Tui::bg(self.style().unwrap().bg.unwrap_or(Color::Reset),
|
||||
Bsp::b(Fill::XY(Border(self.enabled(), self)), w))
|
||||
}
|
||||
const NW: &'static str = "";
|
||||
const N: &'static str = "";
|
||||
const NE: &'static str = "";
|
||||
const E: &'static str = "";
|
||||
const SE: &'static str = "";
|
||||
const S: &'static str = "";
|
||||
const SW: &'static str = "";
|
||||
const W: &'static str = "";
|
||||
|
||||
const N0: &'static str = "";
|
||||
const S0: &'static str = "";
|
||||
const W0: &'static str = "";
|
||||
const E0: &'static str = "";
|
||||
|
||||
fn border_n (&self) -> &str { Self::N }
|
||||
fn border_s (&self) -> &str { Self::S }
|
||||
fn border_e (&self) -> &str { Self::E }
|
||||
fn border_w (&self) -> &str { Self::W }
|
||||
fn border_nw (&self) -> &str { Self::NW }
|
||||
fn border_ne (&self) -> &str { Self::NE }
|
||||
fn border_sw (&self) -> &str { Self::SW }
|
||||
fn border_se (&self) -> &str { Self::SE }
|
||||
#[inline] fn draw <'a> (
|
||||
&self, to: &mut TuiOut
|
||||
) -> Usually<()> {
|
||||
if self.enabled() {
|
||||
self.draw_horizontal(to, None)?;
|
||||
self.draw_vertical(to, None)?;
|
||||
self.draw_corners(to, None)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[inline] fn draw_horizontal (
|
||||
&self, to: &mut TuiOut, style: Option<Style>
|
||||
) -> Usually<XYWH<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_horizontal());
|
||||
let [x, x2, y, y2] = area.lrtb();
|
||||
for x in x..x2.saturating_sub(1) {
|
||||
to.blit(&Self::N, x, y, style);
|
||||
to.blit(&Self::S, x, y2.saturating_sub(1), style)
|
||||
}
|
||||
Ok(area)
|
||||
}
|
||||
#[inline] fn draw_vertical (
|
||||
&self, to: &mut TuiOut, style: Option<Style>
|
||||
) -> Usually<XYWH<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_vertical());
|
||||
let [x, x2, y, y2] = area.lrtb();
|
||||
let h = y2 - y;
|
||||
if h > 1 {
|
||||
for y in y..y2.saturating_sub(1) {
|
||||
to.blit(&Self::W, x, y, style);
|
||||
to.blit(&Self::E, x2.saturating_sub(1), y, style);
|
||||
}
|
||||
} else if h > 0 {
|
||||
to.blit(&Self::W0, x, y, style);
|
||||
to.blit(&Self::E0, x2.saturating_sub(1), y, style);
|
||||
}
|
||||
Ok(area)
|
||||
}
|
||||
#[inline] fn draw_corners (
|
||||
&self, to: &mut TuiOut, style: Option<Style>
|
||||
) -> Usually<XYWH<u16>> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_corners());
|
||||
let XYWH(x, y, width, height) = area;
|
||||
if width > 1 && height > 1 {
|
||||
to.blit(&Self::NW, x, y, style);
|
||||
to.blit(&Self::NE, x + width - 1, y, style);
|
||||
to.blit(&Self::SW, x, y + height - 1, style);
|
||||
to.blit(&Self::SE, x + width - 1, y + height - 1, style);
|
||||
}
|
||||
Ok(area)
|
||||
}
|
||||
#[inline] fn style (&self) -> Option<Style> { None }
|
||||
#[inline] fn style_horizontal (&self) -> Option<Style> { self.style() }
|
||||
#[inline] fn style_vertical (&self) -> Option<Style> { self.style() }
|
||||
#[inline] fn style_corners (&self) -> Option<Style> { self.style() }
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue