mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-07-17 15:56:57 +02:00
reorganize, add Azimuth
This commit is contained in:
parent
bf16288884
commit
1f60b43f61
26 changed files with 677 additions and 594 deletions
2
dizzle
2
dizzle
|
|
@ -1 +1 @@
|
||||||
Subproject commit f8e310b477afc1aab4701fdc52da474143d78a28
|
Subproject commit af2a10751f87caef8e5526d1f692c941b8de8357
|
||||||
76
src/draw.rs
76
src/draw.rs
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
pub use crate::space::*;
|
|
||||||
|
|
||||||
/// Output target.
|
/// Output target.
|
||||||
///
|
///
|
||||||
|
|
@ -70,14 +69,16 @@ pub trait Screen: Xy<Self::Unit> + Wh<Self::Unit> + Send + Sync + Sized {
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub trait Draw<S: Screen> {
|
pub trait Draw<S: Screen> {
|
||||||
fn draw (self, to: &mut S) -> Perhaps<XYWH<S::Unit>>;
|
fn draw (self, to: &mut S) -> Drawn<S::Unit>;
|
||||||
fn layout (&self, area: XYWH<S::Unit>) -> Perhaps<XYWH<S::Unit>> {
|
fn layout (&self, area: XYWH<S::Unit>) -> Drawn<S::Unit> {
|
||||||
Ok(Some(area))
|
Ok(Some(area))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type Drawn<U> = Perhaps<XYWH<U>>;
|
||||||
|
|
||||||
impl<S: Screen> Draw<S> for () {
|
impl<S: Screen> Draw<S> for () {
|
||||||
fn draw (self, _: &mut S) -> Perhaps<XYWH<S::Unit>> {
|
fn draw (self, _: &mut S) -> Drawn<S::Unit> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -87,7 +88,7 @@ impl_draw!(<S: Screen, D: Draw<S>,>|self: Option<D>, to: S|{
|
||||||
});
|
});
|
||||||
|
|
||||||
//impl<S: Screen, D: Draw<S>> Draw<S> for RwLock<D> {
|
//impl<S: Screen, D: Draw<S>> Draw<S> for RwLock<D> {
|
||||||
//fn draw (self, __: &mut S) -> Perhaps<XYWH<S::Unit>> {
|
//fn draw (self, __: &mut S) -> Drawn<S::Unit> {
|
||||||
//todo!()
|
//todo!()
|
||||||
//}
|
//}
|
||||||
//}
|
//}
|
||||||
|
|
@ -111,52 +112,21 @@ impl<T: Screen, V: View<T>> Draw<T> for &V {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts.
|
features! {
|
||||||
pub struct Thunk<T: Screen, F: FnOnce(&mut T)->Perhaps<XYWH<T::Unit>>>(
|
"draw": [
|
||||||
pub F,
|
align,
|
||||||
std::marker::PhantomData<T>
|
area,
|
||||||
);
|
azimuth,
|
||||||
|
color,
|
||||||
impl<T: Screen, F: FnOnce(&mut T)->Perhaps<XYWH<T::Unit>>> Draw<T> for Thunk<T, F> {
|
coord,
|
||||||
fn draw (self, to: &mut T) -> Perhaps<XYWH<T::Unit>> {
|
iter,
|
||||||
(self.0)(to)
|
layout,
|
||||||
}
|
lrtb,
|
||||||
}
|
origin,
|
||||||
|
sizer,
|
||||||
/// Basic [Draw]able closure.
|
space,
|
||||||
///
|
split,
|
||||||
/// ```
|
thunk,
|
||||||
/// # use tengri::{draw::*, term::*};
|
xywh
|
||||||
/// # 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)->Perhaps<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) })
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
src/draw/align.rs
Normal file
31
src/draw/align.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub struct Align<T>(Option<Azimuth>, T);
|
||||||
|
|
||||||
|
impl_draw!(<S: Screen, T: Draw<S>,>|self: Align<T>, to: S|{
|
||||||
|
todo!()
|
||||||
|
});
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// use tengri::*;
|
||||||
|
/// let _ = align(None, "unaligned");
|
||||||
|
/// let _ = align(Azimuth::SE, "southeast");
|
||||||
|
/// let _ = align(Some(Azimuth::SE), "southeast");
|
||||||
|
/// ```
|
||||||
|
pub fn align <S: Screen, T: Draw<S>, U: Into<Option<Azimuth>>> (origin: U, it: T) -> Align<T> {
|
||||||
|
Align(origin.into(), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn align_c <S: Screen, T: Draw<S>> (it: T) -> Align<T> { Align(Some(Azimuth::C), it) }
|
||||||
|
pub fn align_x <S: Screen, T: Draw<S>> (it: T) -> Align<T> { Align(Some(Azimuth::X), it) }
|
||||||
|
pub fn align_y <S: Screen, T: Draw<S>> (it: T) -> Align<T> { Align(Some(Azimuth::Y), it) }
|
||||||
|
|
||||||
|
pub fn align_n <S: Screen, T: Draw<S>> (it: T) -> Align<T> { Align(Some(Azimuth::N), it) }
|
||||||
|
pub fn align_s <S: Screen, T: Draw<S>> (it: T) -> Align<T> { Align(Some(Azimuth::S), it) }
|
||||||
|
pub fn align_e <S: Screen, T: Draw<S>> (it: T) -> Align<T> { Align(Some(Azimuth::E), it) }
|
||||||
|
pub fn align_w <S: Screen, T: Draw<S>> (it: T) -> Align<T> { Align(Some(Azimuth::W), it) }
|
||||||
|
|
||||||
|
pub fn align_ne <S: Screen, T: Draw<S>> (it: T) -> Align<T> { Align(Some(Azimuth::NE), it) }
|
||||||
|
pub fn align_se <S: Screen, T: Draw<S>> (it: T) -> Align<T> { Align(Some(Azimuth::SE), it) }
|
||||||
|
pub fn align_nw <S: Screen, T: Draw<S>> (it: T) -> Align<T> { Align(Some(Azimuth::NW), it) }
|
||||||
|
pub fn align_sw <S: Screen, T: Draw<S>> (it: T) -> Align<T> { Align(Some(Azimuth::SW), it) }
|
||||||
12
src/draw/azimuth.rs
Normal file
12
src/draw/azimuth.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Where is [0, 0] located?
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tengri::draw::Azimuth;
|
||||||
|
/// let _ = Azimuth::NW.align(());
|
||||||
|
/// ```
|
||||||
|
#[cfg_attr(test, derive(Arbitrary))]
|
||||||
|
#[derive(Debug, Copy, Clone, Default)] pub enum Azimuth {
|
||||||
|
#[default] C, X, Y, NW, N, NE, E, SE, S, SW, W
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,7 @@ pub fn rgb_to_okhsl (color: Color) -> Okhsl<f32> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait HasColor { fn color (&self) -> ItemColor; }
|
pub trait HasColor { fn color (&self) -> ItemColor; }
|
||||||
|
|
||||||
#[macro_export] macro_rules! has_color {
|
#[macro_export] macro_rules! has_color {
|
||||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? {
|
impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? {
|
||||||
|
|
@ -42,8 +43,11 @@ pub struct ItemColor {
|
||||||
pub term: Color,
|
pub term: Color,
|
||||||
pub okhsl: Okhsl<f32>
|
pub okhsl: Okhsl<f32>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_from!(ItemColor: |term: Color| Self { term, okhsl: rgb_to_okhsl(term) });
|
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) });
|
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.
|
// A single color within item theme parameters, in OKHSL and RGB representations.
|
||||||
impl ItemColor {
|
impl ItemColor {
|
||||||
#[cfg(feature = "term")] pub const fn from_tui (term: Color) -> Self {
|
#[cfg(feature = "term")] pub const fn from_tui (term: Color) -> Self {
|
||||||
|
|
@ -80,8 +84,11 @@ pub struct ItemTheme {
|
||||||
pub darker: ItemColor,
|
pub darker: ItemColor,
|
||||||
pub darkest: ItemColor,
|
pub darkest: ItemColor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_from!(ItemTheme: |base: ItemColor| Self::from_item_color(base));
|
impl_from!(ItemTheme: |base: ItemColor| Self::from_item_color(base));
|
||||||
|
|
||||||
impl_from!(ItemTheme: |base: Color| Self::from_tui_color(base));
|
impl_from!(ItemTheme: |base: Color| Self::from_tui_color(base));
|
||||||
|
|
||||||
impl ItemTheme {
|
impl ItemTheme {
|
||||||
#[cfg(feature = "term")] pub const G: [Self;256] = {
|
#[cfg(feature = "term")] pub const G: [Self;256] = {
|
||||||
let mut builder = dizzle::konst::array::ArrayBuilder::new();
|
let mut builder = dizzle::konst::array::ArrayBuilder::new();
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
mod align; pub use self::align::*;
|
|
||||||
mod area; pub use self::area::*;
|
|
||||||
mod split; pub use self::split::*;
|
|
||||||
|
|
||||||
pub enum Layout<T: Screen, X: Into<Option<T::Unit>>, I: Draw<T>> {
|
pub enum Layout<T: Screen, X: Into<Option<T::Unit>>, I: Draw<T>> {
|
||||||
__(PhantomData<T>),
|
__(PhantomData<T>),
|
||||||
MinW(X, I),
|
MinW(X, I),
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use Origin::*;
|
use Azimuth::*;
|
||||||
|
|
||||||
impl<N: Coord, T: Xywh<N>> Lrtb<N> for T {}
|
impl<N: Coord, T: Xywh<N>> Lrtb<N> for T {}
|
||||||
|
|
||||||
42
src/draw/origin.rs
Normal file
42
src/draw/origin.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub struct Origin<T>(Option<Azimuth>, T);
|
||||||
|
|
||||||
|
impl_draw!(<S: Screen, T: Draw<S>,>|self: Origin<T>, to: S|{
|
||||||
|
todo!()
|
||||||
|
});
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// use tengri::*;
|
||||||
|
/// let _ = origin(None, "origin unspecified");
|
||||||
|
/// let _ = origin(Azimuth::SE, "southeast");
|
||||||
|
/// let _ = origin(Some(Azimuth::SE), "southeast");
|
||||||
|
/// ```
|
||||||
|
pub fn origin <S: Screen, T: Draw<S>, U: Into<Option<Azimuth>>> (origin: U, it: T) -> Origin<T> {
|
||||||
|
Origin(origin.into(), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn origin_c <S: Screen, T: Draw<S>> (it: T) -> Origin<T> { Origin(Some(Azimuth::C), it) }
|
||||||
|
pub fn origin_x <S: Screen, T: Draw<S>> (it: T) -> Origin<T> { Origin(Some(Azimuth::X), it) }
|
||||||
|
pub fn origin_y <S: Screen, T: Draw<S>> (it: T) -> Origin<T> { Origin(Some(Azimuth::Y), it) }
|
||||||
|
|
||||||
|
pub fn origin_n <S: Screen, T: Draw<S>> (it: T) -> Origin<T> { Origin(Some(Azimuth::N), it) }
|
||||||
|
pub fn origin_s <S: Screen, T: Draw<S>> (it: T) -> Origin<T> { Origin(Some(Azimuth::S), it) }
|
||||||
|
pub fn origin_e <S: Screen, T: Draw<S>> (it: T) -> Origin<T> { Origin(Some(Azimuth::E), it) }
|
||||||
|
pub fn origin_w <S: Screen, T: Draw<S>> (it: T) -> Origin<T> { Origin(Some(Azimuth::W), it) }
|
||||||
|
|
||||||
|
pub fn origin_ne <S: Screen, T: Draw<S>> (it: T) -> Origin<T> { Origin(Some(Azimuth::NE), it) }
|
||||||
|
pub fn origin_se <S: Screen, T: Draw<S>> (it: T) -> Origin<T> { Origin(Some(Azimuth::SE), it) }
|
||||||
|
pub fn origin_nw <S: Screen, T: Draw<S>> (it: T) -> Origin<T> { Origin(Some(Azimuth::NW), it) }
|
||||||
|
pub fn origin_sw <S: Screen, T: Draw<S>> (it: T) -> Origin<T> { Origin(Some(Azimuth::SW), it) }
|
||||||
|
|
||||||
|
/// Something that has `[0, 0]` at a particular point.
|
||||||
|
pub trait HasOrigin {
|
||||||
|
fn origin (&self) -> Azimuth;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<Azimuth>> HasOrigin for T {
|
||||||
|
fn origin (&self) -> Azimuth {
|
||||||
|
*self.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,8 +23,8 @@ impl Sizer {
|
||||||
pub const fn of <T: Screen> (&self, of: impl Draw<T>) -> impl Draw<T> {
|
pub const fn of <T: Screen> (&self, of: impl Draw<T>) -> impl Draw<T> {
|
||||||
thunk(move|to: &mut T|{
|
thunk(move|to: &mut T|{
|
||||||
let area = of.draw(to)?;
|
let area = of.draw(to)?;
|
||||||
self.0.store(area.w().into(), Relaxed);
|
self.0.store(area.map(|a|a.w()).unwrap_or(T::Unit::zero()).into(), Relaxed);
|
||||||
self.1.store(area.h().into(), Relaxed);
|
self.1.store(area.map(|a|a.h()).unwrap_or(T::Unit::zero()).into(), Relaxed);
|
||||||
Ok(area)
|
Ok(area)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
2
src/draw/space.rs
Normal file
2
src/draw/space.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use Azimuth::*;
|
||||||
|
|
||||||
pub const fn east <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
pub const fn east <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
||||||
Split::East.half(a, b)
|
Split::East.half(a, b)
|
||||||
|
|
@ -62,7 +63,7 @@ impl Split {
|
||||||
to.show(area(area_b, b))?;
|
to.show(area(area_b, b))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(to.xywh()) // FIXME: compute and return actually used area
|
Ok(Some(to.xywh())) // FIXME: compute and return actually used area
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,8 +83,8 @@ impl Split {
|
||||||
///
|
///
|
||||||
/// */
|
/// */
|
||||||
/// ```
|
/// ```
|
||||||
const fn origins (&self) -> (Origin, Origin) {
|
const fn origins (&self) -> (Azimuth, Azimuth) {
|
||||||
use Origin::*;
|
use Azimuth::*;
|
||||||
match self {
|
match self {
|
||||||
Self::South => (S, N),
|
Self::South => (S, N),
|
||||||
Self::East => (E, W),
|
Self::East => (E, W),
|
||||||
48
src/draw/thunk.rs
Normal file
48
src/draw/thunk.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts.
|
||||||
|
pub struct Thunk<T: Screen, F>(pub F, std::marker::PhantomData<T>);
|
||||||
|
|
||||||
|
impl<T: Screen, F: FnOnce(&mut T)->Perhaps<XYWH<T::Unit>>> Draw<T> for Thunk<T, F> {
|
||||||
|
fn draw (self, to: &mut T) -> Perhaps<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)->Perhaps<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) })
|
||||||
|
}
|
||||||
|
|
@ -1,29 +1,6 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use Origin::*;
|
|
||||||
use Split::*;
|
use Split::*;
|
||||||
|
|
||||||
/// 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Xy<N: Coord> {
|
pub trait Xy<N: Coord> {
|
||||||
fn x (&self) -> N;
|
fn x (&self) -> N;
|
||||||
fn y (&self) -> N;
|
fn y (&self) -> N;
|
||||||
|
|
@ -262,4 +239,3 @@ pub const fn y_push <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
|
||||||
pub const fn y_pull <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
|
pub const fn y_pull <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
|
||||||
a
|
a
|
||||||
}
|
}
|
||||||
|
|
||||||
56
src/eval.rs
56
src/eval.rs
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{*, term::*, draw::*, space::*, lang::*, ratatui::prelude::*};
|
use crate::{*, lang::*};
|
||||||
|
use ratatui::style::Color;
|
||||||
|
|
||||||
/// Some layout operations exist in XY, X, and Y variants that take 3 or 2 arguments.
|
/// Some layout operations exist in XY, X, and Y variants that take 3 or 2 arguments.
|
||||||
/// Their handling in [eval_view] is uniform and goes like this:
|
/// Their handling in [eval_view] is uniform and goes like this:
|
||||||
|
|
@ -78,7 +79,7 @@ macro_rules! eval_enum ((
|
||||||
pub fn eval_view <'a, O: Screen + 'a, S> (
|
pub fn eval_view <'a, O: Screen + 'a, S> (
|
||||||
state: &S, output: &mut O, expr: &'a impl Expression
|
state: &S, output: &mut O, expr: &'a impl Expression
|
||||||
) -> Perhaps<XYWH<O::Unit>> where
|
) -> Perhaps<XYWH<O::Unit>> where
|
||||||
S: Interpret<O, XYWH<O::Unit>>
|
S: Interpret<O, Option<XYWH<O::Unit>>>
|
||||||
+ for<'b> Namespace<'b, bool>
|
+ for<'b> Namespace<'b, bool>
|
||||||
+ for<'b> Namespace<'b, O::Unit>
|
+ for<'b> Namespace<'b, O::Unit>
|
||||||
+ for<'b> Namespace<'b, Option<O::Unit>>
|
+ for<'b> Namespace<'b, Option<O::Unit>>
|
||||||
|
|
@ -100,35 +101,58 @@ pub fn eval_view <'a, O: Screen + 'a, S> (
|
||||||
let arg2 = tail1.head();
|
let arg2 = tail1.head();
|
||||||
|
|
||||||
// First `frags.next()` calls returns the namespace.
|
// First `frags.next()` calls returns the namespace.
|
||||||
Some(match frags.next() {
|
match frags.next() {
|
||||||
|
|
||||||
Some("when") => when(
|
Some("when") => when(
|
||||||
state.namespace(arg0?)?.unwrap(),
|
state.namespace(arg0?)?.unwrap(),
|
||||||
thunk(move|output: &mut O|state.interpret(output, &arg1))
|
thunk(move|output: &mut O|{
|
||||||
|
state.interpret(output, &arg1)
|
||||||
|
})
|
||||||
).draw(output),
|
).draw(output),
|
||||||
|
|
||||||
Some("either") => either(
|
Some("either") => either(
|
||||||
state.namespace(arg0?)?.unwrap(),
|
state.namespace(arg0?)?.unwrap(),
|
||||||
thunk(move|output: &mut O|state.interpret(output, &arg1)),
|
thunk(move|output: &mut O|{
|
||||||
thunk(move|output: &mut O|state.interpret(output, &arg2)),
|
state.interpret(output, &arg1)
|
||||||
|
}),
|
||||||
|
thunk(move|output: &mut O|{
|
||||||
|
state.interpret(output, &arg2)
|
||||||
|
}),
|
||||||
).draw(output),
|
).draw(output),
|
||||||
|
|
||||||
// Second `frags.next()` calls returns the namespace member.
|
// Second `frags.next()` calls returns the namespace member.
|
||||||
Some("bsp") => {
|
Some("bsp") => {
|
||||||
let direction = eval_enum!("bsp", output, state, frags.next(), arg0, Split {
|
let direction = eval_enum!("bsp", output, state, frags.next(), arg0, Split {
|
||||||
"n" => North, "s" => South, "e" => East, "w" => West, "a" => Above, "b" => Below
|
"n" => North,
|
||||||
|
"s" => South,
|
||||||
|
"e" => East,
|
||||||
|
"w" => West,
|
||||||
|
"a" => Above,
|
||||||
|
"b" => Below
|
||||||
});
|
});
|
||||||
direction.half(
|
direction.half(
|
||||||
thunk(move|output: &mut O|state.interpret(output, &arg0)),
|
thunk(move|output: &mut O|{
|
||||||
thunk(move|output: &mut O|state.interpret(output, &arg1)),
|
state.interpret(output, &arg0)
|
||||||
|
}),
|
||||||
|
thunk(move|output: &mut O|{
|
||||||
|
state.interpret(output, &arg1)
|
||||||
|
}),
|
||||||
).draw(output)
|
).draw(output)
|
||||||
},
|
},
|
||||||
|
|
||||||
Some("align") => {
|
Some("align") => {
|
||||||
let alignment = eval_enum!("align", output, state, frags.next(), arg0, Origin {
|
let alignment = eval_enum!("align", output, state, frags.next(), arg0, Azimuth {
|
||||||
"c" => C, "n" => N, "s" => S, "e" => E, "w" => W, "x" => X, "y" => Y
|
"c" => C,
|
||||||
|
"n" => N,
|
||||||
|
"s" => S,
|
||||||
|
"e" => E,
|
||||||
|
"w" => W,
|
||||||
|
"x" => X,
|
||||||
|
"y" => Y
|
||||||
|
});
|
||||||
|
let content = thunk(move|output: &mut O|{
|
||||||
|
state.interpret(output, &arg0)
|
||||||
});
|
});
|
||||||
let content = thunk(move|output: &mut O|state.interpret(output, &arg0));
|
|
||||||
align(alignment, content).draw(output)
|
align(alignment, content).draw(output)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -154,7 +178,7 @@ pub fn eval_view <'a, O: Screen + 'a, S> (
|
||||||
|
|
||||||
_ => return Ok(None)
|
_ => return Ok(None)
|
||||||
|
|
||||||
}).transpose()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Interpret TUI-specific layout operation.
|
/// Interpret TUI-specific layout operation.
|
||||||
|
|
@ -180,7 +204,7 @@ pub fn eval_view <'a, O: Screen + 'a, S> (
|
||||||
pub fn eval_view_tui <'a, S> (
|
pub fn eval_view_tui <'a, S> (
|
||||||
state: &S, output: &mut Tui, expr: impl Expression + 'a
|
state: &S, output: &mut Tui, expr: impl Expression + 'a
|
||||||
) -> Perhaps<XYWH<u16>> where
|
) -> Perhaps<XYWH<u16>> where
|
||||||
S: Interpret<Tui, XYWH<u16>>
|
S: Interpret<Tui, Option<XYWH<u16>>>
|
||||||
+ for<'b>Namespace<'b, bool>
|
+ for<'b>Namespace<'b, bool>
|
||||||
+ for<'b>Namespace<'b, u16>
|
+ for<'b>Namespace<'b, u16>
|
||||||
+ for<'b>Namespace<'b, Color>
|
+ for<'b>Namespace<'b, Color>
|
||||||
|
|
@ -208,7 +232,7 @@ pub fn eval_view_tui <'a, S> (
|
||||||
output.show(fg(color, thunk(move|output: &mut Tui|{
|
output.show(fg(color, thunk(move|output: &mut Tui|{
|
||||||
state.interpret(output, &arg1)?;
|
state.interpret(output, &arg1)?;
|
||||||
// FIXME?: don't max out the used area?
|
// FIXME?: don't max out the used area?
|
||||||
Ok(output.area().into())
|
Ok(Some(output.area().into()))
|
||||||
})))
|
})))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -218,7 +242,7 @@ pub fn eval_view_tui <'a, S> (
|
||||||
output.show(bg(color, thunk(move|output: &mut Tui|{
|
output.show(bg(color, thunk(move|output: &mut Tui|{
|
||||||
state.interpret(output, &arg1)?;
|
state.interpret(output, &arg1)?;
|
||||||
// FIXME?: don't max out the used area?
|
// FIXME?: don't max out the used area?
|
||||||
Ok(output.area().into())
|
Ok(Some(output.area().into()))
|
||||||
})))
|
})))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// ```
|
|
||||||
/// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Align<T>(Option<Origin>, T);
|
|
||||||
|
|
||||||
impl_draw!(<S: Screen, T: Draw<S>,>|self: Align<T>, to: S|{ todo!() });
|
|
||||||
|
|
@ -18,6 +18,7 @@ pub extern crate unicode_width;
|
||||||
#[cfg(feature = "term")] pub extern crate crossterm;
|
#[cfg(feature = "term")] pub extern crate crossterm;
|
||||||
#[cfg(feature = "lang")] pub extern crate dizzle as lang;
|
#[cfg(feature = "lang")] pub extern crate dizzle as lang;
|
||||||
#[cfg(test)] #[macro_use] pub extern crate proptest;
|
#[cfg(test)] #[macro_use] pub extern crate proptest;
|
||||||
|
#[cfg(test)] pub(crate) use proptest_derive::Arbitrary;
|
||||||
|
|
||||||
pub(crate) use ::{
|
pub(crate) use ::{
|
||||||
atomic_float::AtomicF64,
|
atomic_float::AtomicF64,
|
||||||
|
|
@ -47,9 +48,9 @@ features! {
|
||||||
"time": [ time ],
|
"time": [ time ],
|
||||||
"play": [ exit, task ],
|
"play": [ exit, task ],
|
||||||
"sing": [ sing ],
|
"sing": [ sing ],
|
||||||
"draw": [ draw, space, layout, color ],
|
|
||||||
"text": [ text ],
|
"text": [ text ],
|
||||||
"term": [ term ]
|
"term": [ term ],
|
||||||
|
"draw": [ draw ]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a trait an implement it for various mutation-enabled wrapper types. */
|
/// Define a trait an implement it for various mutation-enabled wrapper types. */
|
||||||
|
|
|
||||||
0
src/origin.rs
Normal file
0
src/origin.rs
Normal file
|
|
@ -1,8 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
#[cfg(test)] use proptest_derive::Arbitrary;
|
|
||||||
|
|
||||||
mod coord; pub use self::coord::*;
|
|
||||||
mod iter; pub use self::iter::*;
|
|
||||||
mod sizer; pub use self::sizer::*;
|
|
||||||
mod xywh; pub use self::xywh::*;
|
|
||||||
mod lrtb; pub use self::lrtb::*;
|
|
||||||
540
src/term.rs
540
src/term.rs
|
|
@ -1,10 +1,15 @@
|
||||||
mod border;
|
use crate::{*, lang::*};
|
||||||
pub use self::border::*;
|
|
||||||
|
mod border; pub use self::border::*;
|
||||||
|
mod event; pub use self::event::*;
|
||||||
|
mod keys; pub use self::keys::*;
|
||||||
|
mod buffer; pub use self::buffer::*;
|
||||||
|
mod input; pub use self::input::*;
|
||||||
|
mod output; pub use self::output::*;
|
||||||
|
|
||||||
use crate::{*, lang::*, draw::*, task::*, exit::*};
|
|
||||||
//use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
|
//use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
|
||||||
//use rand::distributions::uniform::UniformSampler;
|
//use rand::distributions::uniform::UniformSampler;
|
||||||
use ::{
|
pub(crate) use ::{
|
||||||
std::{
|
std::{
|
||||||
io::{stdout, Write},
|
io::{stdout, Write},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
|
|
@ -27,6 +32,74 @@ use ::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Terminal output.
|
||||||
|
pub struct Tui(
|
||||||
|
/// Ratatui buffer; area is screen size
|
||||||
|
pub Buffer,
|
||||||
|
/// Current draw area
|
||||||
|
pub XYWH<u16>
|
||||||
|
);
|
||||||
|
|
||||||
|
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 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) -> Azimuth { Azimuth::NW } }
|
||||||
|
impl Xy<u16> for Tui { fn x (&self) -> u16 { self.1.0 } fn y (&self) -> u16 { self.1.1 } }
|
||||||
|
|
||||||
|
impl Tui {
|
||||||
|
pub fn new (width: u16, height: u16) -> Self {
|
||||||
|
Self(Buffer::empty(Rect { x: 0, y: 0, width, height }), XYWH(0, 0, width, height))
|
||||||
|
}
|
||||||
|
pub fn resize <W: Write> (&mut self, back: &mut CrosstermBackend<W>, width: u16, height: u16) {
|
||||||
|
let size = Rect { x: 0, y: 0, width, height };
|
||||||
|
if self.0.area != size {
|
||||||
|
back.clear_region(ClearType::All).unwrap();
|
||||||
|
self.0.resize(size);
|
||||||
|
self.0.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn redraw <'b, W: Write> (&'b mut self, back: &mut CrosstermBackend<W>, mut next: &'b mut Self) {
|
||||||
|
let updates = self.0.diff(&next.0);
|
||||||
|
back.draw(updates.into_iter()).expect("failed to render");
|
||||||
|
Backend::flush(back).expect("failed to flush output new");
|
||||||
|
std::mem::swap(self, &mut next);
|
||||||
|
next.0.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tui {
|
||||||
|
pub 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()
|
||||||
|
}
|
||||||
|
pub fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) {
|
||||||
|
for cell in self.0.content.iter_mut() {
|
||||||
|
cell.fg = fg;
|
||||||
|
cell.bg = bg;
|
||||||
|
cell.modifier = modifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub 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());
|
||||||
|
if x < self.0.area.width && y < self.0.area.height {
|
||||||
|
self.0.set_string(x, y, text, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Implement standard [main] entrypoint for TUI apps.
|
/// Implement standard [main] entrypoint for TUI apps.
|
||||||
#[macro_export] macro_rules! tui_main {
|
#[macro_export] macro_rules! tui_main {
|
||||||
($state:expr) => {
|
($state:expr) => {
|
||||||
|
|
@ -34,24 +107,6 @@ use ::{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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
|
pub fn tui_run_main <T> (state: Arc<RwLock<T>>) -> Usually<()> where
|
||||||
T: View<Tui> + Apply<TuiEvent, Usually<()>> + Send + Sync + 'static
|
T: View<Tui> + Apply<TuiEvent, Usually<()>> + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
|
|
@ -98,444 +153,3 @@ pub fn tui_io <
|
||||||
let terminal = tui_output(exited, state, sleep, output)?;
|
let terminal = tui_output(exited, state, sleep, output)?;
|
||||||
Ok((keyboard, terminal))
|
Ok((keyboard, terminal))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawn the TUI input thread which reads keys from the terminal.
|
|
||||||
pub fn tui_input <T: Apply<TuiEvent, Usually<()>> + Send + Sync + 'static> (
|
|
||||||
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
|
|
||||||
) -> Result<Task, std::io::Error> {
|
|
||||||
let exited = exited.clone();
|
|
||||||
let state = state.clone();
|
|
||||||
Task::new_poll(exited.clone(), poll, move |_| {
|
|
||||||
let event = read().unwrap();
|
|
||||||
match event {
|
|
||||||
|
|
||||||
// Hardcoded exit.
|
|
||||||
Event::Key(KeyEvent {
|
|
||||||
modifiers: KeyModifiers::CONTROL,
|
|
||||||
code: KeyCode::Char('c'),
|
|
||||||
kind: KeyEventKind::Press,
|
|
||||||
state: KeyEventState::NONE
|
|
||||||
}) => { exited.store(true, Relaxed); },
|
|
||||||
|
|
||||||
// Handle all other events by the state:
|
|
||||||
event => {
|
|
||||||
if let Err(e) = state.write().unwrap().apply(&TuiEvent(event)) {
|
|
||||||
panic!("{e}")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Spawn the TUI output thread which writes colored characters to the terminal.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let state = Arc::new(RwLock::new(()));
|
|
||||||
/// Exit::run(|exit|tui_output(exit, state, sleep, stdout()))
|
|
||||||
/// ```
|
|
||||||
pub fn tui_output <
|
|
||||||
W: Write + Send + Sync + 'static, T: View<Tui> + Send + Sync + 'static
|
|
||||||
> (
|
|
||||||
exited: &Arc<AtomicBool>,
|
|
||||||
state: &Arc<RwLock<T>>,
|
|
||||||
sleep: Duration,
|
|
||||||
output: W,
|
|
||||||
) -> Usually<Task> {
|
|
||||||
let state = state.clone();
|
|
||||||
stdout().execute(EnterAlternateScreen)?;
|
|
||||||
CrosstermBackend::new(stdout()).hide_cursor()?;
|
|
||||||
enable_raw_mode()?;
|
|
||||||
let mut backend = CrosstermBackend::new(output);
|
|
||||||
let Size { width, height } = backend.size().expect("get size failed");
|
|
||||||
let mut prev = Tui::new(width, height);
|
|
||||||
let mut next = Tui::new(width, height);
|
|
||||||
Ok(Task::new_sleep(exited.clone(), sleep, move |perf| {
|
|
||||||
let Size { width, height } = backend.size().expect("get size failed");
|
|
||||||
if let Ok(state) = state.try_read() {
|
|
||||||
prev.resize(&mut backend, width, height);
|
|
||||||
state.view().draw(&mut next).expect("draw failed"); // TODO draw error
|
|
||||||
prev.redraw(&mut backend, &mut next);
|
|
||||||
}
|
|
||||||
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
|
|
||||||
prev.set_string(0, 0, &timer, Style::default());
|
|
||||||
})?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Terminal output.
|
|
||||||
pub struct Tui(
|
|
||||||
/// Ratatui buffer; area is screen size
|
|
||||||
pub Buffer,
|
|
||||||
/// Current draw area
|
|
||||||
pub XYWH<u16>
|
|
||||||
);
|
|
||||||
|
|
||||||
impl Tui {
|
|
||||||
pub fn new (width: u16, height: u16) -> Self {
|
|
||||||
Self(Buffer::empty(Rect { x: 0, y: 0, width, height }), XYWH(0, 0, width, height))
|
|
||||||
}
|
|
||||||
pub fn resize <W: Write> (&mut self, back: &mut CrosstermBackend<W>, width: u16, height: u16) {
|
|
||||||
let size = Rect { x: 0, y: 0, width, height };
|
|
||||||
if self.0.area != size {
|
|
||||||
back.clear_region(ClearType::All).unwrap();
|
|
||||||
self.0.resize(size);
|
|
||||||
self.0.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn redraw <'b, W: Write> (&'b mut self, back: &mut CrosstermBackend<W>, mut next: &'b mut Self) {
|
|
||||||
let updates = self.0.diff(&next.0);
|
|
||||||
back.draw(updates.into_iter()).expect("failed to render");
|
|
||||||
Backend::flush(back).expect("failed to flush output new");
|
|
||||||
std::mem::swap(self, &mut next);
|
|
||||||
next.0.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Screen for Tui {
|
|
||||||
type Unit = u16;
|
|
||||||
/// Render drawable in subarea specified by `area`
|
|
||||||
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;
|
|
||||||
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 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 Xy<u16> for Tui {
|
|
||||||
fn x (&self) -> u16 { self.1.0 }
|
|
||||||
fn y (&self) -> u16 { self.1.1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Tui {
|
|
||||||
pub 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()
|
|
||||||
}
|
|
||||||
pub fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) {
|
|
||||||
for cell in self.0.content.iter_mut() {
|
|
||||||
cell.fg = fg;
|
|
||||||
cell.bg = bg;
|
|
||||||
cell.modifier = modifier;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub 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());
|
|
||||||
if x < self.0.area.width && y < self.0.area.height {
|
|
||||||
self.0.set_string(x, y, text, style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apply foreground color.
|
|
||||||
pub const fn fg (fg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
|
||||||
thunk(move|to: &mut Tui|{
|
|
||||||
to.update(&|cell,_,_|{ cell.set_fg(fg); });
|
|
||||||
draw.draw(to)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apply background color.
|
|
||||||
pub const fn bg (bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
|
||||||
thunk(move|to: &mut Tui|{
|
|
||||||
to.update(&|cell,_,_|{ cell.set_bg(bg); });
|
|
||||||
draw.draw(to)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn fg_bg (fg: Color, bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
|
||||||
thunk(move|to: &mut Tui|{
|
|
||||||
to.update(&|cell,_,_|{ cell.set_fg(fg); cell.set_bg(bg); });
|
|
||||||
draw.draw(to)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn fill_char (c: char) -> impl Draw<Tui> {
|
|
||||||
thunk(move|to: &mut Tui|Ok(to.update(&|cell,_,_|{
|
|
||||||
cell.set_char(c);
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw contents with modifier applied.
|
|
||||||
pub const fn modify (on: bool, modifier: Modifier, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
|
||||||
thunk(move|to: &mut Tui|{
|
|
||||||
fill_mod(on, modifier).draw(to)?;
|
|
||||||
draw.draw(to)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (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.modifier.remove(Modifier::UNDERLINED);
|
|
||||||
cell.underline_color = Reset;
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TUI works in u16 coordinates.
|
|
||||||
impl Coord for u16 {
|
|
||||||
fn plus (self, other: Self) -> Self {
|
|
||||||
self.saturating_add(other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_draw!(|self: u64, _to: Tui|{ todo!() });
|
|
||||||
impl_draw!(|self: f64, _to: Tui|{ todo!() });
|
|
||||||
|
|
||||||
mod phat {
|
|
||||||
use super::*;
|
|
||||||
pub const LO: &'static str = "▄";
|
|
||||||
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)))
|
|
||||||
}
|
|
||||||
/// 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)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod scroll {
|
|
||||||
pub const ICON_DEC_V: &[char] = &['▲'];
|
|
||||||
pub const ICON_INC_V: &[char] = &['▼'];
|
|
||||||
pub const ICON_DEC_H: &[char] = &[' ', '🞀', ' '];
|
|
||||||
pub const ICON_INC_H: &[char] = &[' ', '🞂', ' '];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn x_repeat (c: &str) -> impl Draw<Tui> {
|
|
||||||
thunk(move|to: &mut Tui|{
|
|
||||||
let XYWH(x, y, w, h) = to.xywh();
|
|
||||||
for x in x..x+w {
|
|
||||||
if let Some(cell) = to.0.cell_mut(Position::from((x, y))) {
|
|
||||||
cell.set_symbol(&c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(XYWH(x, y, w, 1))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn y_repeat (c: &str) -> impl Draw<Tui> {
|
|
||||||
thunk(move|to: &mut Tui|{
|
|
||||||
let XYWH(x, y, w, h) = to.xywh();
|
|
||||||
for y in y..y+h {
|
|
||||||
if let Some(cell) = to.0.cell_mut(Position::from((x, y))) {
|
|
||||||
cell.set_symbol(&c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(XYWH(x, y, 1, h))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn xy_repeat (c: &str) -> impl Draw<Tui> {
|
|
||||||
thunk(move|to: &mut Tui|{
|
|
||||||
let XYWH(x, y, w, h) = to.xywh();
|
|
||||||
let a = c.len();
|
|
||||||
for (_v, y) in (y..y+h).enumerate() {
|
|
||||||
for (u, x) in (x..x+w).enumerate() {
|
|
||||||
if let Some(cell) = to.0.cell_mut(Position::from((x, y))) {
|
|
||||||
let u = u % a;
|
|
||||||
cell.set_symbol(&c[u..u+1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(XYWH(x, y, w, h))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ```
|
|
||||||
/// let _ = tengri::button_2("", "", true);
|
|
||||||
/// let _ = tengri::button_2("", "", false);
|
|
||||||
/// ```
|
|
||||||
pub const fn button_2 <'a> (key: impl Draw<Tui>, label: impl Draw<Tui>, hide: bool) -> impl Draw<Tui> {
|
|
||||||
let c1 = tui_orange();
|
|
||||||
let c2 = tui_g(0);
|
|
||||||
let c3 = tui_g(96);
|
|
||||||
let c4 = tui_g(255);
|
|
||||||
bold(true, fg_bg(c1, c2, east(fg(c2, east(key, fg(c3, "▐"))), when(!hide, fg_bg(c4, c3, label)))))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ```
|
|
||||||
/// let _ = tengri::button_3("", "", "", true);
|
|
||||||
/// let _ = tengri::button_3("", "", "", false);
|
|
||||||
/// ```
|
|
||||||
pub const fn button_3 <'a> (
|
|
||||||
key: impl Draw<Tui>, label: impl Draw<Tui>, value: impl Draw<Tui>, editing: bool,
|
|
||||||
) -> impl Draw<Tui> {
|
|
||||||
bold(true, east(
|
|
||||||
fg_bg(tui_orange(), tui_g(0),
|
|
||||||
east(fg(tui_g(0), "▐"), east(key, fg(if editing { tui_g(128) } else { tui_g(96) }, "▐")))),
|
|
||||||
east(
|
|
||||||
when(!editing, east(fg_bg(tui_g(255), tui_g(96), label), fg_bg(tui_g(128), tui_g(96), "▐"),)),
|
|
||||||
east(fg_bg(tui_g(224), tui_g(128), value), fg_bg(tui_g(128), Reset, "▌"), ))))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stackably padded.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// /// TODO
|
|
||||||
/// ```
|
|
||||||
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(Some(w), Some(h), south(top, north(low, draw)))
|
|
||||||
}
|
|
||||||
|
|
||||||
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()) {
|
|
||||||
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]);
|
|
||||||
} else if false {
|
|
||||||
cell.set_fg(Rgb(255, 255, 255));
|
|
||||||
cell.set_bg(Reset);
|
|
||||||
cell.set_char('━');
|
|
||||||
} else {
|
|
||||||
cell.set_fg(Rgb(0, 0, 0));
|
|
||||||
cell.set_bg(Reset);
|
|
||||||
cell.set_char('╌');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(XYWH(*x1, *y1, *w, 1))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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()) {
|
|
||||||
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]);
|
|
||||||
} else if false {
|
|
||||||
cell.set_fg(Rgb(255, 255, 255));
|
|
||||||
cell.set_bg(Reset);
|
|
||||||
cell.set_char('‖'); // ━
|
|
||||||
} else {
|
|
||||||
cell.set_fg(Rgb(0, 0, 0));
|
|
||||||
cell.set_bg(Reset);
|
|
||||||
cell.set_char('╎'); // ━
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(XYWH(*x1, *y1, 1, *h))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tui_update (
|
|
||||||
Tui(buf, ..): &mut Tui,
|
|
||||||
area: XYWH<u16>,
|
|
||||||
callback: &impl Fn(&mut Cell, u16, u16)
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw TUI content or its error message.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let _ = tengri::term::catcher(Ok(Some("hello")));
|
|
||||||
/// let _ = tengri::term::catcher(Ok(None));
|
|
||||||
/// let _ = tengri::term::catcher(Err("draw fail".into()));
|
|
||||||
/// ```
|
|
||||||
pub fn catcher <T: Draw<Tui>> (result: Usually<T>) -> impl Draw<Tui> {
|
|
||||||
thunk(move|to: &mut Tui|match result {
|
|
||||||
Ok(content) => content.draw(to),
|
|
||||||
Err(e) => {
|
|
||||||
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))).draw(to)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
pub const fn tui_color_bg () -> Color { Color::Rgb(28, 35, 25) }
|
|
||||||
pub const fn tui_bg0 () -> Color { Color::Rgb(20, 20, 20) }
|
|
||||||
pub const fn tui_bo1 () -> Color { Color::Rgb(100, 110, 40) }
|
|
||||||
pub const fn tui_bo2 () -> Color { Color::Rgb(70, 80, 50) }
|
|
||||||
pub const fn tui_border_bg () -> Color { Color::Rgb(40, 50, 30) }
|
|
||||||
pub const fn tui_border_fg (f: bool) -> Color { if f { tui_bo1() } else { tui_bo2() } }
|
|
||||||
pub const fn tui_brown () -> Color { Color::Rgb(128,255,0) }
|
|
||||||
pub const fn tui_electric () -> Color { Color::Rgb(0,255,128) }
|
|
||||||
pub const fn tui_g (g: u8) -> Color { Color::Rgb(g, g, g) }
|
|
||||||
pub const fn tui_green () -> Color { Color::Rgb(0,255,0) }
|
|
||||||
pub const fn tui_mode_bg () -> Color { Color::Rgb(150, 160, 90) }
|
|
||||||
pub const fn tui_mode_fg () -> Color { Color::Rgb(255, 255, 255) }
|
|
||||||
pub const fn tui_null () -> Color { Color::Reset }
|
|
||||||
pub const fn tui_orange () -> Color { Color::Rgb(255,128,0) }
|
|
||||||
pub const fn tui_red () -> Color { Color::Rgb(255,0, 0) }
|
|
||||||
pub const fn tui_separator_fg (_: bool) -> Color { Color::Rgb(0, 0, 0) }
|
|
||||||
pub const fn tui_status_bar_bg () -> Color { Color::Rgb(28, 35, 25) }
|
|
||||||
pub const fn tui_ti1 () -> Color { Color::Rgb(150, 160, 90) }
|
|
||||||
pub const fn tui_ti2 () -> Color { Color::Rgb(120, 130, 100) }
|
|
||||||
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) }
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ macro_rules! border {
|
||||||
//impl Layout<Tui> for $T {}
|
//impl Layout<Tui> for $T {}
|
||||||
impl Draw<Tui> for $T {
|
impl Draw<Tui> for $T {
|
||||||
fn draw (self, to: &mut Tui) -> Perhaps<XYWH<u16>> {
|
fn draw (self, to: &mut Tui) -> Perhaps<XYWH<u16>> {
|
||||||
when(self.enabled(), thunk(|to: &mut Tui|BorderStyle::draw(self, to))).draw(to)
|
when(self.enabled(), thunk(|to: &mut Tui|BorderStyle::draw(self, to).map(Some))).draw(to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)+}
|
)+}
|
||||||
|
|
|
||||||
39
src/term/input.rs
Normal file
39
src/term/input.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
use crate::{*, lang::*};
|
||||||
|
|
||||||
|
/// 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 thread which reads keys from the terminal.
|
||||||
|
pub fn tui_input <T: Apply<TuiEvent, Usually<()>> + Send + Sync + 'static> (
|
||||||
|
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
|
||||||
|
) -> Result<Task, std::io::Error> {
|
||||||
|
let exited = exited.clone();
|
||||||
|
let state = state.clone();
|
||||||
|
Task::new_poll(exited.clone(), poll, move |_| {
|
||||||
|
let event = read().unwrap();
|
||||||
|
match event {
|
||||||
|
|
||||||
|
// Hardcoded exit.
|
||||||
|
Event::Key(KeyEvent {
|
||||||
|
modifiers: KeyModifiers::CONTROL,
|
||||||
|
code: KeyCode::Char('c'),
|
||||||
|
kind: KeyEventKind::Press,
|
||||||
|
state: KeyEventState::NONE
|
||||||
|
}) => { exited.store(true, Relaxed); },
|
||||||
|
|
||||||
|
// Handle all other events by the state:
|
||||||
|
event => {
|
||||||
|
if let Err(e) = state.write().unwrap().apply(&TuiEvent(event)) {
|
||||||
|
panic!("{e}")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
343
src/term/output.rs
Normal file
343
src/term/output.rs
Normal file
|
|
@ -0,0 +1,343 @@
|
||||||
|
use crate::{*, lang::*};
|
||||||
|
use ratatui::{prelude::{Style, Position, Backend, Color}};
|
||||||
|
|
||||||
|
/// TUI works in u16 coordinates.
|
||||||
|
impl Coord for u16 { fn plus (self, other: Self) -> Self { self.saturating_add(other) } }
|
||||||
|
impl_draw!(|self: u64, _to: Tui|{ todo!() });
|
||||||
|
impl_draw!(|self: f64, _to: Tui|{ todo!() });
|
||||||
|
|
||||||
|
impl Screen for Tui {
|
||||||
|
type Unit = u16;
|
||||||
|
/// Render drawable in subarea specified by `area`
|
||||||
|
fn show <'t, T: Draw<Self>> (&mut self, content: T) -> Perhaps<XYWH<u16>> {
|
||||||
|
let previous_area = self.1;
|
||||||
|
Ok(if let Some(area) = content.layout(self.1)? {
|
||||||
|
self.1 = area;
|
||||||
|
if let Some(result_area) = content.draw(self)? {
|
||||||
|
self.1 = previous_area;
|
||||||
|
Some(result_area)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let state = Arc::new(RwLock::new(()));
|
||||||
|
/// Exit::run(|exit|tui_output(exit, state, sleep, stdout()))
|
||||||
|
/// ```
|
||||||
|
pub fn tui_output <
|
||||||
|
W: Write + Send + Sync + 'static, T: View<Tui> + Send + Sync + 'static
|
||||||
|
> (
|
||||||
|
exited: &Arc<AtomicBool>,
|
||||||
|
state: &Arc<RwLock<T>>,
|
||||||
|
sleep: Duration,
|
||||||
|
output: W,
|
||||||
|
) -> Usually<Task> {
|
||||||
|
let state = state.clone();
|
||||||
|
stdout().execute(EnterAlternateScreen)?;
|
||||||
|
CrosstermBackend::new(stdout()).hide_cursor()?;
|
||||||
|
enable_raw_mode()?;
|
||||||
|
let mut backend = CrosstermBackend::new(output);
|
||||||
|
let Size { width, height } = backend.size().expect("get size failed");
|
||||||
|
let mut prev = Tui::new(width, height);
|
||||||
|
let mut next = Tui::new(width, height);
|
||||||
|
Ok(Task::new_sleep(exited.clone(), sleep, move |perf| {
|
||||||
|
let Size { width, height } = backend.size().expect("get size failed");
|
||||||
|
if let Ok(state) = state.try_read() {
|
||||||
|
prev.resize(&mut backend, width, height);
|
||||||
|
state.view().draw(&mut next).expect("draw failed"); // TODO draw error
|
||||||
|
prev.redraw(&mut backend, &mut next);
|
||||||
|
}
|
||||||
|
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
|
||||||
|
prev.set_string(0, 0, &timer, Style::default());
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
use self::colors::*; mod colors {
|
||||||
|
use ratatui::prelude::Color;
|
||||||
|
pub const fn tui_color_bg () -> Color { Color::Rgb(28, 35, 25) }
|
||||||
|
pub const fn tui_bg0 () -> Color { Color::Rgb(20, 20, 20) }
|
||||||
|
pub const fn tui_bo1 () -> Color { Color::Rgb(100, 110, 40) }
|
||||||
|
pub const fn tui_bo2 () -> Color { Color::Rgb(70, 80, 50) }
|
||||||
|
pub const fn tui_border_bg () -> Color { Color::Rgb(40, 50, 30) }
|
||||||
|
pub const fn tui_border_fg (f: bool) -> Color { if f { tui_bo1() } else { tui_bo2() } }
|
||||||
|
pub const fn tui_brown () -> Color { Color::Rgb(128,255,0) }
|
||||||
|
pub const fn tui_electric () -> Color { Color::Rgb(0,255,128) }
|
||||||
|
pub const fn tui_g (g: u8) -> Color { Color::Rgb(g, g, g) }
|
||||||
|
pub const fn tui_green () -> Color { Color::Rgb(0,255,0) }
|
||||||
|
pub const fn tui_mode_bg () -> Color { Color::Rgb(150, 160, 90) }
|
||||||
|
pub const fn tui_mode_fg () -> Color { Color::Rgb(255, 255, 255) }
|
||||||
|
pub const fn tui_null () -> Color { Color::Reset }
|
||||||
|
pub const fn tui_orange () -> Color { Color::Rgb(255,128,0) }
|
||||||
|
pub const fn tui_red () -> Color { Color::Rgb(255,0, 0) }
|
||||||
|
pub const fn tui_separator_fg (_: bool) -> Color { Color::Rgb(0, 0, 0) }
|
||||||
|
pub const fn tui_status_bar_bg () -> Color { Color::Rgb(28, 35, 25) }
|
||||||
|
pub const fn tui_ti1 () -> Color { Color::Rgb(150, 160, 90) }
|
||||||
|
pub const fn tui_ti2 () -> Color { Color::Rgb(120, 130, 100) }
|
||||||
|
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) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply foreground color.
|
||||||
|
pub const fn fg (fg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
|
thunk(move|to: &mut Tui|{
|
||||||
|
to.update(&|cell,_,_|{ cell.set_fg(fg); });
|
||||||
|
draw.draw(to)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply background color.
|
||||||
|
pub const fn bg (bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
|
thunk(move|to: &mut Tui|{
|
||||||
|
to.update(&|cell,_,_|{ cell.set_bg(bg); });
|
||||||
|
draw.draw(to)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn fg_bg (fg: Color, bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
|
thunk(move|to: &mut Tui|{
|
||||||
|
to.update(&|cell,_,_|{ cell.set_fg(fg); cell.set_bg(bg); });
|
||||||
|
draw.draw(to)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn fill_char (c: char) -> impl Draw<Tui> {
|
||||||
|
thunk(move|to: &mut Tui|Ok(Some(to.update(&|cell,_,_|{
|
||||||
|
cell.set_char(c);
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw contents with modifier applied.
|
||||||
|
pub const fn modify (on: bool, modifier: Modifier, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
|
thunk(move|to: &mut Tui|{
|
||||||
|
fill_mod(on, modifier).draw(to)?;
|
||||||
|
draw.draw(to)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn fill_mod (on: bool, modifier: Modifier) -> impl Draw<Tui> {
|
||||||
|
thunk(move|to: &mut Tui|Ok(Some({
|
||||||
|
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 (color: Option<Color>) -> impl Draw<Tui> {
|
||||||
|
thunk(move|to: &mut Tui|Ok(Some(if let Some(color) = color {
|
||||||
|
to.update(&|cell,_,_|{
|
||||||
|
cell.modifier.insert(Modifier::UNDERLINED);
|
||||||
|
cell.underline_color = color;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
to.update(&|cell,_,_|{
|
||||||
|
cell.modifier.remove(Modifier::UNDERLINED);
|
||||||
|
cell.underline_color = Reset;
|
||||||
|
})
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
mod phat {
|
||||||
|
use super::*;
|
||||||
|
pub const LO: &'static str = "▄";
|
||||||
|
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)))
|
||||||
|
}
|
||||||
|
/// 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)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod scroll {
|
||||||
|
pub const ICON_DEC_V: &[char] = &['▲'];
|
||||||
|
pub const ICON_INC_V: &[char] = &['▼'];
|
||||||
|
pub const ICON_DEC_H: &[char] = &[' ', '🞀', ' '];
|
||||||
|
pub const ICON_INC_H: &[char] = &[' ', '🞂', ' '];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn x_repeat (c: &str) -> impl Draw<Tui> {
|
||||||
|
thunk(move|to: &mut Tui|{
|
||||||
|
let XYWH(x, y, w, _h) = to.xywh();
|
||||||
|
for x in x..x+w {
|
||||||
|
if let Some(cell) = to.0.cell_mut(Position::from((x, y))) {
|
||||||
|
cell.set_symbol(&c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(XYWH(x, y, w, 1)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn y_repeat (c: &str) -> impl Draw<Tui> {
|
||||||
|
thunk(move|to: &mut Tui|{
|
||||||
|
let XYWH(x, y, _w, h) = to.xywh();
|
||||||
|
for y in y..y+h {
|
||||||
|
if let Some(cell) = to.0.cell_mut(Position::from((x, y))) {
|
||||||
|
cell.set_symbol(&c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(XYWH(x, y, 1, h)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn xy_repeat (c: &str) -> impl Draw<Tui> {
|
||||||
|
thunk(move|to: &mut Tui|{
|
||||||
|
let XYWH(x, y, w, h) = to.xywh();
|
||||||
|
let a = c.len();
|
||||||
|
for (_v, y) in (y..y+h).enumerate() {
|
||||||
|
for (u, x) in (x..x+w).enumerate() {
|
||||||
|
if let Some(cell) = to.0.cell_mut(Position::from((x, y))) {
|
||||||
|
let u = u % a;
|
||||||
|
cell.set_symbol(&c[u..u+1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(XYWH(x, y, w, h)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// let _ = tengri::button_2("", "", true);
|
||||||
|
/// let _ = tengri::button_2("", "", false);
|
||||||
|
/// ```
|
||||||
|
pub const fn button_2 <'a> (key: impl Draw<Tui>, label: impl Draw<Tui>, hide: bool) -> impl Draw<Tui> {
|
||||||
|
let c1 = tui_orange();
|
||||||
|
let c2 = tui_g(0);
|
||||||
|
let c3 = tui_g(96);
|
||||||
|
let c4 = tui_g(255);
|
||||||
|
bold(true, fg_bg(c1, c2, east(fg(c2, east(key, fg(c3, "▐"))), when(!hide, fg_bg(c4, c3, label)))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// let _ = tengri::button_3("", "", "", true);
|
||||||
|
/// let _ = tengri::button_3("", "", "", false);
|
||||||
|
/// ```
|
||||||
|
pub const fn button_3 <'a> (
|
||||||
|
key: impl Draw<Tui>, label: impl Draw<Tui>, value: impl Draw<Tui>, editing: bool,
|
||||||
|
) -> impl Draw<Tui> {
|
||||||
|
bold(true, east(
|
||||||
|
fg_bg(tui_orange(), tui_g(0),
|
||||||
|
east(fg(tui_g(0), "▐"), east(key, fg(if editing { tui_g(128) } else { tui_g(96) }, "▐")))),
|
||||||
|
east(
|
||||||
|
when(!editing, east(fg_bg(tui_g(255), tui_g(96), label), fg_bg(tui_g(128), tui_g(96), "▐"),)),
|
||||||
|
east(fg_bg(tui_g(224), tui_g(128), value), fg_bg(tui_g(128), Reset, "▌"), ))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stackably padded.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// /// TODO
|
||||||
|
/// ```
|
||||||
|
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(Some(w), Some(h), south(top, north(low, draw)))
|
||||||
|
}
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
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]);
|
||||||
|
} else if false {
|
||||||
|
cell.set_fg(Rgb(255, 255, 255));
|
||||||
|
cell.set_bg(Reset);
|
||||||
|
cell.set_char('━');
|
||||||
|
} else {
|
||||||
|
cell.set_fg(Rgb(0, 0, 0));
|
||||||
|
cell.set_bg(Reset);
|
||||||
|
cell.set_char('╌');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(XYWH(*x1, *y1, *w, 1)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
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]);
|
||||||
|
} else if false {
|
||||||
|
cell.set_fg(Rgb(255, 255, 255));
|
||||||
|
cell.set_bg(Reset);
|
||||||
|
cell.set_char('‖'); // ━
|
||||||
|
} else {
|
||||||
|
cell.set_fg(Rgb(0, 0, 0));
|
||||||
|
cell.set_bg(Reset);
|
||||||
|
cell.set_char('╎'); // ━
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(XYWH(*x1, *y1, 1, *h)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tui_update (
|
||||||
|
Tui(buf, ..): &mut Tui,
|
||||||
|
area: XYWH<u16>,
|
||||||
|
callback: &impl Fn(&mut Cell, u16, u16)
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw TUI content or its error message.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let _ = tengri::term::catcher(Ok(Some("hello")));
|
||||||
|
/// let _ = tengri::term::catcher(Ok(None));
|
||||||
|
/// let _ = tengri::term::catcher(Err("draw fail".into()));
|
||||||
|
/// ```
|
||||||
|
pub fn catcher <T: Draw<Tui>> (result: Usually<T>) -> impl Draw<Tui> {
|
||||||
|
thunk(move|to: &mut Tui|match result {
|
||||||
|
Ok(content) => content.draw(to),
|
||||||
|
Err(e) => {
|
||||||
|
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))).draw(to)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Usually, draw::Draw, space::*};
|
use crate::*;
|
||||||
pub(crate) use ::unicode_width::*;
|
pub(crate) use ::unicode_width::*;
|
||||||
|
|
||||||
#[cfg(feature = "term")] mod impl_term {
|
#[cfg(feature = "term")] mod impl_term {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue