wip: final simplify

This commit is contained in:
facile pop culture reference 2026-07-05 18:46:22 +03:00
parent 90ebae0f26
commit 0b9e9c0696
21 changed files with 607 additions and 544 deletions

0
PAPER.qmd Normal file
View file

2
dizzle

@ -1 +1 @@
Subproject commit 0292cea24f5bfe71e0f8c6f4afe3cad105aa8ab8
Subproject commit f8e310b477afc1aab4701fdc52da474143d78a28

View file

@ -1,13 +1,28 @@
#!/usr/bin/env nix-shell
{pkgs?import<nixpkgs>{}}:let
stdenv = pkgs.clang19Stdenv;
name = "tengri";
nativeBuildInputs = [ pkgs.pkg-config pkgs.clang pkgs.libclang pkgs.mold pkgs.bacon ];
buildInputs = [ pkgs.libclang pkgs.jack2 ];
stdenv = pkgs.clang19Stdenv;
LIBCLANG_PATH = "${pkgs.libclang.lib.outPath}/lib";
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [];
in pkgs.mkShell.override {
inherit stdenv;
} {
inherit name nativeBuildInputs buildInputs LIBCLANG_PATH LD_LIBRARY_PATH;
inherit name LIBCLANG_PATH LD_LIBRARY_PATH;
buildInputs = [
pkgs.libclang
pkgs.jack2
];
nativeBuildInputs = [
pkgs.pkg-config
pkgs.clang
pkgs.libclang
pkgs.mold
pkgs.bacon
(pkgs.quarto.overrideAttrs (oldAttrs: { # https://github.com/NixOS/nixpkgs/issues/519484
postPatch = (oldAttrs.postPatch or "") + ''
substituteInPlace bin/quarto.js \
--replace-fail "syntax-highlighting" "highlight-style"
'';
}))
];
}

View file

@ -1,14 +1,30 @@
use crate::*;
pub use crate::space::*;
mod view;
pub use self::view::*;
mod thunk;
pub use self::thunk::*;
mod screen;
pub use self::screen::*;
/// Output target.
///
/// ```
/// use tengri::{*, draw::*};
///
/// struct TestOut<T: Screen<Unit = u16>>(T);
///
/// impl<T: Screen<Unit = u16>> Screen for TestOut {
/// type Unit = u16;
/// fn show <D: Draw<Self> + ?Sized> (&mut self, _: D) {
/// println!("placed");
/// ()
/// }
/// }
///
/// impl_draw!(|self: String, to: TestOut<u16>|{
/// to.area_mut().set_w(self.len() as u16);
/// });
/// ```
pub trait Screen: Xy<Self::Unit> + Wh<Self::Unit> + Send + Sync + Sized {
type Unit: Coord;
/// Render drawable in subarea specified by `area`
fn show <'t, T: Draw<Self>> (&mut self, content: T) -> Perhaps<XYWH<Self::Unit>>;
}
/// Implement the [Draw] trait for a particular drawable and [Screen].
///
@ -22,7 +38,7 @@ pub use self::screen::*;
#[macro_export] macro_rules! impl_draw ((|
$self:ident:$Self:ty, $to:ident:$To:ty
|$draw:block)=>{ impl Draw<$To> for $Self {
fn draw ($self, $to: &mut $To) -> Usually<XYWH<u16>> $draw
fn draw ($self, $to: &mut $To) -> Usually<XYWH<<$To as Screen>::Unit>> $draw
} });
/// Drawable that supports dynamic dispatch.
@ -48,29 +64,94 @@ pub use self::screen::*;
/// ```
pub trait Draw<S: Screen> {
fn draw (self, to: &mut S) -> Usually<XYWH<S::Unit>>;
fn layout (&self, area: XYWH<S::Unit>) -> Perhaps<XYWH<S::Unit>> {
Ok(Some(area))
}
}
impl<S: Screen> Draw<S> for () {
fn draw (self, __: &mut S) -> Usually<XYWH<S::Unit>> {
Ok(Default::default())
}
}
impl<S: Screen, D: Draw<S>> Draw<S> for &D {
fn draw (self, __: &mut S) -> Usually<XYWH<S::Unit>> {
todo!()
}
}
impl<S: Screen, D: Draw<S>> Draw<S> for Option<D> {
fn draw (self, __: &mut S) -> Usually<XYWH<S::Unit>> {
todo!()
}
}
impl<S: Screen, D: Draw<S>> Draw<S> for RwLock<D> {
fn draw (self, __: &mut S) -> Usually<XYWH<S::Unit>> {
todo!()
fn draw (self, to: &mut S) -> Usually<XYWH<S::Unit>> {
self.map(|it|it.draw(to)).transpose().map(Option::unwrap_or_default)
}
}
//impl<S: Screen, D: Draw<S>> Draw<S> for RwLock<D> {
//fn draw (self, __: &mut S) -> Usually<XYWH<S::Unit>> {
//todo!()
//}
//}
//impl<T: Screen, D: Draw<T>> Draw<T> for Arc<D> {
//fn draw (self, __: &mut T) -> Usually<XYWH<T::Unit>> {
//todo!()
//}
//}
/// Emit a [Draw]able.
///
/// Speculative. How to avoid conflicts with [Draw] proper?
pub trait View<T: Screen> {
fn view (&self) -> impl Draw<T>;
}
impl<T: Screen, V: View<T>> Draw<T> for &V {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
self.view().draw(to)
}
}
/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts.
pub struct Thunk<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>>(
pub F,
std::marker::PhantomData<T>
);
impl<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> Draw<T> for Thunk<T, F> {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
(self.0)(to)
}
}
/// Basic [Draw]able closure.
///
/// ```
/// # use tengri::{draw::*, term::*};
/// # fn test () -> impl tengri::draw::Draw<tengri::term::Tui> {
/// thunk(|to: &mut Tui|Ok(to.1))
/// # }
/// ```
pub const fn thunk <T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> (
draw: F
) -> Thunk<T, F> {
Thunk(draw, std::marker::PhantomData)
}
/// Only render when condition is true.
///
/// ```
/// # use tengri::{draw::*, term::*};
/// # fn test () -> impl tengri::draw::Draw<tengri::term::Tui> {
/// when(true, "Yes")
/// # }
/// ```
pub const fn when <T: Screen> (condition: bool, draw: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|if condition { draw.draw(to) } else { Ok(Default::default()) })
}
/// Render one thing if a condition is true and another false.
///
/// ```
/// # use tengri::{draw::*, term::*};
/// # fn test () -> impl tengri::draw::Draw<tengri::term::Tui> {
/// either(true, "Yes", "No")
/// # }
/// ```
pub const fn either <T: Screen> (condition: bool, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) })
}

View file

@ -1,27 +0,0 @@
use super::*;
/// Output target.
///
/// ```
/// use tengri::{*, draw::*};
///
/// struct TestOut<T: Screen<Unit = u16>>(T);
///
/// impl<T: Screen<Unit = u16>> Screen for TestOut {
/// type Unit = u16;
/// fn show <D: Draw<Self> + ?Sized> (&mut self, area: T, _: D) {
/// println!("placed: {area:?}");
/// ()
/// }
/// }
///
/// impl_draw!(|self: String, to: TestOut<u16>|{
/// to.area_mut().set_w(self.len() as u16);
/// });
/// ```
pub trait Screen: Space<Self::Unit> + Send + Sync + Sized {
type Unit: Coord;
/// Render drawable in subarea specified by `area`
fn show <'t, T: Draw<Self>> (&mut self, area: XYWH<Self::Unit>, content: T) ->
Usually<XYWH<Self::Unit>>;
}

View file

@ -1,51 +0,0 @@
use super::*;
/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts.
pub struct Thunk<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>>(
pub F,
std::marker::PhantomData<T>
);
impl<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> Draw<T> for Thunk<T, F> {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
(self.0)(to)
}
}
/// Basic [Draw]able closure.
///
/// ```
/// # use tengri::{draw::*, term::*};
/// # fn test () -> impl tengri::draw::Draw<tengri::term::Tui> {
/// thunk(|to: &mut Tui|Ok(to.1))
/// # }
/// ```
pub const fn thunk <T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> (
draw: F
) -> Thunk<T, F> {
Thunk(draw, std::marker::PhantomData)
}
/// Only render when condition is true.
///
/// ```
/// # use tengri::{draw::*, term::*};
/// # fn test () -> impl tengri::draw::Draw<tengri::term::Tui> {
/// when(true, "Yes")
/// # }
/// ```
pub const fn when <T: Screen> (condition: bool, draw: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|if condition { draw.draw(to) } else { Ok(Default::default()) })
}
/// Render one thing if a condition is true and another false.
///
/// ```
/// # use tengri::{draw::*, term::*};
/// # fn test () -> impl tengri::draw::Draw<tengri::term::Tui> {
/// either(true, "Yes", "No")
/// # }
/// ```
pub const fn either <T: Screen> (condition: bool, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) })
}

View file

@ -1,8 +0,0 @@
use super::*;
/// Emit a [Draw]able.
///
/// Speculative. How to avoid conflicts with [Draw] proper?
pub trait View<T: Screen> {
fn view (&self) -> impl Draw<T>;
}

View file

