This commit is contained in:
same mf who else 2026-03-21 17:35:31 +02:00
parent 5d627f7669
commit eb899906f9
8 changed files with 627 additions and 728 deletions

View file

@ -1,6 +1,10 @@
use ::ratatui::style::Color;
use crate::lang::impl_from;
pub(crate) use ::palette::{Okhsl, Srgb, OklabHue, okhsl::UniformOkhsl};
pub(crate) use ::palette::{
Okhsl, Srgb, OklabHue, Mix, okhsl::UniformOkhsl,
convert::{FromColor, FromColorUnclamped}
};
use rand::distributions::uniform::UniformSampler;
pub fn rgb (r: u8, g: u8, b: u8) -> ItemColor { todo!(); }
@ -26,13 +30,17 @@ pub trait HasColor { fn color (&self) -> ItemColor; }
}
}
pub struct ItemColor {}
impl_from!(ItemColor: |rgb: Color| Self { rgb, okhsl: rgb_to_okhsl(rgb) });
impl_from!(ItemColor: |okhsl: Okhsl<f32>| Self { okhsl, rgb: okhsl_to_rgb(okhsl) });
#[derive(Copy, Clone, Debug, Default)]
pub struct ItemColor {
term: Color,
okhsl: Okhsl<f32>
}
impl_from!(ItemColor: |term: Color| Self { term, okhsl: rgb_to_okhsl(term) });
impl_from!(ItemColor: |okhsl: Okhsl<f32>| Self { okhsl, term: okhsl_to_rgb(okhsl) });
// A single color within item theme parameters, in OKHSL and RGB representations.
impl ItemColor {
#[cfg(feature = "tui")] pub const fn from_tui (rgb: Color) -> Self {
Self { rgb, okhsl: Okhsl::new_const(OklabHue::new(0.0), 0.0, 0.0) }
#[cfg(feature = "term")] pub const fn from_tui (term: Color) -> Self {
Self { term, okhsl: Okhsl::new_const(OklabHue::new(0.0), 0.0, 0.0) }
}
pub fn random () -> Self {
let mut rng = ::rand::thread_rng();
@ -67,8 +75,8 @@ pub struct ItemTheme {
impl_from!(ItemTheme: |base: ItemColor| Self::from_item_color(base));
impl_from!(ItemTheme: |base: Color| Self::from_tui_color(base));
impl ItemTheme {
#[cfg(feature = "tui")] pub const G: [Self;256] = {
let mut builder = konst::array::ArrayBuilder::new();
#[cfg(feature = "term")] pub const G: [Self;256] = {
let mut builder = dizzle::konst::array::ArrayBuilder::new();
while !builder.is_full() {
let index = builder.len() as u8;
let light = (index as f64 * 1.15) as u8;
@ -96,7 +104,7 @@ impl ItemTheme {
pub const G00: Self = {
let color: ItemColor = ItemColor {
okhsl: Okhsl { hue: OklabHue::new(0.0), lightness: 0.0, saturation: 0.0 },
rgb: Color::Rgb(0, 0, 0)
term: Color::Rgb(0, 0, 0)
};
Self {
base: color,
@ -108,7 +116,7 @@ impl ItemTheme {
darkest: color,
}
};
#[cfg(feature = "tui")] pub fn from_tui_color (base: Color) -> Self {
#[cfg(feature = "term")] pub fn from_tui_color (base: Color) -> Self {
Self::from_item_color(ItemColor::from_tui(base))
}
pub fn from_item_color (base: ItemColor) -> Self {

View file

@ -1,4 +1,4 @@
use crate::{*, lang::*, color::*};
use crate::{*, lang::*, color::*, space::*};
/// Drawable that supports dynamic dispatch.
///
@ -8,7 +8,7 @@ use crate::{*, lang::*, color::*};
/// impl Screen for TestScreen { type Unit = u16; }
/// struct TestWidget(bool);
/// impl Draw<TestScreen> for TestWidget {
/// fn draw (&self, screen: &mut T) -> Usually<WH<u16>> {
/// fn draw (&self, screen: &mut T) -> Usually<XYWH<u16>> {
/// screen.0 |= self.0;
/// }
/// }
@ -18,45 +18,45 @@ use crate::{*, lang::*, color::*};
/// TestWidget(false).draw(&mut screen);
/// ```
pub trait Draw<T: Screen> {
fn draw (&self, to: &mut T) -> Usually<WH<T::Unit>>;
fn draw (&self, to: &mut T) -> Usually<XYWH<T::Unit>>;
}
impl<T: Screen> Draw<T> for () {
fn draw (&self, __: &mut T) -> Usually<WH<T::Unit>> {
fn draw (&self, __: &mut T) -> Usually<XYWH<T::Unit>> {
Ok(Default::default())
}
}
impl<T: Screen, D: Draw<T>> Draw<T> for &D {
fn draw (&self, to: &mut T) -> Usually<WH<T::Unit>> {
fn draw (&self, to: &mut T) -> Usually<XYWH<T::Unit>> {
(*self).draw(to)
}
}
impl<T: Screen, D: Draw<T>> Draw<T> for Arc<D> {
fn draw (&self, to: &mut T) -> Usually<WH<T::Unit>> {
fn draw (&self, to: &mut T) -> Usually<XYWH<T::Unit>> {
(**self).draw(to)
}
}
impl<T: Screen, D: Draw<T>> Draw<T> for RwLock<D> {
fn draw (&self, to: &mut T) -> Usually<WH<T::Unit>> {
fn draw (&self, to: &mut T) -> Usually<XYWH<T::Unit>> {
self.read().unwrap().draw(to)
}
}
impl<T: Screen, D: Draw<T>> Draw<T> for Option<D> {
fn draw (&self, to: &mut T) -> Usually<WH<T::Unit>> {
self.map(|draw|draw.draw(to)).transpose()?.flatten()
fn draw (&self, to: &mut T) -> Usually<XYWH<T::Unit>> {
Ok(self.as_ref().map(|draw|draw.draw(to)).transpose()?.unwrap_or_default())
}
}
/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts.
pub struct Thunk<T: Screen, F: FnOnce(&mut T)->Usually<WH<T::Unit>>>(
pub struct Thunk<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>>(
pub F,
std::marker::PhantomData<T>
);
pub const fn thunk <T: Screen, F: FnOnce(&mut T)->Usually<WH<T::Unit>>> (draw: F) -> Thunk<T, F> {
pub const fn thunk <T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> (draw: F) -> Thunk<T, F> {
Thunk(draw, std::marker::PhantomData)
}
impl<T: Screen, F: FnOnce(&mut T)->Usually<WH<T::Unit>>> Draw<T> for Thunk<T, F> {
fn draw (&self, to: &mut T) -> Usually<WH<T::Unit>> {
(self.0)(to)
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)
}
}
@ -82,447 +82,6 @@ pub const fn either <T: Screen> (condition: bool, a: impl Draw<T>, b: impl Draw<
thunk(move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) })
}
/// 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() }
}
/// 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!() })
}
/// A numeric type that can be used as coordinate.
///
/// FIXME: Replace with `num` crate?
/// FIXME: Use AsRef/AsMut?
///
/// ```
/// use tengri::draw::Coord;
/// let a: u16 = Coord::zero();
/// let b: u16 = a.plus(1);
/// let c: u16 = a.minus(2);
/// let d = a.atomic();
/// ```
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>
+ std::iter::Step
{
/// Zero in own type.
fn zero () -> Self { 0.into() }
/// Addition.
fn plus (self, other: Self) -> Self;
/// Saturating subtraction.
fn minus (self, other: Self) -> Self { if self >= other { self - other } else { 0.into() } }
/// Convert to [AtomicUsize].
fn atomic (self) -> AtomicUsize { AtomicUsize::new(self.into()) }
}
/// A cardinal direction.
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, PartialEq, Debug, Default)] pub enum Split {
North, South, East, West, Above, #[default] Below
}
pub const fn east <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::East.half(a, b)
}
pub const fn north <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::North.half(a, b)
}
pub const fn west <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::West.half(a, b)
}
pub const fn south <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::South.half(a, b)
}
pub const fn above <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::Above.half(a, b)
}
pub const fn below <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::Below.half(a, b)
}
impl Split {
/// ```
/// use tengri::draw::Split::*;
/// let _ = Above.bsp((), ());
/// let _ = Below.bsp((), ());
/// let _ = North.bsp((), ());
/// let _ = South.bsp((), ());
/// let _ = East.bsp((), ());
/// let _ = West.bsp((), ());
/// ```
pub const fn half <T: Screen> (&self, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|{
let (area_a, area_b) = to.xywh().split_half(self);
let (origin_a, origin_b) = self.origins();
match self {
Self::Below => {
to.place(area_b, &origin_b.align(b));
to.place(area_a, &origin_a.align(a));
},
_ => {
to.place(area_a, &origin_a.align(a));
to.place(area_b, &origin_b.align(b));
}
}
Ok(to.wh()) // FIXME: compute and return actually used area
})
}
/// Newly split areas begin at the center of the split
/// to maintain centeredness in the user's field of view.
///
/// Use [align] to override that and always start
/// at the top, bottom, etc.
///
/// ```
/// /*
///
/// Split east: Split south:
/// | | | | A |
/// | <-A|B-> | |---------|
/// | | | | B |
///
/// */
/// ```
const fn origins (&self) -> (Origin, Origin) {
use Origin::*;
match self {
Self::South => (S, N),
Self::East => (E, W),
Self::North => (N, S),
Self::West => (W, E),
Self::Above => (C, C),
Self::Below => (C, C),
}
}
/// Iterate over a collection of renderables:
///
/// ```
/// use tengri::draw::{Origin::*, Split::*};
/// let _ = Below.iter([
/// NW.align(W(15).max(W(10).min("Leftbar"))),
/// NE.align(W(12).max(W(10).min("Rightbar"))),
/// Center.align(W(40).max(H(20.max("Center"))))
/// ].iter(), |x|x);
/// ```
pub fn iter <T: Screen, U: Draw<T>, F: Fn(U)->dyn Draw<T>> (
_items: impl Iterator<Item = U>, _cb: F
) -> impl Draw<T> {
thunk(move|_to: &mut T|{ todo!() })
}
}
/// Coordinate along horizontal axis.
#[derive(Copy, Clone, Debug, Default)] pub struct X<N: Coord>(pub N);
impl<N: Coord> X<N> {
pub const fn push <T: Screen<Unit = N>> (x: N, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn pull <T: Screen<Unit = N>> (x: N, a: impl Draw<T>) -> impl Draw<T> {
a
}
}
#[derive(Copy, Clone, Debug, Default)] pub struct W<N: Coord>(pub N);
impl<N: Coord> W<N> {
pub const fn max <T: Screen<Unit = N>> (w: Option<N>, a: impl Draw<T>) -> impl Draw<T> {
WH::max(w, None, a)
}
pub const fn min <T: Screen<Unit = N>> (w: Option<N>, a: impl Draw<T>) -> impl Draw<T> {
WH::min(w, None, a)
}
pub const fn exact <T: Screen<Unit = N>> (w: T::Unit, c: impl Draw<T>) -> impl Draw<T> {
WH::exact(Some(w), None, c)
}
/// Shrink drawing area symmetrically.
///
/// ```
/// let padded = tengri::W(3).pad("Hello");
/// ```
pub const fn pad <T: Screen<Unit = N>> (x: N, draw: impl Draw<T>)
-> impl Draw<T>
{
thunk(move|to: &mut T|draw.draw(todo!()))
}
}
#[derive(Copy, Clone, Debug, Default)] pub struct Y<N: Coord>(pub N);
impl<N: Coord> Y<N> {
pub const fn push <T: Screen<Unit = N>> (x: N, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn pull <T: Screen<Unit = N>> (x: N, a: impl Draw<T>) -> impl Draw<T> {
a
}
}
#[derive(Copy, Clone, Debug, Default)] pub struct H<N: Coord>(pub N);
impl<N: Coord> H<N> {
pub const fn max <T: Screen<Unit = N>> (h: Option<N>, a: impl Draw<T>) -> impl Draw<T> {
WH::max(None, h, a)
}
pub const fn min <T: Screen<Unit = N>> (h: Option<N>, a: impl Draw<T>) -> impl Draw<T> {
WH::min(None, h, a)
}
pub const fn exact <T: Screen<Unit = N>> (h: T::Unit, c: impl Draw<T>) -> impl Draw<T> {
WH::exact(None, Some(h), c)
}
/// Shrink drawing area symmetrically.
///
/// ```
/// let padded = tengri::W::pad(3, "Hello");
/// ```
pub const fn pad <T: Screen<Unit = N>> (x: N, draw: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|draw.draw(todo!()))
}
}
/// An origin 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);
impl<N: Coord> HasX<N> for XY<N> { fn x (&self) -> X<N> { X(self.0) } }
impl<N: Coord> HasY<N> for XY<N> { fn y (&self) -> Y<N> { Y(self.1) } }
impl<N: Coord> XY<N> {
pub const fn push <T: Screen<Unit = N>> (&self, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn pull <T: Screen<Unit = N>> (&self, a: impl Draw<T>) -> impl Draw<T> {
a
}
}
/// 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);
impl<N: Coord> HasW<N> for WH<N> { fn w (&self) -> W<N> { W(self.0) } }
impl<N: Coord> HasH<N> for WH<N> { fn h (&self) -> H<N> { H(self.1) } }
impl<N: Coord> WH<N> {
/// Shrink drawing area symmetrically.
///
/// ```
/// let padded = tengri::WH(3, 5).pad("Hello");
/// ```
pub const fn pad <T: Screen<Unit = N>> (&self, 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::WH::min(3, 5, "Hello"); // 5x5
/// ```
pub const fn min <T: Screen<Unit = N>> (w: Option<N>, h: Option<N>, 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::WH::max(Some(3), Some(5), "Hello");
/// ```
pub const fn max <T: Screen<Unit = N>> (w: Option<N>, h: Option<N>, 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::WH::exact(Some(3), Some(5), "Hello");
/// ```
pub const fn exact <T: Screen<Unit = N>> (w: Option<N>, h: Option<N>, draw: impl Draw<T>)
-> impl Draw<T>
{
thunk(move|to: &mut T|draw.draw(todo!()))
}
/// Limit size of drawing area
/// ```
/// let clipped = tengri::WH::clip(Some(3), Some(5), "Hello");
/// ```
pub const fn clip <T: Screen<Unit = N>> (w: Option<N>, h: Option<N>, draw: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|draw.draw(todo!()))
}
}
/// 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: origin 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);
impl<N: Coord> HasX<N> for XYWH<N> { fn x (&self) -> X<N> { X(self.0) } }
impl<N: Coord> HasY<N> for XYWH<N> { fn y (&self) -> Y<N> { Y(self.1) } }
impl<N: Coord> HasW<N> for XYWH<N> { fn w (&self) -> W<N> { W(self.2) } }
impl<N: Coord> HasH<N> for XYWH<N> { fn h (&self) -> H<N> { H(self.3) } }
impl<N: Coord> XYWH<N> {
pub fn zero () -> Self {
Self(0.into(), 0.into(), 0.into(), 0.into())
}
pub fn center (&self) -> XY<N> {
let Self(x, y, w, h) = *self;
XY(x.plus(w/2.into()), y.plus(h/2.into()))
}
pub fn centered (&self) -> XY<N> {
let Self(x, y, w, h) = *self;
XY(x.minus(w/2.into()), y.minus(h/2.into()))
}
pub fn centered_x (&self, n: N) -> Self {
let Self(x, y, w, h) = *self;
let x_center = (x.plus(w / 2.into())).minus(n / 2.into());
let y_center = y.plus(h / 2.into());
XYWH(x_center, y_center, n, 1.into())
}
pub fn centered_y (&self, n: N) -> Self {
let Self(x, y, w, h) = *self;
let x_center = x.plus(w / 2.into());
let y_corner = (y.plus(h / 2.into())).minus(n / 2.into());
XYWH(x_center, y_corner, 1.into(), n)
}
pub fn centered_xy (&self, [n, m]: [N;2]) -> Self {
let Self(x, y, w, h) = *self;
let x_center = (x.plus(w / 2.into())).minus(n / 2.into());
let y_corner = (y.plus(h / 2.into())).minus(m / 2.into());
XYWH(x_center, y_corner, n, m)
}
pub fn split_half (&self, direction: &Split) -> (Self, Self) {
use Split::*;
let XYWH(x, y, w, h) = self.xywh();
match self {
South => (XYWH(x, y, w, h - h / 2), XYWH(x, y + h / 2, w, h / 2)),
East => (XYWH(x, y, w - w / 2, h), XYWH(x + w / 2, y, w / 2, h)),
North => (XYWH(x, y + h / 2, w, h - h / 2), XYWH(x, y, w, h / 2)),
West => (XYWH(x + w / 2, y, w - w / 2, h), XYWH(x, y, w / 2, h)),
Above | Below => (XYWH(x, y, w, h), XYWH(x, y, w, h))
}
}
}
pub trait HasXYWH<N: Coord>: HasXY<N> + HasWH<N> {
fn xywh (&self) -> XYWH<N>;
}
pub trait HasXY<N: Coord>: HasX<N> + HasY<N> {
fn xy (&self) -> XY<N>;
}
pub trait HasWH<N: Coord>: HasX<N> + HasY<N> {
fn wh (&self) -> WH<N>;
}
pub trait HasX<N: Coord> {
fn x (&self) -> X<N>;
fn iter_x (&self) -> impl Iterator<Item = N> where Self: HasW<N> + HasOrigin {
self.x_west()..self.x_east()
}
fn x_west (&self) -> N where Self: HasW<N> + 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: HasW<N> + 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: HasW<N> + HasOrigin {
todo!()
}
}
pub trait HasY<N: Coord> {
fn y (&self) -> Y<N>;
fn iter_y (&self) -> impl Iterator<Item = N> where Self: HasH<N> + HasOrigin {
self.y_north()..self.y_south()
}
fn y_north (&self) -> N where Self: HasH<N> + 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: HasH<N> + 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: HasH<N> + HasOrigin {
todo!()
}
}
pub trait HasW<N: Coord> {
fn w (&self) -> W<N>;
fn w_min (&self) -> W<N> { self.w() }
fn w_max (&self) -> W<N> { self.w() }
}
pub trait HasH<N: Coord> {
fn h (&self) -> H<N>;
fn h_min (&self) -> H<N> { self.h() }
fn h_max (&self) -> H<N> { self.h() }
}
impl<N: Coord, T: AsRef<X<N>>> HasX<N> for T {
fn x (&self) -> X<N> { *self.as_ref() }
}
impl<N: Coord, T: AsRef<X<N>>> HasY<N> for T {
fn y (&self) -> Y<N> { *self.as_ref() }
}
impl<N: Coord, T: HasX<N> + HasY<N>> HasXY<N> for T {
fn xy (&self) -> XY<N> { XY(self.x(), self.y()) }
}
impl<N: Coord, T: HasX<N> + HasY<N>> HasWH<N> for T {
fn wh (&self) -> WH<N> { WH(self.x(), self.h()) }
}
impl<N: Coord, T: HasXY<N> + HasWH<N>> HasXYWH<N> for T {
fn xywh (&self) -> XYWH<N> { XYWH(self.x(), self.y(), self.w(), self.h()) }
}
/// Output target.
///
/// ```
@ -539,38 +98,18 @@ impl<N: Coord, T: HasXY<N> + HasWH<N>> HasXYWH<N> for T {
/// }
///
/// impl Draw<Screen> for String {
/// fn draw (&self, to: &mut TestOut) -> Usually<WH<u16>> {
/// fn draw (&self, to: &mut TestOut) -> Usually<XYWH<u16>> {
/// to.area_mut().set_w(self.len() as u16);
/// }
/// }
/// ```
pub trait Screen: HasXYWH<Self::Unit> + HasOrigin + Send + Sync + Sized {
pub trait Screen: Space<Self::Unit> + Send + Sync + Sized {
type Unit: Coord;
/// Render drawable in area specified by `area`
fn place <'t, T: Draw<Self> + ?Sized> (&mut self, area: impl HasWH<Self::Unit>, content: &'t T) {
fn place <'t, T: Draw<Self> + ?Sized> (
&mut self, content: &'t T, area: Option<XYWH<Self::Unit>>
) {
let area = area.unwrap_or_else(||self.xywh());
unimplemented!()
}
}
/// Something that has a [Measure] of its rendered size.
pub trait Measured <N: Coord> {}
/// Something that has a bounding box
///
/// ```
/// use tengri::{Bounded, XYWH};
/// let bounded: Bounded<tengri::Tui, _> = Bounded(0, 0 ,0 ,0 ,"");
/// ```
pub trait Bounding <N: Coord>: HasXY<N> + HasWH<N> + HasOrigin {
}
//impl<O: Screen, T: Draw<O>> Draw<O> for Bounded<O, T> {
//fn draw (&self, to: &mut O) {
//let area = to.area();
//*to.area_mut() = self.0;
//self.1.draw(to);
//*to.area_mut() = area;
//}
//}
// pub fn displace ...

View file

@ -1,5 +1,5 @@
#[cfg(feature = "tui")] impl TuiKey {
#[cfg(feature = "term")]
impl crate::term::TuiKey {
#[cfg(feature = "lang")]
pub fn from_dsl (dsl: impl Language) -> Usually<Self> {
if let Some(word) = dsl.word()? {
@ -37,5 +37,4 @@
return Err(format!("TuiKey: unspecified").into())
}
}
}

View file

@ -33,6 +33,7 @@ pub(crate) use ::{
#[cfg(feature = "sing")] pub use ::jack::{*, contrib::{*, ClosureProcessHandler}};
#[cfg(feature = "draw")] pub mod draw;
#[cfg(feature = "draw")] pub mod space;
#[cfg(feature = "draw")] pub mod color;
#[cfg(feature = "text")] pub mod text;
#[cfg(feature = "term")] pub mod term;

View file

@ -1,6 +1,6 @@
use crate::{*, time::*, lang::*};
use ::std::{thread::JoinHandle, time::Duration};
#[cfg(feature = "tui")] use ::crossterm::event::poll;
#[cfg(feature = "term")] use ::crossterm::event::poll;
#[derive(Clone)] pub struct Exit(Arc<AtomicBool>);
@ -48,7 +48,7 @@ impl Thread {
/// Spawn a thread that uses [crossterm::event::poll]
/// to run `call` every `time` msec.
#[cfg(feature = "tui")]pub fn new_poll <F> (
#[cfg(feature = "term")]pub fn new_poll <F> (
exit: Arc<AtomicBool>, time: Duration, call: F
) -> Result<Self, std::io::Error>
where F: Fn(&PerfModel)->() + Send + Sync + 'static

385
src/space.rs Normal file
View file

@ -0,0 +1,385 @@
use crate::{*, draw::*};
/// 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: origin field (determines at which corner/side is X0 Y0)
///
#[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<N: Coord> X<N> for XYWH<N> { fn x (&self) -> N { self.0 } fn w (&self) -> N { self.2 } }
impl<N: Coord> Y<N> for XYWH<N> { fn y (&self) -> N { self.0 } fn h (&self) -> N { self.2 } }
impl<N: Coord> XYWH<N> {
pub fn zero () -> Self {
Self(0.into(), 0.into(), 0.into(), 0.into())
}
pub fn center (&self) -> (N, N) {
let Self(x, y, w, h) = *self;
(x.plus(w/2.into()), y.plus(h/2.into()))
}
pub fn centered (&self) -> (N, N) {
let Self(x, y, w, h) = *self;
(x.minus(w/2.into()), y.minus(h/2.into()))
}
pub fn centered_x (&self, n: N) -> Self {
let Self(x, y, w, h) = *self;
let x_center = (x.plus(w / 2.into())).minus(n / 2.into());
let y_center = y.plus(h / 2.into());
XYWH(x_center, y_center, n, 1.into())
}
pub fn centered_y (&self, n: N) -> Self {
let Self(x, y, w, h) = *self;
let x_center = x.plus(w / 2.into());
let y_corner = (y.plus(h / 2.into())).minus(n / 2.into());
XYWH(x_center, y_corner, 1.into(), n)
}
pub fn centered_xy (&self, [n, m]: [N;2]) -> Self {
let Self(x, y, w, h) = *self;
let x_center = (x.plus(w / 2.into())).minus(n / 2.into());
let y_corner = (y.plus(h / 2.into())).minus(m / 2.into());
XYWH(x_center, y_corner, n, m)
}
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())),
East => (XYWH(x, y, w - w / 2.into(), h), XYWH(x + w / 2.into(), y, w / 2.into(), h)),
North => (XYWH(x, y + h / 2.into(), w, h - h / 2.into()), XYWH(x, y, w, h / 2.into())),
West => (XYWH(x + w / 2.into(), y, w - w / 2.into(), h), XYWH(x, y, w / 2.into(), h)),
Above | Below => (XYWH(x, y, w, h), XYWH(x, y, w, h))
}
}
}
/// 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() }
}
/// 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!() })
}
/// A numeric type that can be used as coordinate.
///
/// FIXME: Replace with `num` crate?
/// FIXME: Use AsRef/AsMut?
///
/// ```
/// use tengri::draw::Coord;
/// let a: u16 = Coord::zero();
/// let b: u16 = a.plus(1);
/// let c: u16 = a.minus(2);
/// let d = a.atomic();
/// ```
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>
+ std::iter::Step
{
/// Zero in own type.
fn zero () -> Self { 0.into() }
/// Addition.
fn plus (self, other: Self) -> Self;
/// Saturating subtraction.
fn minus (self, other: Self) -> Self { if self >= other { self - other } else { 0.into() } }
/// Convert to [AtomicUsize].
fn atomic (self) -> AtomicUsize { AtomicUsize::new(self.into()) }
}
/// A cardinal direction.
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, PartialEq, Debug, Default)] pub enum Split {
North, South, East, West, Above, #[default] Below
}
pub const fn east <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::East.half(a, b)
}
pub const fn north <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::North.half(a, b)
}
pub const fn west <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::West.half(a, b)
}
pub const fn south <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::South.half(a, b)
}
pub const fn above <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::Above.half(a, b)
}
pub const fn below <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::Below.half(a, b)
}
impl Split {
/// ```
/// use tengri::draw::Split::*;
/// let _ = Above.bsp((), ());
/// let _ = Below.bsp((), ());
/// let _ = North.bsp((), ());
/// let _ = South.bsp((), ());
/// let _ = East.bsp((), ());
/// let _ = West.bsp((), ());
/// ```
pub const fn half <T: Screen> (&self, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|{
let (area_a, area_b) = to.xywh().split_half(self);
let (origin_a, origin_b) = self.origins();
match self {
Self::Below => {
to.place(&origin_b.align(b), Some(area_b));
to.place(&origin_a.align(a), Some(area_b));
},
_ => {
to.place(&origin_a.align(a), Some(area_a));
to.place(&origin_b.align(b), Some(area_a));
}
}
Ok(to.xywh()) // FIXME: compute and return actually used area
})
}
/// Newly split areas begin at the center of the split
/// to maintain centeredness in the user's field of view.
///
/// Use [align] to override that and always start
/// at the top, bottom, etc.
///
/// ```
/// /*
///
/// Split east: Split south:
/// | | | | A |
/// | <-A|B-> | |---------|
/// | | | | B |
///
/// */
/// ```
const fn origins (&self) -> (Origin, Origin) {
use Origin::*;
match self {
Self::South => (S, N),
Self::East => (E, W),
Self::North => (N, S),
Self::West => (W, E),
Self::Above => (C, C),
Self::Below => (C, C),
}
}
/// Iterate over a collection of renderables:
///
/// ```
/// use tengri::draw::{Origin::*, Split::*};
/// let _ = Below.iter([
/// NW.align(W(15).max(W(10).min("Leftbar"))),
/// NE.align(W(12).max(W(10).min("Rightbar"))),
/// Center.align(W(40).max(H(20.max("Center"))))
/// ].iter(), |x|x);
/// ```
pub fn iter <T: Screen, U: Draw<T>, F: Fn(U)->dyn Draw<T>> (
_items: impl Iterator<Item = U>, _cb: F
) -> impl Draw<T> {
thunk(move|_to: &mut T|{ todo!() })
}
}
/// 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::W(3).pad("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::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::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::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::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::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!()))
}