@ -195,7 +195,7 @@ pub fn eval_view_tui <'a, S> (
Some("text") => {
if let Some(src) = args?.src()? {
output.show(output.xywh(), &src)?
output.show(src)?
} else {
return Ok(None)
}
@ -204,7 +204,7 @@ pub fn eval_view_tui <'a, S> (
Some("fg") => {
let arg0 = arg0?.expect("fg: expected arg 0 (color)");
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color"));
output.show(output.xywh(), &fg(color, thunk(move|output: &mut Tui|{
output.show(fg(color, thunk(move|output: &mut Tui|{
state.interpret(output, &arg1)?;
// FIXME?: don't max out the used area?
Ok(output.area().into())
@ -214,7 +214,7 @@ pub fn eval_view_tui <'a, S> (
Some("bg") => {
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color"));
output.show(output.xywh(), &bg(color, thunk(move|output: &mut Tui|{
output.show(bg(color, thunk(move|output: &mut Tui|{
state.interpret(output, &arg1)?;
// FIXME?: don't max out the used area?
Ok(output.area().into())

87
src/layout.rs Normal file
View file

@ -0,0 +1,87 @@
use crate::*;
mod align; pub use self::align::*;
mod area; pub use self::area::*;
mod origin; pub use self::origin::*;
mod split; pub use self::split::*;
pub enum Layout<T: Screen, X: Into<Option<T::Unit>>, I: Draw<T>> {
__(PhantomData<T>),
MinW(X, I),
MinH(X, I),
MinWH(X, X, I),
MaxW(X, I),
MaxH(X, I),
MaxWH(X, X, I),
ExactW(X, I),
ExactH(X, I),
ExactWH(X, X, I),
ClipW(X, I),
ClipH(X, I),
ClipWH(X, X, I),
}
impl<
T: Screen,
X: Into<Option<T::Unit>>,
I: Draw<T>
> Draw<T> for Layout<T, X, I> {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
use Layout::*;
match self {
__(_) => unreachable!(),
MinW(x, it) => {
let x = x.into();
it.draw(to)
},
MinH(y, it) => {
let y = y.into();
it.draw(to)
},
MinWH(x, y, it) => {
let x = x.into();
let y = y.into();
it.draw(to)
},
MaxW(x, it) => {
let x = x.into();
it.draw(to)
},
MaxH(y, it) => {
let y = y.into();
it.draw(to)
},
MaxWH(x, y, it) => {
let x = x.into();
let y = y.into();
it.draw(to)
},
ExactW(x, it) => {
let x = x.into();
it.draw(to)
},
ExactH(y, it) => {
let y = y.into();
it.draw(to)
},
ExactWH(x, y, it) => {
let x = x.into();
let y = y.into();
it.draw(to)
},
ClipW(x, it) => {
let x = x.into();
it.draw(to)
},
ClipH(y, it) => {
let y = y.into();
it.draw(to)
},
ClipWH(x, y, it) => {
let x = x.into();
let y = y.into();
it.draw(to)
},
}
}
}

13
src/layout/align.rs Normal file
View file

@ -0,0 +1,13 @@
use crate::*;
pub struct Align<T>(Option<Origin>, T);
/// ```
/// use tengri::*;
/// let _ = align(None, "unaligned");
/// let _ = align(Origin::SE, "southeast");
/// let _ = align(Some(Origin::SE), "southeast");
/// ```
pub fn align <S: Screen, T: Draw<S>, U: Into<Option<Origin>>> (origin: U, it: T) -> Align<T> {
Align(origin.into(), it)
}

15
src/layout/area.rs Normal file
View file

@ -0,0 +1,15 @@
use crate::*;
pub struct Area<S: Screen, T: Draw<S>>(Option<XYWH<S::Unit>>, T);
/// ```
/// use tengri::*;
/// let _ = area(None, "unareaed");
/// let _ = area([1, 2, 3, 4], "southeast");
/// let _ = area(Some([1, 2, 3, 4]), "southeast");
/// ```
pub fn area <S: Screen, T: Draw<S>, U: Into<Option<XYWH<S::Unit>>>> (
origin: U, it: T
) -> Area<S, T> {
Area(origin.into(), it)
}

23
src/layout/origin.rs Normal file
View file

@ -0,0 +1,23 @@
use super::*;
/// Where is [0, 0] located?
///
/// ```
/// use tengri::draw::Origin;
/// let _ = Origin::NW.align(());
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Debug, Copy, Clone, Default)] pub enum Origin {
#[default] C, X, Y, NW, N, NE, E, SE, S, SW, W
}
/// Something that has `[0, 0]` at a particular point.
pub trait HasOrigin {
fn origin (&self) -> Origin;
}
impl<T: AsRef<Origin>> HasOrigin for T {
fn origin (&self) -> Origin {
*self.as_ref()
}
}

View file

@ -50,16 +50,16 @@ impl Split {
thunk(move|to: &mut T|{
let (area_a, area_b) = to.xywh().split_half(self);
let (origin_a, origin_b) = self.origins();
let a = origin_a.align(a);
let b = origin_b.align(b);
let a = align(origin_a, a);
let b = align(origin_b, b);
match self {
Self::Below => {
to.show(area_b, &b);
to.show(area_b, &a);
to.show(area(area_b, b))?;
to.show(area(area_b, a))?;
},
_ => {
to.show(area_a, &a);
to.show(area_a, &b);
to.show(area(area_a, a))?;
to.show(area(area_b, b))?;
}
}
Ok(to.xywh()) // FIXME: compute and return actually used area

View file

@ -25,30 +25,32 @@ pub(crate) use ::{
std::ops::{Add, Sub, Mul, Div},
std::sync::{Arc, RwLock},
std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::*},
std::error::Error,
std::marker::PhantomData
};
#[cfg(feature = "lang")]
pub use ::dizzle::{Usually, Perhaps, impl_default};
/// DSL builtins.
#[cfg(feature = "lang")] pub mod eval;
/// Temporal dimension.
#[cfg(feature = "time")] pub mod time;
/// Circuit breaker for main loop.
#[cfg(feature = "play")] pub mod exit;
/// Thread management.
#[cfg(feature = "play")] pub mod task;
/// Integration with JACK audio backend.
#[cfg(feature = "sing")] pub mod sing;
/// Generic drawing utilities.
#[cfg(feature = "draw")] pub mod draw;
/// Spatial dimension.
#[cfg(feature = "draw")] pub mod space;
/// Color handling.
#[cfg(feature = "draw")] pub mod color;
/// Text handling.
#[cfg(feature = "text")] pub mod text;
/// Terminal output.
#[cfg(feature = "term")] pub mod term;
macro_rules! features {
($($feature:literal: [ $($module:ident),* ]),*) => {
$(
$(
#[cfg(feature = $feature)] mod $module;
#[cfg(feature = $feature)] pub use $module::*;
)*
)*
}
}
#[cfg(feature = "lang")] pub use ::dizzle::{Usually, Perhaps, impl_default};
features! {
"lang": [ eval ],
"time": [ time ],
"play": [ exit, task ],
"sing": [ sing ],
"draw": [ draw, space, layout, color ],
"text": [ text ],
"term": [ term ]
}
/// Define a trait an implement it for various mutation-enabled wrapper types. */
#[macro_export] macro_rules! flex_trait_mut (

View file

@ -1,195 +1,7 @@
use crate::{*, draw::*};
use crate::*;
#[cfg(test)] use proptest_derive::Arbitrary;
mod coord;
pub use self::coord::*;
mod xywh;
pub use self::xywh::*;
mod origin;
pub use self::origin::*;
mod split;
pub use self::split::*;
mod iter;
pub use self::iter::*;
mod size;
pub use self::size::*;
/// Horizontal axis.
pub trait X<N: Coord> {
fn x (&self) -> N;
fn w (&self) -> N { N::zero() }
fn w_min (&self) -> N { self.w() }
fn w_max (&self) -> N { self.w() }
fn iter_x (&self) -> impl Iterator<Item = N> where Self: HasOrigin {
self.x_west()..self.x_east()
}
fn x_west (&self) -> N where Self: HasOrigin {
use Origin::*;
let w = self.w();
let a = self.origin();
let d = match a { NW|W|SW => 0.into(), N|X|C|Y|S => w/2.into(), NE|E|SE => w };
self.x().minus(d)
}
fn x_east (&self) -> N where Self: HasOrigin {
use Origin::*;
let w = self.w();
let a = self.origin();
let d = match a { NW|W|SW => w, N|X|C|Y|S => w/2.into(), NE|E|SE => 0.into() };
self.x().plus(d)
}
fn x_center (&self) -> N where Self: HasOrigin {
todo!()
}
}
pub const fn x_push <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn x_pull <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn w_max <T: Screen> (w: Option<T::Unit>, a: impl Draw<T>) -> impl Draw<T> {
wh_max(w, None, a)
}
pub const fn w_min <T: Screen> (w: Option<T::Unit>, a: impl Draw<T>) -> impl Draw<T> {
wh_min(w, None, a)
}
pub const fn w_exact <T: Screen> (w: T::Unit, c: impl Draw<T>) -> impl Draw<T> {
wh_exact(Some(w), None, c)
}
/// Shrink drawing area symmetrically.
///
/// ```
/// let padded = tengri::space::w_pad(3, "Hello");
/// ```
pub const fn w_pad <T: Screen> (x: T::Unit, draw: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|draw.draw(todo!()))
}
pub trait Y<N: Coord> {
fn y (&self) -> N;
fn h (&self) -> N { N::zero() }
fn h_min (&self) -> N { self.h() }
fn h_max (&self) -> N { self.h() }
fn iter_y (&self) -> impl Iterator<Item = N> where Self: HasOrigin {
self.y_north()..self.y_south()
}
fn y_north (&self) -> N where Self: HasOrigin {
let a = self.origin();
let h = self.h();
use Origin::*;
let d = match a { NW|N|NE => 0.into(), W|X|C|Y|E => h/2.into(), SW|S|SE => h };
self.y().minus(d)
}
fn y_south (&self) -> N where Self: HasOrigin {
let a = self.origin();
let h = self.h();
use Origin::*;
let d = match a { NW|N|NE => h, W|X|C|Y|E => h/2.into(), SW|S|SE => 0.into() };
self.y().plus(d)
}
fn y_center (&self) -> N where Self: HasOrigin {
todo!()
}
}
pub const fn y_push <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn y_pull <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn h_max <T: Screen> (h: Option<T::Unit>, a: impl Draw<T>) -> impl Draw<T> {
wh_max(None, h, a)
}
pub const fn h_min <T: Screen> (h: Option<T::Unit>, a: impl Draw<T>) -> impl Draw<T> {
wh_min(None, h, a)
}
pub const fn h_exact <T: Screen> (h: T::Unit, c: impl Draw<T>) -> impl Draw<T> {
wh_exact(None, Some(h), c)
}
/// Shrink drawing area symmetrically.
///
/// ```
/// let padded = tengri::space::w_pad(3, "Hello");
/// ```
pub const fn h_pad <T: Screen> (x: T::Unit, draw: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|draw.draw(todo!()))
}
pub trait Space<N: Coord>: X<N> + Y<N> {
fn xywh (&self) -> XYWH<N> { XYWH(self.x(), self.y(), self.w(), self.h()) }
// FIXME: factor origin
fn lrtb (&self) -> [N;4] { [self.x(), self.y(), self.x()+self.w(), self.y()+self.h()] }
}
impl<N: Coord, T: X<N> + Y<N>> Space<N> for T {}
pub const fn xy_push <T: Screen> (x: T::Unit, y: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn xy_pull <T: Screen> (x: T::Unit, y: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
/// Shrink drawing area symmetrically.
///
/// ```
/// let padded = tengri::WH(3, 5).pad("Hello");
/// ```
pub const fn wh_pad <T: Screen> (w: T::Unit, h: T::Unit, draw: impl Draw<T>)
-> impl Draw<T>
{
thunk(move|to: &mut T|draw.draw(todo!()))
}
/// Only draw content if area is above a certain size.
///
/// ```
/// let min = tengri::space::wh_min(3, 5, "Hello"); // 5x5
/// ```
pub const fn wh_min <T: Screen> (w: Option<T::Unit>, h: Option<T::Unit>, draw: impl Draw<T>)
-> impl Draw<T>
{
thunk(move|to: &mut T|draw.draw(todo!()))
}
/// Set the maximum width and/or height of the content.
///
/// ```
/// let max = tengri::space::wh_max(Some(3), Some(5), "Hello");
/// ```
pub const fn wh_max <T: Screen> (w: Option<T::Unit>, h: Option<T::Unit>, draw: impl Draw<T>)
-> impl Draw<T>
{
thunk(move|_to: &mut T|draw.draw(todo!()))
}
/// Set the maximum width and/or height of the content.
///
/// ```
/// let exact = tengri::space::wh_exact(Some(3), Some(5), "Hello");
/// ```
pub const fn wh_exact <T: Screen> (w: Option<T::Unit>, h: Option<T::Unit>, draw: impl Draw<T>)
-> impl Draw<T>
{
thunk(move|_to: &mut T|draw.draw(todo!()))
}
/// Limit size of drawing area
/// ```
/// let clipped = tengri::space::wh_clip(Some(3), Some(5), "Hello");
/// ```
pub const fn wh_clip <T: Screen> (
w: Option<T::Unit>, h: Option<T::Unit>, draw: impl Draw<T>
) -> impl Draw<T> {
thunk(move|_to: &mut T|draw.draw(todo!()))
}
pub const fn wh_full <T: Screen> (a: impl Draw<T>) -> impl Draw<T> { a }
pub const fn w_full <T: Screen> (a: impl Draw<T>) -> impl Draw<T> { a }
pub const fn h_full <T: Screen> (a: impl Draw<T>) -> impl Draw<T> { a }
mod coord; pub use self::coord::*;
mod iter; pub use self::iter::*;
mod sizer; pub use self::sizer::*;
mod xywh; pub use self::xywh::*;

View file

@ -1,100 +0,0 @@
use super::*;
pub const fn origin_c <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::C, a)
}
pub const fn origin_x <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::X, a)
}
pub const fn origin_y <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::Y, a)
}
pub const fn origin_nw <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::NW, a)
}
pub const fn origin_n <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::N, a)
}
pub const fn origin_ne <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::NE, a)
}
pub const fn origin_w <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::W, a)
}
pub const fn origin_e <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::E, a)
}
pub const fn origin_sw <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::SW, a)
}
pub const fn origin_s <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::S, a)
}
pub const fn origin_se <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::SE, a)
}
/// Something that has `[0, 0]` at a particular point.
pub trait HasOrigin {
fn origin (&self) -> Origin;
}
impl<T: AsRef<Origin>> HasOrigin for T {
fn origin (&self) -> Origin {
*self.as_ref()
}
}
pub struct Anchor<T>(Origin, T);
impl<T> AsRef<Origin> for Anchor<T> {
fn as_ref (&self) -> &Origin {
&self.0
}
}
impl<T: Screen, U: Draw<T>> Draw<T> for Anchor<U> {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
todo!()
}
}
/// Where is [0, 0] located?
///
/// ```
/// use tengri::draw::Origin;
/// let _ = Origin::NW.align(());
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Debug, Copy, Clone, Default)] pub enum Origin {
#[default] C, X, Y, NW, N, NE, E, SE, S, SW, W
}
impl Origin {
pub fn align <T: Screen> (&self, a: impl Draw<T>) -> impl Draw<T> {
align(*self, a)
}
}
/// ```
/// use tengri::draw::{align, Origin::*};
/// let _ = align(NW, "test");
/// let _ = align(SE, "test");
/// ```
pub fn align <T: Screen> (origin: Origin, a: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T| { todo!() })
}
pub fn align_n <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
align(Origin::N, a)
}