View file

@ -1,10 +1,11 @@
use crate::{*, lang::*, play::*, draw::{*, Split::*}, color::*, text::*};
use crate::{*, lang::*, play::*, draw::*, space::{*, Split::*}, color::*, text::*};
use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
use rand::distributions::uniform::UniformSampler;
use ::{
std::{
io::{stdout, Write},
time::Duration
time::Duration,
ops::{Deref, DerefMut},
},
better_panic::{Settings, Verbosity},
ratatui::{
@ -20,25 +21,36 @@ use ::{
event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
}
};
pub struct Tui(pub Buffer, pub XYWH<u16>);
impl Screen for Tui { type Unit = u16; }
impl HasX<u16> for Tui { fn x (&self) -> X<u16> { self.1.x() } }
impl HasY<u16> for Tui { fn y (&self) -> Y<u16> { self.1.y() } }
impl HasW<u16> for Tui { fn w (&self) -> W<u16> { self.1.w() } }
impl HasH<u16> for Tui { fn h (&self) -> H<u16> { self.1.h() } }
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 HasOrigin for Tui { fn origin (&self) -> Origin { Origin::NW } }
impl AsRef<Buffer> for Tui { fn as_ref (&self) -> &Buffer { &self.0 } }
impl AsMut<Buffer> for Tui { fn as_mut (&mut self) -> &mut Buffer { &mut self.0 } }
impl AsRef<XYWH<u16>> for Tui { fn as_ref (&self) -> &XYWH<u16> { &self.0 } }
impl AsMut<XYWH<u16>> for Tui { fn as_mut (&mut self) -> &mut XYWH<u16> { &mut self.0 } }
impl X<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 {
fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> WH<u16> {
tui_update(self.0, self.1, callback);
self.1.wh()
fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> XYWH<u16> {
for row in 0..self.h() {
let y = self.y() + row;
for col in 0..self.w() {
let x = self.x() + col;
if x < self.0.area.width && y < self.0.area.height {
if let Some(cell) = self.0.cell_mut(Position { x, y }) {
callback(cell, col, row);
}
}
}
}
self.xywh()
}
fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) {
for cell in self.buffer().content.iter_mut() {
for cell in self.0.content.iter_mut() {
cell.fg = fg;
cell.bg = bg;
cell.modifier = modifier;
@ -47,9 +59,8 @@ impl Tui {
fn blit (&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>) {
let text = text.as_ref();
let style = style.unwrap_or(Style::default());
let buf = self.buffer();
if x < buf.area.width && y < buf.area.height {
buf.set_string(x, y, text, style);
if x < self.0.area.width && y < self.0.area.height {
self.0.set_string(x, y, text, style);
}
}
}
@ -73,7 +84,7 @@ pub const fn fg_bg (fg: Color, bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui
draw.draw(to)
})
}
pub const fn fill_char (c: char) {
pub const fn fill_char (c: char) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|Ok(to.update(&|cell,_,_|{
cell.set_char(c);
})))
@ -85,33 +96,31 @@ pub const fn modify (on: bool, modifier: Modifier, draw: impl Draw<Tui>) -> impl
draw.draw(to)
})
}
pub const fn fill_mod (on: bool, modifier: Modifier) {
thunk(move|to: &mut Tui|{
pub const fn fill_mod (on: bool, modifier: Modifier) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|Ok({
if on {
to.update(&|cell,_,_|cell.modifier.insert(modifier))
} else {
to.update(&|cell,_,_|cell.modifier.remove(modifier))
}
})
}))
}
/// Draw contents with bold modifier applied.
pub const fn bold (on: bool, draw: impl Draw<Tui>) -> impl Draw<Tui> {
modify(on, Modifier::BOLD, draw)
}
pub const fn fill_ul (on: bool, color: Option<Color>) {
thunk(move|to: &mut Tui|{
if on {
pub const fn fill_ul (color: Option<Color>) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|Ok(if let Some(color) = color {
to.update(&|cell,_,_|{
cell.modifier.insert(Modifier::UNDERLINED);
cell.underline_color = color;
})
} else {
to.update(&|cell,_,_|&|cell,_,_|{
to.update(&|cell,_,_|{
cell.modifier.remove(Modifier::UNDERLINED);
cell.underline_color = Reset;
})
}
})
}))
}
/// TUI works in u16 coordinates.
@ -122,24 +131,24 @@ impl Coord for u16 {
}
impl Draw<Tui> for u64 {
fn draw (&self, _to: &mut Tui) -> Usually<WH<u16>> { todo!() }
fn draw (&self, _to: &mut Tui) -> Usually<XYWH<u16>> { todo!() }
}
impl Draw<Tui> for f64 {
fn draw (&self, _to: &mut Tui) -> Usually<WH<u16>> { todo!() }
fn draw (&self, _to: &mut Tui) -> Usually<XYWH<u16>> { todo!() }
}
impl Draw<Tui> for &str {
fn draw (&self, to: &mut Tui) -> Usually<WH<u16>> {
let XYWH(x, y, w, ..) = to.centered_xy([width_chars_max(to.w(), self), 1]);
fn draw (&self, to: &mut Tui) -> Usually<XYWH<u16>> {
let XYWH(x, y, w, ..) = to.1.centered_xy([width_chars_max(to.w(), self), 1]);
to.text(&self, x, y, w)
}
}
impl Draw<Tui> for String {
fn draw (&self, to: &mut Tui) -> Usually<WH<u16>> {
fn draw (&self, to: &mut Tui) -> Usually<XYWH<u16>> {
self.as_str().draw(to)
}
}
impl Draw<Tui> for Arc<str> {
fn draw (&self, to: &mut Tui) -> Usually<WH<u16>> {
fn draw (&self, to: &mut Tui) -> Usually<XYWH<u16>> {
self.as_ref().draw(to)
}
}
@ -150,11 +159,11 @@ mod phat {
pub const HI: &'static str = "";
/// A phat line
pub fn lo (fg: Color, bg: Color) -> impl Draw<Tui> {
H::exact(1, fg_bg(fg, bg, x_repeat(self::phat::LO)))
h_exact(1, fg_bg(fg, bg, x_repeat(self::phat::LO)))
}
/// A phat line
pub fn hi (fg: Color, bg: Color) -> impl Draw<Tui> {
H::exact(1, fg_bg(fg, bg, x_repeat(self::phat::HI)))
h_exact(1, fg_bg(fg, bg, x_repeat(self::phat::HI)))
}
}
@ -173,7 +182,7 @@ pub const fn x_repeat (c: &str) -> impl Draw<Tui> {
cell.set_symbol(&c);
}
}
Ok(WH(w, 1))
Ok(XYWH(x, y, w, 1))
})
}
@ -185,7 +194,7 @@ pub const fn y_repeat (c: &str) -> impl Draw<Tui> {
cell.set_symbol(&c);
}
}
Ok(WH(1, h))
Ok(XYWH(x, y, 1, h))
})
}
@ -201,7 +210,7 @@ pub const fn xy_repeat (c: &str) -> impl Draw<Tui> {
}
}
}
Ok(WH(w, h))
Ok(XYWH(x, y, w, h))
})
}
@ -252,8 +261,8 @@ macro_rules! border {
#[derive(Copy, Clone)] pub struct $T(pub bool, pub Style);
//impl Layout<Tui> for $T {}
impl Draw<Tui> for $T {
fn draw (&self, to: &mut Tui) -> Usually<WH<u16>> {
when(self.enabled(), |to: &mut Tui|BorderStyle::draw(self, to)).draw(to)
fn draw (&self, to: &mut Tui) -> Usually<XYWH<u16>> {
when(self.enabled(), thunk(|to: &mut Tui|BorderStyle::draw(self, to))).draw(to)
}
}
)+}
@ -376,36 +385,26 @@ pub trait BorderStyle: Draw<Tui> + Copy {
fn border_sw (&self) -> &str { Self::SW }
fn border_se (&self) -> &str { Self::SE }
fn enclose (self, w: impl Draw<Tui>) -> impl Draw<Tui> {
below(WH::fill(border(self.enabled(), self)), w)
}
fn enclose2 (self, w: impl Draw<Tui>) -> impl Draw<Tui> {
below(WH::pad(1, 1, WH::fill(border(self.enabled(), self))), w)
}
fn enclose_bg (self, w: impl Draw<Tui>) -> impl Draw<Tui> {
Tui::bg(self.style().unwrap().bg.unwrap_or(Color::Reset),
below(WH::fill(border(self.enabled(), self)), w))
}
#[inline] fn draw <'a> (&self, to: &mut impl Draw<Tui>) -> Usually<()> {
#[inline] fn draw <'a> (&self, to: &mut Tui) -> Usually<XYWH<u16>> {
if self.enabled() {
self.draw_h(to, None)?;
self.draw_v(to, None)?;
self.draw_c(to, None)?;
}
Ok(())
Ok(to.1)
}
#[inline] fn draw_h (&self, to: &mut impl Draw<Tui>, style: Option<Style>) -> Usually<XYWH<u16>> {
let area = to.area();
#[inline] fn draw_h (&self, to: &mut Tui, style: Option<Style>) -> Usually<XYWH<u16>> {
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)
let y1 = to.y_north();
let y2 = to.y_south().saturating_sub(1);
for x in to.x_west()..to.x_east().saturating_sub(1) {
to.blit(&Self::N, x, y1, style);
to.blit(&Self::S, x, y2, style)
}
Ok(area)
Ok(to.xywh())
}
#[inline] fn draw_v (&self, to: &mut impl Draw<Tui>, style: Option<Style>) -> Usually<XYWH<u16>> {
let area = to.area();
#[inline] fn draw_v (&self, to: &mut Tui, style: Option<Style>) -> Usually<XYWH<u16>> {
let area = to.1;
let style = style.or_else(||self.style_vertical());
let [x, x2, y, y2] = area.lrtb();
let h = y2 - y;
@ -420,8 +419,8 @@ pub trait BorderStyle: Draw<Tui> + Copy {
}
Ok(area)
}
#[inline] fn draw_c (&self, to: &mut impl Draw<Tui>, style: Option<Style>) -> Usually<XYWH<u16>> {
let area = to.area();
#[inline] fn draw_c (&self, to: &mut Tui, style: Option<Style>) -> Usually<XYWH<u16>> {
let area = to.1;
let style = style.or_else(||self.style_corners());
let XYWH(x, y, w, h) = area;
if w > 1 && h > 1 {
@ -457,28 +456,26 @@ pub trait BorderStyle: Draw<Tui> + Copy {
/// ```
/// /// TODO
/// ```
pub fn phat <T, N: Coord> (w: N, h: N, [fg, bg, hi, lo]: [Color;4], draw: impl Draw<Tui>) -> impl Draw<Tui> {
let top = W::exact(1, self::phat::lo(bg, hi));
let low = W::exact(1, self::phat::hi(bg, lo));
pub fn phat (w: u16, h: u16, [fg, bg, hi, lo]: [Color;4], draw: impl Draw<Tui>) -> impl Draw<Tui> {
let top = w_exact(1, self::phat::lo(bg, hi));
let low = w_exact(1, self::phat::hi(bg, lo));
let draw = fg_bg(fg, bg, draw);
WH::min(w, h, south(top, north(low, WH::fill(draw))))
wh_min(Some(w), Some(h), south(top, north(low, draw)))
}
fn x_scroll () {
|to: &mut Tui|{
let XYWH(x1, y1, w, h) = to.area();
let mut buf = to.buffer.write().unwrap();
let x2 = x1 + w;
for (i, x) in (x1..=x2).enumerate() {
if let Some(cell) = buf.cell_mut(Position::from((x, y1))) {
fn x_scroll () -> impl Draw<Tui> {
thunk(|Tui(buf, XYWH(x1, y1, w, h)): &mut Tui|{
let x2 = *x1 + *w;
for (i, x) in (*x1..=x2).enumerate() {
if let Some(cell) = buf.cell_mut(Position::from((x, *y1))) {
if i < (self::scroll::ICON_DEC_H.len()) {
cell.set_fg(Rgb(255, 255, 255));
cell.set_bg(Rgb(0, 0, 0));
cell.set_char(self::scroll::ICON_DEC_H[i as usize]);
} else if i > (w as usize - self::scroll::ICON_INC_H.len()) {
} else if i > (*w as usize - self::scroll::ICON_INC_H.len()) {
cell.set_fg(Rgb(255, 255, 255));
cell.set_bg(Rgb(0, 0, 0));
cell.set_char(self::scroll::ICON_INC_H[w as usize - i]);
cell.set_char(self::scroll::ICON_INC_H[*w as usize - i]);
} else if false {
cell.set_fg(Rgb(255, 255, 255));
cell.set_bg(Reset);
@ -490,24 +487,23 @@ fn x_scroll () {
}
}
}
}
Ok(XYWH(*x1, *y1, *w, 1))
})
}
fn y_scroll () {
|to: &mut Tui|{
let XYWH(x1, y1, w, h) = to.area();
let mut buf = to.buffer.write().unwrap();
let y2 = y1 + h;
for (i, y) in (y1..=y2).enumerate() {
if let Some(cell) = buf.cell_mut(Position::from((x1, y))) {
fn y_scroll () -> impl Draw<Tui> {
thunk(|Tui(buf, XYWH(x1, y1, w, h)): &mut Tui|{
let y2 = *y1 + *h;
for (i, y) in (*y1..=y2).enumerate() {
if let Some(cell) = buf.cell_mut(Position::from((*x1, y))) {
if (i as usize) < (self::scroll::ICON_DEC_V.len()) {
cell.set_fg(Rgb(255, 255, 255));
cell.set_bg(Rgb(0, 0, 0));
cell.set_char(self::scroll::ICON_DEC_V[i as usize]);
} else if (i as usize) > (h as usize - self::scroll::ICON_INC_V.len()) {
} else if (i as usize) > (*h as usize - self::scroll::ICON_INC_V.len()) {
cell.set_fg(Rgb(255, 255, 255));
cell.set_bg(Rgb(0, 0, 0));
cell.set_char(self::scroll::ICON_INC_V[h as usize - i]);
cell.set_char(self::scroll::ICON_INC_V[*h as usize - i]);
} else if false {
cell.set_fg(Rgb(255, 255, 255));
cell.set_bg(Reset);
@ -519,11 +515,12 @@ fn y_scroll () {
}
}
}
}
Ok(XYWH(*x1, *y1, 1, *h))
})
}
/// Spawn the TUI output thread which writes colored characters to the terminal.
pub fn tui_output <W: Write, T: Draw<Tui> + Send + Sync + 'static> (
pub fn tui_output <W: Write + Send + Sync, T: Draw<Tui> + Send + Sync + 'static> (
output: W,
exited: &Arc<AtomicBool>,
state: &Arc<RwLock<T>>,
@ -532,11 +529,11 @@ pub fn tui_output <W: Write, T: Draw<Tui> + Send + Sync + 'static> (
let state = state.clone();
tui_setup(&mut output)?;
let mut backend = CrosstermBackend::new(output);
let WH(width, height) = tui_wh(&mut backend);
let Size { width, height } = backend.size().expect("get size failed");
let mut buffer_a = Buffer::empty(Rect { x: 0, y: 0, width, height });
let mut buffer_b = Buffer::empty(Rect { x: 0, y: 0, width, height });
Thread::new_sleep(exited.clone(), sleep, move |perf| {
let size = tui_wh(&mut backend);
let size = backend.size().expect("get size failed");
if let Ok(state) = state.try_read() {
tui_resize(&mut backend, &mut buffer_a, size);
buffer_a = tui_redraw(&mut backend, &mut buffer_a, &mut buffer_b);
@ -546,7 +543,7 @@ pub fn tui_output <W: Write, T: Draw<Tui> + Send + Sync + 'static> (
})
}
pub fn tui_setup (output: &mut impl Write) -> Usually<()> {
pub fn tui_setup <T: Write + Send + Sync> (output: &mut T) -> Usually<()> {
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{
output.execute(LeaveAlternateScreen).unwrap();
@ -561,10 +558,10 @@ pub fn tui_setup (output: &mut impl Write) -> Usually<()> {
pub fn tui_resize <W: Write> (
backend: &mut CrosstermBackend<W>,
buffer: &mut Tui,
size: WH<u16>
buffer: &mut Buffer,
size: XYWH<u16>
) {
if buffer.area != size {
if buffer.1 != size {
backend.clear_region(ClearType::All).unwrap();
buffer.resize(size);
buffer.reset();
@ -591,24 +588,8 @@ pub fn tui_teardown <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()
}
pub fn tui_update (
buf: &mut Tui, area: XYWH<u16>, callback: &impl Fn(&mut Cell, u16, u16)
Tui(buf, ..): &mut Tui, area: XYWH<u16>, callback: &impl Fn(&mut Cell, u16, u16)
) {
for row in 0..area.h().0 {
let y = area.y().0 + row;
for col in 0..area.w().0 {
let x = area.x() + col;
if x < buf.area.width && y < buf.area.height {
if let Some(cell) = buf.cell_mut(Position { x, y }) {
callback(cell, col, row);
}
}
}
}
}
pub(crate) fn tui_wh <W: Write> (backend: &mut CrosstermBackend<W>) -> WH<u16> {
let Size { width, height } = backend.size().expect("get size failed");
WH(width, height)
}
/// Draw border around shrinked item.
@ -617,25 +598,26 @@ pub(crate) fn tui_wh <W: Write> (backend: &mut CrosstermBackend<W>) -> WH<u16> {
/// /// TODO
/// ```
pub const fn border <T, S: BorderStyle> (on: bool, style: S, draw: impl Draw<Tui>) -> impl Draw<Tui> {
let content = pad(Some(1), Some(1), draw);
let outline = when(on, |to: &mut Tui|{
let area = to.area();
if area.w() > 0 && area.y() > 0 {
to.blit(&style.border_nw(), area.x(), area.y(), style.style());
to.blit(&style.border_ne(), area.x() + area.w() - 1, area.y(), style.style());
to.blit(&style.border_sw(), area.x(), area.y() + area.h() - 1, style.style());
to.blit(&style.border_se(), area.x() + area.w() - 1, area.y() + area.h() - 1, style.style());
for x in area.x()+1..area.x()+area.w()-1 {
to.blit(&style.border_n(), x, area.y(), style.style());
to.blit(&style.border_s(), x, area.y() + area.h() - 1, style.style());
let content = wh_pad(1, 1, draw);
let outline = when(on, thunk(move|to: &mut Tui|{
let XYWH(x, y, w, h) = to.1;
if w > 0 && h > 0 {
to.blit(&style.border_nw(), x, y, style.style());
to.blit(&style.border_ne(), x + w - 1, y, style.style());
to.blit(&style.border_sw(), x, y + h - 1, style.style());
to.blit(&style.border_se(), x + w - 1, y + h - 1, style.style());
for x in x+1..x+w-1 {
to.blit(&style.border_n(), x, y, style.style());
to.blit(&style.border_s(), x, y + h - 1, style.style());
}
for y in area.y()+1..area.y()+area.h()-1 {
to.blit(&style.border_w(), area.x(), y, style.style());
to.blit(&style.border_e(), area.x() + area.w() - 1, y, style.style());
for y in y+1..y+h-1 {
to.blit(&style.border_w(), x, y, style.style());
to.blit(&style.border_e(), x + w - 1, y, style.style());
}
}
});
Above.split(outline, content)
Ok(XYWH(x, y, w, h))
}));
above(outline, content)
}
/// Draw TUI content or its error message.
@ -645,16 +627,15 @@ pub const fn border <T, S: BorderStyle> (on: bool, style: S, draw: impl Draw<Tui
/// let _ = tengri::tui::catcher(Ok(None));
/// let _ = tengri::tui::catcher(Err("draw fail".into()));
/// ```
pub fn catcher <T, E> (error: Usually<E>, draw: impl Draw<Tui>) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|match error.as_ref() {
Ok(Some(content)) => draw(to),
Ok(None) => to.blit(&"<empty>", 0, 0, Some(Style::default().yellow())),
pub fn catcher <T: Draw<Tui>> (result: Usually<T>) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|match result.as_ref() {
Ok(content) => content.draw(to),
Err(e) => {
let err_fg = rgb(255,224,244);
let err_bg = rgb(96, 24, 24);
let err_fg = Color::Rgb(255,224,244);
let err_bg = Color::Rgb(96, 24, 24);
let title = east(bold(true, "upsi daisy. "), "rendering failed.");
let error = east("\"why?\" ", bold(true, format!("{e}")));
&fg(err_fg, bg(err_bg, south(title, error)))(to)
fg(err_fg, bg(err_bg, south(title, error))).draw(to)
}
})
}
@ -730,8 +711,8 @@ pub fn tui_input <T: Do<TuiEvent, T> + Send + Sync + 'static> (
// Handle all other events by the state:
_ => {
let event = TuiEvent::from_crossterm(event);
if let Err(e) = state.write().unwrap().handle(&event) {
let event = TuiEvent(TuiKey::from_crossterm(event));
if let Err(e) = state.write().unwrap().apply(&event) {
panic!("{e}")
}
}
@ -742,17 +723,8 @@ pub fn tui_input <T: Do<TuiEvent, T> + Send + Sync + 'static> (
/// TUI input loop event.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
pub struct TuiEvent(pub Event);
impl_from!(TuiEvent: |e: Event| TuiEvent(e));
impl_from!(TuiEvent: |c: char| TuiEvent(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE))));
impl TuiEvent {
#[cfg(feature = "lang")]
pub fn from_dsl (dsl: impl Language) -> Usually<Self> {
Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self))
}
}
impl Ord for TuiEvent {
fn cmp (&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other)
@ -763,11 +735,8 @@ impl Ord for TuiEvent {
/// TUI key spec.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
pub struct TuiKey(pub Option<KeyCode>, pub KeyModifiers);
impl TuiKey {
const SPLIT: char = '/';
pub fn to_crossterm (&self) -> Option<Event> {
self.0.map(|code|Event::Key(KeyEvent {
code,
@ -776,7 +745,6 @@ impl TuiKey {
state: KeyEventState::NONE,
}))
}
pub fn named (token: &str) -> Option<KeyCode> {
use KeyCode::*;
Some(match token {
@ -820,5 +788,4 @@ impl TuiKey {
_ => return None,
})
}
}

View file

@ -1,18 +1,62 @@
use crate::{Usually, draw::{Draw, WH}};
use crate::{Usually, draw::Draw, space::*};
pub(crate) use ::unicode_width::*;
/// 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);
#[cfg(feature = "term")] mod impl_term {
use super::*;
use crate::term::Tui;
use ratatui::prelude::Position;
/// 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);
impl Draw<Tui> for str {
fn draw (&self, to: &mut Tui) -> Usually<XYWH<u16>> { todo!() }
}
impl<'a, T: AsRef<str>> TrimString<T> {
fn to_ref (&self) -> TrimStringRef<'_, T> { TrimStringRef(self.0, &self.1) }
impl<T: AsRef<str>> Draw<Tui> for TrimString<T> {
fn draw (&self, to: &mut Tui) -> Usually<XYWH<u16>> { self.as_ref().draw(to) }
}
impl<T: AsRef<str>> Draw<Tui> for TrimStringRef<'_, T> {
fn draw (&self, to: &mut Tui) -> Usually<XYWH<u16>> {
let XYWH(x, y, w, ..) = to.1;
let mut width: u16 = 1;
let mut chars = self.1.as_ref().chars();
while let Some(c) = chars.next() {
if width > self.0 || width > w {
break
}
let pos = Position { x: x + width - 1, y };
if let Some(cell) = to.0.cell_mut(pos) {
cell.set_char(c);
}
width += c.width().unwrap_or(0) as u16;
}
let XYWH(x, y, w, ..) = XYWH(to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h());
to.text(&self.as_ref(), x, y, w)
}
}
impl Tui {
/// Write a line of text
///
/// TODO: do a paragraph (handle newlines)
pub fn text (&mut self, text: &impl AsRef<str>, x0: u16, y: u16, max_width: u16) -> Usually<XYWH<u16>> {
let text = text.as_ref();
let mut string_width: u16 = 0;
for character in text.chars() {
let x = x0 + string_width;
let character_width = character.width().unwrap_or(0) as u16;
string_width += character_width;
if string_width > max_width {
break
}
if let Some(cell) = self.0.cell_mut(ratatui::prelude::Position { x, y }) {
cell.set_char(character);
} else {
break
}
}
Ok(XYWH(x0, y, string_width, 1))
}
}
}
/// Trim string with [unicode_width].
@ -31,6 +75,23 @@ pub fn trim_string (max_width: usize, input: impl AsRef<str>) -> String {
return output.into_iter().collect()
}
/// 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);
impl<T: AsRef<str>> AsRef<str> for TrimString<T> { fn as_ref (&self) -> &str { self.1.as_ref() } }
impl<'a, T: AsRef<str>> TrimString<T> {
fn to_ref (&self) -> TrimStringRef<'_, T> { TrimStringRef(self.0, &self.1) }
}
/// 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);
impl<T: AsRef<str>> AsRef<str> for TrimStringRef<'_, T> {
fn as_ref (&self) -> &str { self.1.as_ref() }
}
pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
let mut width: u16 = 0;
let mut chars = text.as_ref().chars();
@ -42,64 +103,3 @@ pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
}
return width
}
#[cfg(feature = "term")] mod impl_term {
use super::*;
use crate::draw::XYWH;
use crate::term::Tui;
use ratatui::prelude::Position;
impl Draw<Tui> for str {
fn draw (&self, to: &mut Tui) -> Usually<WH<u16>> { todo!() }
}
impl<T: AsRef<str>> Draw<Tui> for TrimString<T> {
fn draw (&self, to: &mut Tui) -> Usually<WH<u16>> { self.as_ref().draw(to) }
}
impl<T: AsRef<str>> Draw<Tui> for TrimStringRef<'_, T> {
fn draw (&self, to: &mut Tui) -> Usually<WH<u16>> {
let area = to.area();
let mut width: u16 = 1;
let mut chars = self.1.as_ref().chars();
while let Some(c) = chars.next() {
if width > self.0 || width > area.w() {
break
}
let x = area.x() + width - 1;
let y = area.y();
let pos = Position { x, y };
if let Some(cell) = to.cell_mut(pos) {
cell.set_char(c);
}
width += c.width().unwrap_or(0) as u16;
}
let XYWH(x, y, w, ..) = XYWH(to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h());
to.text(&self, x, y, w)
}
}
impl Tui {
/// Write a line of text
///
/// TODO: do a paragraph (handle newlines)
fn text (&mut self, text: &impl AsRef<str>, x0: u16, y: u16, max_width: u16) {
let text = text.as_ref();
let buf = self.buffer();
let mut string_width: u16 = 0;
for character in text.chars() {
let x = x0 + string_width;
let character_width = character.width().unwrap_or(0) as u16;
string_width += character_width;
if string_width > max_width {
break
}
if let Some(cell) = buf.write().unwrap().cell_mut(ratatui::prelude::Position { x, y }) {
cell.set_char(character);
} else {
break
}
}
}
}
}