View file

@ -1,32 +1,25 @@
use super::*;
use std::sync::atomic::Ordering;
/// Uses [AtomicUsize] to measure size during\
/// rendering (which is normally read-only).
#[derive(Default, Debug, Clone)]
pub struct Size(
pub struct Sizer(
/// Width
pub Arc<AtomicUsize>,
/// Height
pub Arc<AtomicUsize>,
);
impl PartialEq for Size {
fn eq (&self, _: &Self) -> bool {
todo!()
}
impl Xy<u16> for Sizer {
fn x (&self) -> u16 { self.0.load(Ordering::Relaxed) as u16 }
fn y (&self) -> u16 { self.1.load(Ordering::Relaxed) as u16 }
}
impl Wide<u16> for Sizer { fn w (&self) -> u16 { self.0.load(Relaxed) as u16 } }
impl Tall<u16> for Sizer { fn h (&self) -> u16 { self.1.load(Relaxed) as u16 } }
impl PartialEq for Sizer { fn eq (&self, _: &Self) -> bool { todo!() } }
impl X<u16> for Size {
fn x (&self) -> u16 { 0 }
fn w (&self) -> u16 { self.0.load(Relaxed) as u16 }
}
impl Y<u16> for Size {
fn y (&self) -> u16 { 0 }
fn h (&self) -> u16 { self.1.load(Relaxed) as u16 }
}
impl Size {
impl Sizer {
pub const fn of <T: Screen> (&self, of: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|{
let area = of.draw(to)?;

View file

@ -1,4 +1,76 @@
use super::*;
use Origin::*;
use Split::*;
pub trait Xy<N: Coord> {
fn x (&self) -> N;
fn y (&self) -> N;
}
pub trait Wh<N: Coord>: Wide<N> + Tall<N> {
fn wh (&self) -> [N;2];
}
pub trait Xywh<N: Coord>: Xy<N> + Wh<N> {
fn xywh (&self) -> XYWH<N> {
XYWH(self.x(), self.y(), self.w(), self.h())
}
}
pub trait Lrtb<N: Coord> {
// FIXME: factor origin
fn lrtb (&self) -> [N;4] {
[self.x(), self.y(), self.x()+self.w(), self.y()+self.h()]
}
}
pub trait Wide<N: Coord> {
fn w (&self) -> N { N::zero() }
fn w_min (&self) -> N { self.w() }
fn w_max (&self) -> N { self.w() }
fn iter_x (&self) -> impl Iterator<Item = N> where Self: HasOrigin {
self.x_west()..self.x_east()
}
fn x_west (&self) -> N where Self: HasOrigin {
let w = self.w();
let a = self.origin();
let d = match a { NW|W|SW => 0.into(), N|X|C|Y|S => w/2.into(), NE|E|SE => w };
self.x().minus(d)
}
fn x_east (&self) -> N where Self: HasOrigin {
let w = self.w();
let a = self.origin();
let d = match a { NW|W|SW => w, N|X|C|Y|S => w/2.into(), NE|E|SE => 0.into() };
self.x().plus(d)
}
fn x_center (&self) -> N where Self: HasOrigin {
todo!()
}
}
pub trait Tall<N: Coord> {
fn h (&self) -> N { N::zero() }
fn h_min (&self) -> N { self.h() }
fn h_max (&self) -> N { self.h() }
fn iter_y (&self) -> impl Iterator<Item = N> where Self: HasOrigin {
self.y_north()..self.y_south()
}
fn y_north (&self) -> N where Self: HasOrigin {
let a = self.origin();
let h = self.h();
let d = match a { NW|N|NE => 0.into(), W|X|C|Y|E => h/2.into(), SW|S|SE => h };
self.y().minus(d)
}
fn y_south (&self) -> N where Self: HasOrigin {
let a = self.origin();
let h = self.h();
let d = match a { NW|N|NE => h, W|X|C|Y|E => h/2.into(), SW|S|SE => 0.into() };
self.y().plus(d)
}
fn y_center (&self) -> N where Self: HasOrigin {
todo!()
}
}
/// Point with size.
///
@ -13,21 +85,14 @@ use super::*;
#[cfg_attr(test, derive(Arbitrary))] #[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct XYWH<N: Coord>(pub N, pub N, pub N, pub N);
impl From<&ratatui::prelude::Rect> for XYWH<u16> {
fn from (rect: &ratatui::prelude::Rect) -> Self {
Self(rect.x, rect.y, rect.width, rect.height)
}
}
impl<N: Coord> X<N> for XYWH<N> {
impl<N: Coord> Xy<N> for XYWH<N> {
fn x (&self) -> N { self.0 }
fn w (&self) -> N { self.2 }
fn y (&self) -> N { self.1 }
}
impl<N: Coord> Y<N> for XYWH<N> {
fn y (&self) -> N { self.0 }
fn h (&self) -> N { self.2 }
}
impl<N: Coord> Wide<N> for XYWH<N> { fn w (&self) -> N { self.2 } }
impl<N: Coord> Tall<N> for XYWH<N> { fn h (&self) -> N { self.3 } }
impl<N: Coord> XYWH<N> {
@ -67,7 +132,6 @@ impl<N: Coord> XYWH<N> {
}
pub fn split_half (&self, direction: &Split) -> (Self, Self) {
use Split::*;
let XYWH(x, y, w, h) = self.xywh();
match direction {
South => (XYWH(x, y, w, h - h / 2.into()), XYWH(x, y + h / 2.into(), w, h / 2.into())),
@ -79,3 +143,144 @@ impl<N: Coord> XYWH<N> {
}
}
impl From<&ratatui::prelude::Rect> for XYWH<u16> {
fn from (rect: &ratatui::prelude::Rect) -> Self {
Self(rect.x, rect.y, rect.width, rect.height)
}
}
impl<N: Coord, T: Wide<N> + Tall<N>> Wh<N> for T {
fn wh (&self) -> [N;2] {
[self.w(), self.h()]
}
}
impl<N: Coord, T: Xy<N> + Wh<N>> Xywh<N> for T {}
/// Shrink drawing area symmetrically.
///
/// ```
/// let padded = tengri::wh_pad("Hello");
/// ```
pub const fn wh_pad <T: Screen> (w: T::Unit, h: T::Unit, it: impl Draw<T>)
-> impl Draw<T>
{
thunk(move|to: &mut T|it.draw(todo!()))
}
/// Only draw content if area is above a certain size.
///
/// ```
/// let min = tengri::wh_min(3, 5, "Hello"); // 5x5
/// ```
pub const fn wh_min <T: Screen> (w: Option<T::Unit>, h: Option<T::Unit>, it: impl Draw<T>)
-> impl Draw<T>
{
thunk(move|to: &mut T|it.draw(todo!()))
}
/// Set the maximum width and/or height of the content.
///
/// ```
/// let max = tengri::wh_max(Some(3), Some(5), "Hello");
/// ```
pub const fn wh_max <T: Screen> (w: Option<T::Unit>, h: Option<T::Unit>, it: impl Draw<T>)
-> impl Draw<T>
{
thunk(move|_to: &mut T|it.draw(todo!()))
}
/// Set the maximum width and/or height of the content.
///
/// ```
/// let exact = tengri::wh_exact(Some(3), Some(5), "Hello");
/// ```
pub const fn wh_exact <T: Screen> (w: Option<T::Unit>, h: Option<T::Unit>, it: impl Draw<T>)
-> impl Draw<T>
{
thunk(move|_to: &mut T|it.draw(todo!()))
}
/// Limit size of drawing area
/// ```
/// let clipped = tengri::wh_clip(Some(3), Some(5), "Hello");
/// ```
pub const fn wh_clip <T: Screen> (
w: Option<T::Unit>, h: Option<T::Unit>, it: impl Draw<T>
) -> impl Draw<T> {
thunk(move|_to: &mut T|it.draw(todo!()))
}
pub const fn wh_full <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
todo!();
a
}
pub const fn w_full <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
todo!();
a
}
pub const fn h_full <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
todo!();
a
}
pub const fn h_max <T: Screen> (h: Option<T::Unit>, a: impl Draw<T>) -> impl Draw<T> {
wh_max(None, h, a)
}
pub const fn h_min <T: Screen> (h: Option<T::Unit>, a: impl Draw<T>) -> impl Draw<T> {
wh_min(None, h, a)
}
pub const fn h_exact <T: Screen> (h: T::Unit, c: impl Draw<T>) -> impl Draw<T> {
wh_exact(None, Some(h), c)
}
pub const fn w_max <T: Screen> (w: Option<T::Unit>, a: impl Draw<T>) -> impl Draw<T> {
wh_max(w, None, a)
}
pub const fn w_min <T: Screen> (w: Option<T::Unit>, a: impl Draw<T>) -> impl Draw<T> {
wh_min(w, None, a)
}
pub const fn w_exact <T: Screen> (w: T::Unit, c: impl Draw<T>) -> impl Draw<T> {
wh_exact(Some(w), None, c)
}
/// Shrink drawing area symmetrically.
///
/// ```
/// let padded = tengri::space::w_pad(3, "Hello");
/// ```
pub const fn w_pad <T: Screen> (x: T::Unit, draw: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|draw.draw(todo!()))
}
/// Shrink drawing area symmetrically.
///
/// ```
/// let padded = tengri::space::w_pad(3, "Hello");
/// ```
pub const fn h_pad <T: Screen> (x: T::Unit, draw: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|draw.draw(todo!()))
}
pub const fn xy_push <T: Screen> (x: T::Unit, y: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn xy_pull <T: Screen> (x: T::Unit, y: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn x_push <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn x_pull <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn y_push <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn y_pull <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}

View file

@ -29,7 +29,27 @@ use ::{
/// Implement standard [main] entrypoint for TUI apps.
#[macro_export] macro_rules! tui_main {
($state:expr) => { pub fn main () -> Usually<()> { tui_run_main($state) } }
($state:expr) => {
pub fn main () -> Usually<()> { tui_run_main($state) }
}
}
/// Enable TUI keyboard input for main state struct.
#[macro_export] macro_rules! tui_keys {
(|$self:ident:$State:ty,$input:ident|$body:block) => {
impl Apply<TuiEvent, Usually<()>> for $State {
fn apply (&mut $self, $input: &TuiEvent) -> Usually<()> $body
}
};
}
/// Enable TUI output for state struct.
#[macro_export] macro_rules! tui_view {
(|$self:ident: $State:ty|$body:block) => {
impl View<Tui> for $State {
fn view (&$self) -> impl Draw<Tui> $body
}
}
}
pub fn tui_run_main <T> (state: Arc<RwLock<T>>) -> Usually<()> where
@ -63,15 +83,6 @@ pub fn tui_setup_panic () {
}));
}
/// Enable TUI keyboard input for main state struct.
#[macro_export] macro_rules! tui_keys {
(|$self:ident:$State:ty,$input:ident|$body:block) => {
impl Apply<TuiEvent, Usually<()>> for $State {
fn apply (&mut $self, $input: &TuiEvent) -> Usually<()> $body
}
};
}
/// Spawn the TUI input and output threadsl.
pub fn tui_io <
T: View<Tui> + Apply<TuiEvent, Usually<()>> + Send + Sync + 'static,
@ -117,15 +128,6 @@ pub fn tui_input <T: Apply<TuiEvent, Usually<()>> + Send + Sync + 'static> (
})
}
/// Enable TUI output for state struct.
#[macro_export] macro_rules! tui_view {
(|$self:ident: $State:ty|$body:block) => {
impl View<Tui> for $State {
fn view (&$self) -> impl Draw<Tui> $body
}
}
}
/// Spawn the TUI output thread which writes colored characters to the terminal.
///
/// ```
@ -192,37 +194,31 @@ impl Tui {
impl Screen for Tui {
type Unit = u16;
/// Render drawable in subarea specified by `area`
fn show <'t, T: Draw<Self>> (
&mut self, area: XYWH<u16>, content: T
) -> Usually<XYWH<u16>> {
fn show <'t, T: Draw<Self>> (&mut self, content: T) -> Perhaps<XYWH<u16>> {
let previous_area = self.1;
if let Some(area) = content.layout(self.1)? {
self.1 = area;
let result_area = content.draw(self);
if let Some(result_area) = content.draw(self)? {
self.1 = previous_area;
result_area
} else {
Ok(None)
}
} else {
Ok(None)
}
}
}
impl Deref for Tui { type Target = Buffer; fn deref (&self) -> &Buffer { &self.0 } }
impl DerefMut for Tui { fn deref_mut (&mut self) -> &mut Buffer { &mut self.0 } }
impl AsMut<Buffer> for Tui {
fn as_mut (&mut self) -> &mut Buffer {
&mut self.0
}
}
impl AsMut<Buffer> for Tui { fn as_mut (&mut self) -> &mut Buffer { &mut self.0 } }
impl Wide<u16> for Tui { fn w (&self) -> u16 { self.1.2 } }
impl Tall<u16> for Tui { fn h (&self) -> u16 { self.1.3 } }
impl HasOrigin for Tui { fn origin (&self) -> Origin { Origin::NW } }
impl X<u16> for Tui {
impl Xy<u16> for Tui {
fn x (&self) -> u16 { self.1.0 }
fn w (&self) -> u16 { self.1.2 }
}
impl Y<u16> for Tui {
fn y (&self) -> u16 { self.1.1 }
fn h (&self) -> u16 { self.1.3 }
}
impl Tui {
@ -520,33 +516,9 @@ pub fn catcher <T: Draw<Tui>> (result: Usually<T>) -> impl Draw<Tui> {
})
}
/// TUI buffer sized by `usize` instead of `u16`.
#[derive(Default)] pub struct BigBuffer {
pub width: usize,
pub height: usize,
pub content: Vec<Cell>
}
impl_from!(BigBuffer: |size:(usize, usize)| Self::new(size.0, size.1));
impl_debug!(BigBuffer |self, f| { write!(f, "[BB {}x{} ({})]", self.width, self.height, self.content.len()) });
impl BigBuffer {
pub fn new (width: usize, height: usize) -> Self {
Self { width, height, content: vec![Cell::default(); width*height] }
}
pub fn get (&self, x: usize, y: usize) -> Option<&Cell> {
let i = self.index_of(x, y);
self.content.get(i)
}
pub fn get_mut (&mut self, x: usize, y: usize) -> Option<&mut Cell> {
let i = self.index_of(x, y);
self.content.get_mut(i)
}
pub fn index_of (&self, x: usize, y: usize) -> usize {
y * self.width + x
}
}
mod event; pub use self::event::*;
mod keys; pub use self::keys::*;
mod buffer; pub use self::buffer::*;
use self::colors::*; mod colors {
use ratatui::prelude::Color;
@ -572,9 +544,3 @@ use self::colors::*; mod colors {
pub const fn tui_title_fg (f: bool) -> Color { if f { tui_ti1() } else { tui_ti2() } }
pub const fn tui_yellow () -> Color { Color::Rgb(255,255,0) }
}
#[cfg(feature = "term")] pub mod event;
#[cfg(feature = "term")] pub use self::event::*;
#[cfg(feature = "term")] pub mod keys;
#[cfg(feature = "term")] pub use self::keys::*;

31
src/term/buffer.rs Normal file
View file

@ -0,0 +1,31 @@
use crate::*;
use crate::{*, lang::*, draw::*, task::*, exit::*};
use ::ratatui::buffer::Cell;
/// TUI buffer sized by `usize` instead of `u16`.
#[derive(Default)] pub struct BigBuffer {
pub width: usize,
pub height: usize,
pub content: Vec<Cell>
}
impl_from!(BigBuffer: |size:(usize, usize)| Self::new(size.0, size.1));
impl_debug!(BigBuffer |self, f| { write!(f, "[BB {}x{} ({})]", self.width, self.height, self.content.len()) });
impl BigBuffer {
pub fn new (width: usize, height: usize) -> Self {
Self { width, height, content: vec![Cell::default(); width*height] }
}
pub fn get (&self, x: usize, y: usize) -> Option<&Cell> {
let i = self.index_of(x, y);
self.content.get(i)
}
pub fn get_mut (&mut self, x: usize, y: usize) -> Option<&mut Cell> {
let i = self.index_of(x, y);
self.content.get_mut(i)
}
pub fn index_of (&self, x: usize, y: usize) -> usize {
y * self.width + x
}
}

View file

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