mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-04-03 13:30:44 +02:00
wip: refactor(tengri): routines!
This commit is contained in:
parent
8f0a2accce
commit
4e8d58d793
9 changed files with 1495 additions and 1669 deletions
|
|
@ -5,10 +5,11 @@ version = "0.15.0"
|
|||
description = "UI metaframework."
|
||||
|
||||
[features]
|
||||
default = ["lang", "sing", "draw", "tui"]
|
||||
default = ["lang", "sing", "draw", "play", "tui"]
|
||||
lang = ["dep:dizzle"]
|
||||
sing = ["dep:jack"]
|
||||
draw = []
|
||||
play = []
|
||||
tui = ["draw", "dep:ratatui", "dep:crossterm"]
|
||||
gui = ["draw", "dep:winit"]
|
||||
|
||||
|
|
|
|||
666
src/draw.rs
666
src/draw.rs
|
|
@ -1,8 +1,29 @@
|
|||
use crate::*;
|
||||
|
||||
/// A cardinal direction.
|
||||
///
|
||||
/// ```
|
||||
/// let direction = tengri::Direction::Above;
|
||||
/// ```
|
||||
#[cfg_attr(test, derive(Arbitrary))]
|
||||
#[derive(Copy, Clone, PartialEq, Debug)] pub enum Direction {
|
||||
North, South, East, West, Above, Below
|
||||
}
|
||||
|
||||
/// 9th of area to place.
|
||||
///
|
||||
/// ```
|
||||
/// let alignment = tengri::Alignment::Center;
|
||||
/// ```
|
||||
#[cfg_attr(test, derive(Arbitrary))]
|
||||
#[derive(Debug, Copy, Clone, Default)] pub enum Alignment {
|
||||
#[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W
|
||||
}
|
||||
|
||||
/// A numeric type that can be used as coordinate.
|
||||
///
|
||||
/// FIXME: Replace this ad-hoc trait with `num` crate.
|
||||
/// FIXME: Replace with `num` crate?
|
||||
/// FIXME: Use AsRef/AsMut?
|
||||
pub trait Coord: Send + Sync + Copy
|
||||
+ Add<Self, Output=Self>
|
||||
+ Sub<Self, Output=Self>
|
||||
|
|
@ -16,6 +37,7 @@ pub trait Coord: Send + Sync + Copy
|
|||
{
|
||||
fn zero () -> Self { 0.into() }
|
||||
fn plus (self, other: Self) -> Self;
|
||||
/// Saturating subtraction
|
||||
fn minus (self, other: Self) -> Self { if self >= other { self - other } else { 0.into() } }
|
||||
fn atomic (self) -> AtomicUsize { AtomicUsize::new(self.into()) }
|
||||
}
|
||||
|
|
@ -26,32 +48,103 @@ pub trait X<N: Coord> { fn x (&self) -> N { N::zero() } }
|
|||
/// Point along vertical axis.
|
||||
pub trait Y<N: Coord> { fn y (&self) -> N { N::zero() } }
|
||||
|
||||
/// Point along (X, Y).
|
||||
pub trait Point<N: Coord>: X<N> + Y<N> { fn xy (&self) -> XY<N> { XY(self.x(), self.y()) } }
|
||||
|
||||
/// Length along horizontal axis.
|
||||
pub trait W<N: Coord> { fn w (&self) -> N { N::zero() } }
|
||||
pub trait W<N: Coord> {
|
||||
fn w (&self) -> N { N::zero() }
|
||||
fn w_min (&self) -> N { self.w() }
|
||||
fn w_max (&self) -> N { self.w() }
|
||||
}
|
||||
|
||||
/// Length along vertical axis.
|
||||
pub trait H<N: Coord> { fn h (&self) -> N { N::zero() } }
|
||||
pub trait H<N: Coord> {
|
||||
fn h (&self) -> N { N::zero() }
|
||||
fn h_min (&self) -> N { self.h() }
|
||||
fn h_max (&self) -> N { self.h() }
|
||||
}
|
||||
|
||||
/// Area in 2D space.
|
||||
pub trait Area<N: Coord>: W<N> + H<N> {
|
||||
fn wh (&self) -> WH<N> { WH(self.w(), self.h()) }
|
||||
fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] }
|
||||
fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] }
|
||||
fn at_least (&self, w: N, h: N) -> Usually<&Self> {
|
||||
if self.w() < w || self.h() < h { return Err(format!("min {w}x{h}").into()) }
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Which corner/side of a box is 0, 0
|
||||
pub trait Anchor { fn anchor (&self) -> Alignment; }
|
||||
|
||||
/// Area along (X, Y).
|
||||
pub trait Area<N: Coord>: W<N> + H<N> { fn wh (&self) -> WH<N> { WH(self.w(), self.h()) } }
|
||||
/// Bounding box
|
||||
pub trait Bounding<N: Coord>: Point<N> + Area<N> + Anchor {
|
||||
fn x_left (&self) -> N {
|
||||
use Alignment::*;
|
||||
let w = self.w();
|
||||
self.x().minus(match self.anchor() { NW|W|SW => 0, N|X|C|Y|S => w / 2, NE|E|SE => w }
|
||||
}
|
||||
fn x_right (&self) -> N {
|
||||
use Alignment::*;
|
||||
let w = self.w();
|
||||
self.x().plus(match self.anchor() { NW|W|SW => w, N|X|C|Y|S => w/2, NE|E|SE => 0 })
|
||||
}
|
||||
fn y_top (&self) -> N {
|
||||
use Alignment::*;
|
||||
let h = self.h();
|
||||
self.y().minus(match self.anchor() { NW|N|NE => 0, W|X|C|Y|E => h/2, SW|E|SE => h })
|
||||
}
|
||||
fn y_bottom (&self) -> N {
|
||||
use Alignment::*;
|
||||
let h = self.h();
|
||||
self.y().plus(match self.anchor() { NW|N|NE => h, W|X|C|Y|E => h/2, SW|E|SE => 0 })
|
||||
}
|
||||
}
|
||||
|
||||
/// Point along (X, Y).
|
||||
pub trait Point<N: Coord>: X<N> + Y<N> { fn xy (&self) -> XY<N> { XY(self.x(), self.y()) } }
|
||||
|
||||
// Something that has a 2D bounding box (X, Y, W, H).
|
||||
pub trait Bounded<N: Coord>: Point<N> + Area<N> + Anchor<N> {
|
||||
fn x2 (&self) -> N { self.x().plus(self.w()) }
|
||||
fn y2 (&self) -> N { self.y().plus(self.h()) }
|
||||
fn xywh (&self) -> [N;4] { [..self.xy(), ..self.wh()] }
|
||||
fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
|
||||
if self.w() < w || self.h() < h {
|
||||
Err(format!("min {w}x{h}").into())
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
impl<N: Coord> XYWH<N> {
|
||||
pub fn zero () -> Self {
|
||||
Self(0.into(), 0.into(), 0.into(), 0.into())
|
||||
}
|
||||
pub fn clipped_w (&self, w: N) -> XYWH<N> {
|
||||
Self(self.x(), self.y(), self.w().min(w), self.h())
|
||||
}
|
||||
pub fn clipped_h (&self, h: N) -> XYWH<N> {
|
||||
Self(self.x(), self.y(), self.w(), self.h().min(h))
|
||||
}
|
||||
pub fn clipped (&self, wh: WH<N>) -> XYWH<N> {
|
||||
Self(self.x(), self.y(), wh.w(), wh.h())
|
||||
}
|
||||
/// Iterate over every covered X coordinate.
|
||||
pub fn iter_x (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
|
||||
let Self(x, _, w, _) = *self;
|
||||
x..(x+w)
|
||||
}
|
||||
/// Iterate over every covered Y coordinate.
|
||||
pub fn iter_y (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
|
||||
let Self(_, y, _, h) = *self;
|
||||
y..(y+h)
|
||||
}
|
||||
pub fn center (&self) -> XY<N> {
|
||||
let Self(x, y, w, h) = self;
|
||||
XY(self.x().plus(self.w()/2.into()), self.y().plus(self.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) -> XYWH<N> {
|
||||
let Self(x, y, w, h) = *self;
|
||||
XYWH((x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into())
|
||||
}
|
||||
pub fn centered_y (&self, n: N) -> XYWH<N> {
|
||||
let Self(x, y, w, h) = *self;
|
||||
XYWH(x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n)
|
||||
}
|
||||
pub fn centered_xy (&self, [n, m]: [N;2]) -> XYWH<N> {
|
||||
let Self(x, y, w, h) = *self;
|
||||
XYWH((x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,26 +178,6 @@ pub trait Bounded<N: Coord>: Point<N> + Area<N> + Anchor<N> {
|
|||
pub C, pub C, pub C, pub C
|
||||
);
|
||||
|
||||
/// A cardinal direction.
|
||||
///
|
||||
/// ```
|
||||
/// let direction = tengri::Direction::Above;
|
||||
/// ```
|
||||
#[cfg_attr(test, derive(Arbitrary))]
|
||||
#[derive(Copy, Clone, PartialEq, Debug)] pub enum Direction {
|
||||
North, South, East, West, Above, Below
|
||||
}
|
||||
|
||||
/// 9th of area to place.
|
||||
///
|
||||
/// ```
|
||||
/// let alignment = tengri::Alignment::Center;
|
||||
/// ```
|
||||
#[cfg_attr(test, derive(Arbitrary))]
|
||||
#[derive(Debug, Copy, Clone, Default)] pub enum Alignment {
|
||||
#[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W
|
||||
}
|
||||
|
||||
/// Drawable with dynamic dispatch.
|
||||
pub trait Draw<T>: Fn(&mut T)->Usually<()> { fn draw (&self, to: &mut T) -> Usually<()>; }
|
||||
|
||||
|
|
@ -167,9 +240,38 @@ pub fn clip <T, N: Coord> (w: Option<N>, h: Option<N>, draw: impl Draw<T>) -> im
|
|||
move|to|draw(to.clip(w, h))
|
||||
}
|
||||
|
||||
pub fn align () {
|
||||
//impl<O: Screen, T: Layout<O>> Layout<O> for Align<T> {
|
||||
//fn layout_x (&self, to: XYWH<O::Unit>) -> O::Unit {
|
||||
//use Alignment::*;
|
||||
//match self.0 {
|
||||
//NW | W | SW => to.x(),
|
||||
//N | Center | S => to.x().plus(to.w() / 2.into()).minus(self.1.layout_w(to) / 2.into()),
|
||||
//NE | E | SE => to.x().plus(to.w()).minus(self.1.layout_w(to)),
|
||||
//_ => todo!(),
|
||||
//}
|
||||
//}
|
||||
//fn layout_y (&self, to: XYWH<O::Unit>) -> O::Unit {
|
||||
//use Alignment::*;
|
||||
//match self.0 {
|
||||
//NW | N | NE => to.y(),
|
||||
//W | Center | E => to.y().plus(to.h() / 2.into()).minus(self.1.layout_h(to) / 2.into()),
|
||||
//SW | S | SE => to.y().plus(to.h()).minus(self.1.layout_h(to)),
|
||||
//_ => todo!(),
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
}
|
||||
|
||||
/// Shrink drawing area symmetrically.
|
||||
pub fn pad <T, N: Coord> (w: Option<N>, h: Option<N>, draw: impl Draw<T>) -> impl Draw<T> {
|
||||
move|to|draw(to.pad(w, h))
|
||||
//impl<O: Screen, T: Layout<O>> Layout<O> for Pad<O::Unit, T> {
|
||||
//fn layout_x (&self, area: XYWH<O::Unit>) -> O::Unit { area.x().plus(self.dx()) }
|
||||
//fn layout_y (&self, area: XYWH<O::Unit>) -> O::Unit { area.x().plus(self.dx()) }
|
||||
//fn layout_w (&self, area: XYWH<O::Unit>) -> O::Unit { area.w().minus(self.dx() * 2.into()) }
|
||||
//fn layout_h (&self, area: XYWH<O::Unit>) -> O::Unit { area.h().minus(self.dy() * 2.into()) }
|
||||
//}
|
||||
}
|
||||
|
||||
/// Only draw content if area is above a certain size.
|
||||
|
|
@ -215,8 +317,122 @@ pub fn phat <T, N: Coord> (
|
|||
min_wh(w, h, bsp_s(top, bsp_n(low, fill_wh(draw))))
|
||||
}
|
||||
|
||||
pub fn iter <T> (items: impl Iterator<Item = dyn Draw<T>>) -> impl Draw<T> {
|
||||
todo!()
|
||||
pub fn iter <T> (items: impl Iterator<Item = dyn Draw<T>>, callback: Fn(dyn Draw<T>)->impl Draw<T>) -> impl Draw<T> {
|
||||
//todo!()
|
||||
|
||||
//impl<'a, O, A, B, I, F, G> Map<O, A, B, I, F, G> where
|
||||
//I: Iterator<Item = A> + Send + Sync + 'a,
|
||||
//F: Fn() -> I + Send + Sync + 'a,
|
||||
//{
|
||||
//pub const fn new (get_iter: F, get_item: G) -> Self {
|
||||
//Self {
|
||||
//__: PhantomData,
|
||||
//get_iter,
|
||||
//get_item
|
||||
//}
|
||||
//}
|
||||
//const fn to_south <S: Screen> (
|
||||
//item_offset: S::Unit, item_height: S::Unit, item: impl Draw<S>
|
||||
//) -> impl Draw<S> {
|
||||
//Push::Y(item_offset, Fixed::Y(item_height, Fill::X(item)))
|
||||
//}
|
||||
//const fn to_south_west <S: Screen> (
|
||||
//item_offset: S::Unit, item_height: S::Unit, item: impl Draw<S>
|
||||
//) -> impl Draw<S> {
|
||||
//Push::Y(item_offset, Align::nw(Fixed::Y(item_height, Fill::X(item))))
|
||||
//}
|
||||
//const fn to_east <S: Screen> (
|
||||
//item_offset: S::Unit, item_width: S::Unit, item: impl Draw<S>
|
||||
//) -> impl Draw<S> {
|
||||
//Push::X(item_offset, Align::w(Fixed::X(item_width, Fill::Y(item))))
|
||||
//}
|
||||
//}
|
||||
|
||||
//macro_rules! impl_map_direction (($name:ident, $axis:ident, $align:ident)=>{
|
||||
//impl<'a, O, A, B, I, F> Map<
|
||||
//O, A, Push<O::Unit, Align<Fixed<O::Unit, Fill<B>>>>, I, F, fn(A, usize)->B
|
||||
//> where
|
||||
//O: Screen,
|
||||
//B: Draw<O>,
|
||||
//I: Iterator<Item = A> + Send + Sync + 'a,
|
||||
//F: Fn() -> I + Send + Sync + 'a
|
||||
//{
|
||||
//pub const fn $name (
|
||||
//size: O::Unit,
|
||||
//get_iter: F,
|
||||
//get_item: impl Fn(A, usize)->B + Send + Sync
|
||||
//) -> Map<
|
||||
//O, A,
|
||||
//Push<O::Unit, Align<Fixed<O::Unit, B>>>,
|
||||
//I, F,
|
||||
//impl Fn(A, usize)->Push<O::Unit, Align<Fixed<O::Unit, B>>> + Send + Sync
|
||||
//> {
|
||||
//Map {
|
||||
//__: PhantomData,
|
||||
//get_iter,
|
||||
//get_item: move |item: A, index: usize|{
|
||||
//// FIXME: multiply
|
||||
//let mut push: O::Unit = O::Unit::from(0u16);
|
||||
//for _ in 0..index {
|
||||
//push = push + size;
|
||||
//}
|
||||
//Push::$axis(push, Align::$align(Fixed::$axis(size, get_item(item, index))))
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
//});
|
||||
//impl_map_direction!(east, X, w);
|
||||
//impl_map_direction!(south, Y, n);
|
||||
//impl_map_direction!(west, X, e);
|
||||
//impl_map_direction!(north, Y, s);
|
||||
|
||||
//impl<'a, O, A, B, I, F, G> Layout<O> for Map<O, A, B, I, F, G> where
|
||||
//O: Screen,
|
||||
//B: Layout<O>,
|
||||
//I: Iterator<Item = A> + Send + Sync + 'a,
|
||||
//F: Fn() -> I + Send + Sync + 'a,
|
||||
//G: Fn(A, usize)->B + Send + Sync
|
||||
//{
|
||||
//fn layout (&self, area: XYWH<O::Unit>) -> XYWH<O::Unit> {
|
||||
//let Self { get_iter, get_item, .. } = self;
|
||||
//let mut index = 0;
|
||||
//let XY(mut min_x, mut min_y) = area.centered();
|
||||
//let XY(mut max_x, mut max_y) = area.center();
|
||||
//for item in get_iter() {
|
||||
//let XYWH(x, y, w, h) = get_item(item, index).layout(area);
|
||||
//min_x = min_x.min(x);
|
||||
//min_y = min_y.min(y);
|
||||
//max_x = max_x.max(x + w);
|
||||
//max_y = max_y.max(y + h);
|
||||
//index += 1;
|
||||
//}
|
||||
//let w = max_x - min_x;
|
||||
//let h = max_y - min_y;
|
||||
////[min_x.into(), min_y.into(), w.into(), h.into()].into()
|
||||
//area.centered_xy([w.into(), h.into()])
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl<'a, O, A, B, I, F, G> Draw<O> for Map<O, A, B, I, F, G> where
|
||||
//O: Screen,
|
||||
//B: Draw<O>,
|
||||
//I: Iterator<Item = A> + Send + Sync + 'a,
|
||||
//F: Fn() -> I + Send + Sync + 'a,
|
||||
//G: Fn(A, usize)->B + Send + Sync
|
||||
//{
|
||||
//fn draw (&self, to: &mut O) {
|
||||
//let Self { get_iter, get_item, .. } = self;
|
||||
//let mut index = 0;
|
||||
//let area = self.layout(to.area());
|
||||
//for item in get_iter() {
|
||||
//let item = get_item(item, index);
|
||||
////to.show(area.into(), &item);
|
||||
//to.show(item.layout(area), &item);
|
||||
//index += 1;
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
}
|
||||
|
||||
/// Split screen between two items, or layer them atop each other.
|
||||
|
|
@ -294,6 +510,48 @@ pub fn bsp <T> (dir: Direction, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T
|
|||
//}
|
||||
//}
|
||||
//pub fn iter
|
||||
|
||||
//impl<O: Screen, Head: Layout<O>, Tail: Layout<O>> Layout<O> for Bsp<Head, Tail> {
|
||||
//fn layout_w (&self, area: XYWH<O::Unit>) -> O::Unit {
|
||||
//match self.0 {
|
||||
//Above | Below | North | South => self.1.layout_w(area).max(self.2.layout_w(area)),
|
||||
//East | West => self.1.layout_w_min(area).plus(self.2.layout_w(area)),
|
||||
//}
|
||||
//}
|
||||
//fn layout_w_min (&self, area: XYWH<O::Unit>) -> O::Unit {
|
||||
//match self.0 {
|
||||
//Above | Below | North | South => self.1.layout_w_min(area).max(self.2.layout_w_min(area)),
|
||||
//East | West => self.1.layout_w_min(area).plus(self.2.layout_w_min(area)),
|
||||
//}
|
||||
//}
|
||||
//fn layout_w_max (&self, area: XYWH<O::Unit>) -> O::Unit {
|
||||
//match self.0 {
|
||||
//Above | Below | North | South => self.1.layout_w_max(area).max(self.2.layout_w_max(area)),
|
||||
//East | West => self.1.layout_w_max(area).plus(self.2.layout_w_max(area)),
|
||||
//}
|
||||
//}
|
||||
//fn layout_h (&self, area: XYWH<O::Unit>) -> O::Unit {
|
||||
//match self.0 {
|
||||
//Above | Below | East | West => self.1.layout_h(area).max(self.2.layout_h(area)),
|
||||
//North | South => self.1.layout_h(area).plus(self.2.layout_h(area)),
|
||||
//}
|
||||
//}
|
||||
//fn layout_h_min (&self, area: XYWH<O::Unit>) -> O::Unit {
|
||||
//match self.0 {
|
||||
//Above | Below | East | West => self.1.layout_h_min(area).max(self.2.layout_h_min(area)),
|
||||
//North | South => self.1.layout_h_min(area).plus(self.2.layout_h_min(area)),
|
||||
//}
|
||||
//}
|
||||
//fn layout_h_max (&self, area: XYWH<O::Unit>) -> O::Unit {
|
||||
//match self.0 {
|
||||
//Above | Below | North | South => self.1.layout_h_max(area).max(self.2.layout_h_max(area)),
|
||||
//East | West => self.1.layout_h_max(area).plus(self.2.layout_h_max(area)),
|
||||
//}
|
||||
//}
|
||||
//fn layout (&self, area: XYWH<O::Unit>) -> XYWH<O::Unit> {
|
||||
//bsp_areas(area, self.0, &self.1, &self.2)[2]
|
||||
//}
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -333,14 +591,6 @@ impl<O: Screen, T: Draw<O>> Draw<O> for Bounded<O, T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<O: Screen, T: Draw<O>> Draw<O> for Align<T> {
|
||||
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), &self.1).draw(to) }
|
||||
}
|
||||
|
||||
impl<O: Screen, T: Draw<O>> Draw<O> for Pad<O::Unit, T> {
|
||||
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) }
|
||||
}
|
||||
|
||||
impl<O: Screen, Head: Draw<O>, Tail: Draw<O>> Draw<O> for Bsp<Head, Tail> {
|
||||
fn draw (&self, to: &mut O) {
|
||||
let [a, b, _] = bsp_areas(to.area(), self.0, &self.1, &self.2);
|
||||
|
|
@ -405,3 +655,321 @@ impl<O: Screen, Head: Draw<O>, Tail: Draw<O>> Draw<O> for Bsp<Head, Tail> {
|
|||
#[macro_export] macro_rules! row (($($expr:expr),* $(,)?) => {{
|
||||
let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)* bsp
|
||||
}});
|
||||
|
||||
/// Interpret layout operation.
|
||||
///
|
||||
/// ```
|
||||
/// # use tengri::*;
|
||||
/// struct Target { xywh: XYWH<u16> /*platform-specific*/}
|
||||
/// impl tengri::Out for Target {
|
||||
/// type Unit = u16;
|
||||
/// fn area (&self) -> XYWH<u16> { self.xywh }
|
||||
/// fn area_mut (&mut self) -> &mut XYWH<u16> { &mut self.xywh }
|
||||
/// fn show <'t, T: Draw<Self> + ?Sized> (&mut self, area: XYWH<u16>, content: &'t T) {}
|
||||
/// }
|
||||
///
|
||||
/// struct State {/*app-specific*/}
|
||||
/// impl<'b> Namespace<'b, bool> for State {}
|
||||
/// impl<'b> Namespace<'b, u16> for State {}
|
||||
/// impl Understand<Target, ()> for State {}
|
||||
///
|
||||
/// # fn main () -> tengri::Usually<()> {
|
||||
/// let state = State {};
|
||||
/// let mut target = Target { xywh: Default::default() };
|
||||
/// eval_view(&state, &mut target, &"")?;
|
||||
/// eval_view(&state, &mut target, &"(when true (text hello))")?;
|
||||
/// eval_view(&state, &mut target, &"(either true (text hello) (text world))")?;
|
||||
/// // TODO test all
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
#[cfg(feature = "dsl")] pub fn eval_view <'a, O: Screen + 'a, S> (
|
||||
state: &S, output: &mut O, expr: &'a impl Expression
|
||||
) -> Usually<bool> where
|
||||
S: Understand<O, ()>
|
||||
+ for<'b>Namespace<'b, bool>
|
||||
+ for<'b>Namespace<'b, O::Unit>
|
||||
{
|
||||
// First element of expression is used for dispatch.
|
||||
// Dispatch is proto-namespaced using separator character
|
||||
let head = expr.head()?;
|
||||
let mut frags = head.src()?.unwrap_or_default().split("/");
|
||||
// The rest of the tokens in the expr are arguments.
|
||||
// Their meanings depend on the dispatched operation
|
||||
let args = expr.tail();
|
||||
let arg0 = args.head();
|
||||
let tail0 = args.tail();
|
||||
let arg1 = tail0.head();
|
||||
let tail1 = tail0.tail();
|
||||
let arg2 = tail1.head();
|
||||
// And we also have to do the above binding dance
|
||||
// so that the Perhaps<token>s remain in scope.
|
||||
match frags.next() {
|
||||
|
||||
Some("when") => output.place(&When::new(
|
||||
state.namespace(arg0?)?.unwrap(),
|
||||
Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap())
|
||||
)),
|
||||
|
||||
Some("either") => output.place(&Either::new(
|
||||
state.namespace(arg0?)?.unwrap(),
|
||||
Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap()),
|
||||
Thunk::new(move|output: &mut O|state.understand(output, &arg2).unwrap())
|
||||
)),
|
||||
|
||||
Some("bsp") => output.place(&{
|
||||
let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap());
|
||||
let b = Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap());
|
||||
match frags.next() {
|
||||
Some("n") => Bsp::n(a, b),
|
||||
Some("s") => Bsp::s(a, b),
|
||||
Some("e") => Bsp::e(a, b),
|
||||
Some("w") => Bsp::w(a, b),
|
||||
Some("a") => Bsp::a(a, b),
|
||||
Some("b") => Bsp::b(a, b),
|
||||
frag => unimplemented!("bsp/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
Some("align") => output.place(&{
|
||||
let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap());
|
||||
match frags.next() {
|
||||
Some("n") => Align::n(a),
|
||||
Some("s") => Align::s(a),
|
||||
Some("e") => Align::e(a),
|
||||
Some("w") => Align::w(a),
|
||||
Some("x") => Align::x(a),
|
||||
Some("y") => Align::y(a),
|
||||
Some("c") => Align::c(a),
|
||||
frag => unimplemented!("align/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
Some("fill") => output.place(&{
|
||||
let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap());
|
||||
match frags.next() {
|
||||
Some("xy") | None => Fill::XY(a),
|
||||
Some("x") => Fill::X(a),
|
||||
Some("y") => Fill::Y(a),
|
||||
frag => unimplemented!("fill/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
Some("fixed") => output.place(&{
|
||||
let axis = frags.next();
|
||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
|
||||
let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap());
|
||||
match axis {
|
||||
Some("xy") | None => Fixed::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
||||
Some("x") => Fixed::X(state.namespace(arg0?)?.unwrap(), cb),
|
||||
Some("y") => Fixed::Y(state.namespace(arg0?)?.unwrap(), cb),
|
||||
frag => unimplemented!("fixed/{frag:?} ({expr:?}) ({head:?}) ({:?})",
|
||||
head.src()?.unwrap_or_default().split("/").next())
|
||||
}
|
||||
}),
|
||||
|
||||
Some("min") => output.place(&{
|
||||
let axis = frags.next();
|
||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
|
||||
let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap());
|
||||
match axis {
|
||||
Some("xy") | None => Min::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
||||
Some("x") => Min::X(state.namespace(arg0?)?.unwrap(), cb),
|
||||
Some("y") => Min::Y(state.namespace(arg0?)?.unwrap(), cb),
|
||||
frag => unimplemented!("min/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
Some("max") => output.place(&{
|
||||
let axis = frags.next();
|
||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
|
||||
let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap());
|
||||
match axis {
|
||||
Some("xy") | None => Max::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
||||
Some("x") => Max::X(state.namespace(arg0?)?.unwrap(), cb),
|
||||
Some("y") => Max::Y(state.namespace(arg0?)?.unwrap(), cb),
|
||||
frag => unimplemented!("max/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
Some("push") => output.place(&{
|
||||
let axis = frags.next();
|
||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
|
||||
let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap());
|
||||
match axis {
|
||||
Some("xy") | None => Push::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
||||
Some("x") => Push::X(state.namespace(arg0?)?.unwrap(), cb),
|
||||
Some("y") => Push::Y(state.namespace(arg0?)?.unwrap(), cb),
|
||||
frag => unimplemented!("push/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
_ => return Ok(false)
|
||||
|
||||
};
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Trim string with [unicode_width].
|
||||
pub fn trim_string (max_width: usize, input: impl AsRef<str>) -> String {
|
||||
let input = input.as_ref();
|
||||
let mut output = Vec::with_capacity(input.len());
|
||||
let mut width: usize = 1;
|
||||
let mut chars = input.chars();
|
||||
while let Some(c) = chars.next() {
|
||||
if width > max_width {
|
||||
break
|
||||
}
|
||||
output.push(c);
|
||||
width += c.width().unwrap_or(0);
|
||||
}
|
||||
return output.into_iter().collect()
|
||||
}
|
||||
|
||||
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();
|
||||
while let Some(c) = chars.next() {
|
||||
width += c.width().unwrap_or(0) as u16;
|
||||
if width > max {
|
||||
break
|
||||
}
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
/// Memoize a rendering.
|
||||
///
|
||||
/// ```
|
||||
/// let _ = tengri::Memo::new((), ());
|
||||
/// ```
|
||||
#[derive(Debug, Default)] pub struct Memo<T, U> {
|
||||
pub value: T,
|
||||
pub view: Arc<RwLock<U>>
|
||||
}
|
||||
|
||||
impl<'a, T: AsRef<str>> TrimString<T> {
|
||||
fn to_ref (&self) -> TrimStringRef<'_, T> { TrimStringRef(self.0, &self.1) }
|
||||
}
|
||||
impl<C, T, U> Field<C, T, U> {
|
||||
pub fn new (direction: Direction) -> Field<C, (), ()> {
|
||||
Field::<C, (), ()> {
|
||||
direction,
|
||||
label: None, label_fg: None, label_bg: None, label_align: None,
|
||||
value: None, value_fg: None, value_bg: None, value_align: None,
|
||||
}
|
||||
}
|
||||
pub fn label <L> (
|
||||
self, label: Option<L>, align: Option<Direction>, fg: Option<C>, bg: Option<C>
|
||||
) -> Field<C, L, U> {
|
||||
Field::<C, L, U> { label, label_fg: fg, label_bg: bg, label_align: align, ..self }
|
||||
}
|
||||
pub fn value <V> (
|
||||
self, value: Option<V>, align: Option<Direction>, fg: Option<C>, bg: Option<C>
|
||||
) -> Field<C, T, V> {
|
||||
Field::<C, T, V> { value, value_fg: fg, value_bg: bg, value_align: align, ..self }
|
||||
}
|
||||
}
|
||||
impl<O: Screen> Clone for Measure<O> { fn clone (&self) -> Self { Self { __: Default::default(), x: self.x.clone(), y: self.y.clone(), } } }
|
||||
impl<T: PartialEq, U> Memo<T, U> {
|
||||
pub fn new (value: T, view: U) -> Self {
|
||||
Self { value, view: Arc::new(view.into()) }
|
||||
}
|
||||
pub fn update <R> (&mut self, newval: T, draw: impl Fn(&mut U, &T, &T)->R) -> Option<R> {
|
||||
if newval != self.value {
|
||||
let result = draw(&mut*self.view.write().unwrap(), &newval, &self.value);
|
||||
self.value = newval;
|
||||
return Some(result);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
impl<O: Screen> PartialEq for Measure<O> { fn eq (&self, other: &Self) -> bool { self.w() == other.w() && self.h() == other.h() } }
|
||||
impl<N: Unit> W<N> for Measure<N> { fn w (&self) -> O::Unit { (self.x.load(Relaxed) as u16).into() } }
|
||||
impl<N: Unit> H<N> for Measure<N> { fn h (&self) -> O::Unit { (self.y.load(Relaxed) as u16).into() } }
|
||||
impl<N: Unit> Debug for Measure<N> { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { f.debug_struct("Measure").field("width", &self.x).field("height", &self.y).finish() } }
|
||||
impl<N: Unit> Measure<N> {
|
||||
pub fn set_w (&self, w: impl Into<usize>) -> &Self { self.x.store(w.into(), Relaxed); self }
|
||||
pub fn set_h (&self, h: impl Into<usize>) -> &Self { self.y.store(h.into(), Relaxed); self }
|
||||
pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) -> &Self { self.set_w(w); self.set_h(h); self }
|
||||
pub fn format (&self) -> Arc<str> { format!("{}x{}", self.w(), self.h()).into() }
|
||||
pub fn of <T: Draw<O>> (&self, item: T) -> Bsp<Fill<&Self>, T> { Bsp::b(Fill::XY(self), item) }
|
||||
pub fn new (x: O::Unit, y: O::Unit) -> Self {
|
||||
Self { __: PhantomData::default(), x: Arc::new(x.atomic()), y: Arc::new(y.atomic()), }
|
||||
}
|
||||
}
|
||||
impl<N: Unit> From<WH<N>> for Measure<N> { fn from (WH(x, y): WH<O::Unit>) -> Self { Self::new(x, y) } }
|
||||
// Implement layout op that increments X and/or Y by fixed amount.
|
||||
macro_rules! push_pull(($T:ident: $method: ident)=>{
|
||||
layout_op_xy!(1: $T);
|
||||
impl<O: Screen, T: Layout<O>> Layout<O> for $T<O::Unit, T> {
|
||||
fn layout_x (&self, area: XYWH<O::Unit>) -> O::Unit { area.x().$method(self.dx()) }
|
||||
fn layout_y (&self, area: XYWH<O::Unit>) -> O::Unit { area.y().$method(self.dy()) }
|
||||
}
|
||||
});
|
||||
push_pull!(Push: plus);
|
||||
push_pull!(Pull: minus);
|
||||
|
||||
layout_op_xy!(0: Fill);
|
||||
|
||||
impl<O: Screen, T: Layout<O>> Layout<O> for Fill<T> {
|
||||
fn layout_x (&self, area: XYWH<O::Unit>) -> O::Unit { if self.dx() { area.x() } else { self.inner().layout_x(area) } }
|
||||
fn layout_y (&self, area: XYWH<O::Unit>) -> O::Unit { if self.dy() { area.y() } else { self.inner().layout_y(area) } }
|
||||
fn layout_w (&self, area: XYWH<O::Unit>) -> O::Unit { if self.dx() { area.w() } else { self.inner().layout_w(area) } }
|
||||
fn layout_w_min (&self, area: XYWH<O::Unit>) -> O::Unit { if self.dx() { area.w() } else { self.inner().layout_w_min(area) } }
|
||||
fn layout_w_max (&self, area: XYWH<O::Unit>) -> O::Unit { if self.dx() { area.w() } else { self.inner().layout_w_max(area) } }
|
||||
fn layout_h (&self, area: XYWH<O::Unit>) -> O::Unit { if self.dy() { area.h() } else { self.inner().layout_h(area) } }
|
||||
fn layout_h_min (&self, area: XYWH<O::Unit>) -> O::Unit { if self.dy() { area.h() } else { self.inner().layout_h_min(area) } }
|
||||
fn layout_h_max (&self, area: XYWH<O::Unit>) -> O::Unit { if self.dy() { area.h() } else { self.inner().layout_h_max(area) } }
|
||||
}
|
||||
|
||||
impl<A> Fill<A> {
|
||||
#[inline] pub const fn dx (&self) -> bool { matches!(self, Self::X(_) | Self::XY(_)) }
|
||||
#[inline] pub const fn dy (&self) -> bool { matches!(self, Self::Y(_) | Self::XY(_)) }
|
||||
}
|
||||
|
||||
|
||||
impl<O: Screen, T: Layout<O>> Layout<O> for Fixed<O::Unit, T> {
|
||||
fn layout_w (&self, area: XYWH<O::Unit>) -> O::Unit { self.dx().unwrap_or(self.inner().layout_w(area)) }
|
||||
fn layout_w_min (&self, area: XYWH<O::Unit>) -> O::Unit { self.dx().unwrap_or(self.inner().layout_w_min(area)) }
|
||||
fn layout_w_max (&self, area: XYWH<O::Unit>) -> O::Unit { self.dx().unwrap_or(self.inner().layout_w_max(area)) }
|
||||
fn layout_h (&self, area: XYWH<O::Unit>) -> O::Unit { self.dy().unwrap_or(self.inner().layout_h(area)) }
|
||||
fn layout_h_min (&self, area: XYWH<O::Unit>) -> O::Unit { self.dy().unwrap_or(self.inner().layout_h_min(area)) }
|
||||
fn layout_h_max (&self, area: XYWH<O::Unit>) -> O::Unit { self.dy().unwrap_or(self.inner().layout_h_max(area)) }
|
||||
}
|
||||
|
||||
|
||||
impl<E: Screen, T: Layout<E>> Layout<E> for Max<E::Unit, T> {
|
||||
fn layout (&self, area: XYWH<E::Unit>) -> XYWH<E::Unit> {
|
||||
let XYWH(x, y, w, h) = self.inner().layout(area);
|
||||
match self {
|
||||
Self::X(mw, _) => XYWH(x, y, w.min(*mw), h ),
|
||||
Self::Y(mh, _) => XYWH(x, y, w, h.min(*mh)),
|
||||
Self::XY(mw, mh, _) => XYWH(x, y, w.min(*mw), h.min(*mh)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<E: Screen, T: Layout<E>> Layout<E> for Min<E::Unit, T> {
|
||||
fn layout (&self, area: XYWH<E::Unit>) -> XYWH<E::Unit> {
|
||||
let XYWH(x, y, w, h) = self.inner().layout(area);
|
||||
match self {
|
||||
Self::X(mw, _) => XYWH(x, y, w.max(*mw), h),
|
||||
Self::Y(mh, _) => XYWH(x, y, w, h.max(*mh)),
|
||||
Self::XY(mw, mh, _) => XYWH(x, y, w.max(*mw), h.max(*mh)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Coord> X<N> for XY<N> { fn x (&self) -> N { self.0 } }
|
||||
impl<N: Coord> Y<N> for XY<N> { fn y (&self) -> N { self.1 } }
|
||||
impl<N: Coord> W<N> for WH<N> { fn w (&self) -> N { self.0 } }
|
||||
impl<N: Coord> H<N> for WH<N> { fn h (&self) -> N { self.1 } }
|
||||
impl<N: Coord> X<N> for XYWH<N> { fn x (&self) -> N { self.0 } }
|
||||
impl<N: Coord> Y<N> for XYWH<N> { fn y (&self) -> N { self.1 } }
|
||||
impl<N: Coord> W<N> for XYWH<N> { fn w (&self) -> N { self.2 } }
|
||||
impl<N: Coord> H<N> for XYWH<N> { fn h (&self) -> N { self.3 } }
|
||||
impl<O: Screen> X<O::Unit> for O { fn x (&self) -> O::Unit { self.area().x() } }
|
||||
impl<O: Screen> Y<O::Unit> for O { fn y (&self) -> O::Unit { self.area().y() } }
|
||||
impl<O: Screen> W<O::Unit> for O { fn w (&self) -> O::Unit { self.area().w() } }
|
||||
impl<O: Screen> H<O::Unit> for O { fn h (&self) -> O::Unit { self.area().h() } }
|
||||
|
|
|
|||
104
src/play.rs
104
src/play.rs
|
|
@ -1,5 +1,64 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Clone)] pub struct Exit(Arc<AtomicBool>);
|
||||
|
||||
impl Exit {
|
||||
pub fn run <T> (run: impl Fn()->Usually<T>) -> Usually<T> {
|
||||
run(Self(Arc::new(AtomicBool::new(false))))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)] pub struct Thread {
|
||||
/// Exit flag.
|
||||
pub exit: Arc<AtomicBool>,
|
||||
/// Performance counter.
|
||||
pub perf: Arc<PerfModel>,
|
||||
/// Use this to wait for the thread to finish.
|
||||
pub join: JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
/// Spawn a TUI thread that runs `callt least one, then repeats until `exit`.
|
||||
pub fn new <F> (exit: Arc<AtomicBool>, call: F) -> Result<Self, std::io::Error>
|
||||
where F: Fn(&PerfModel)->() + Send + Sync + 'static
|
||||
{
|
||||
let perf = Arc::new(PerfModel::default());
|
||||
Ok(Self {
|
||||
exit: exit.clone(),
|
||||
perf: perf.clone(),
|
||||
join: std::thread::Builder::new().name("tengri tui output".into()).spawn(move || {
|
||||
while !exit.fetch_and(true, Relaxed) {
|
||||
let _ = perf.cycle(&call);
|
||||
}
|
||||
})?.into()
|
||||
})
|
||||
}
|
||||
|
||||
/// Spawn a TUI thread that runs `callt least one, then repeats
|
||||
/// until `exit`, sleeping for `time` msec after every iteration.
|
||||
pub fn new_sleep <F> (
|
||||
exit: Arc<AtomicBool>, time: Duration, call: F
|
||||
) -> Result<Self, std::io::Error>
|
||||
where F: Fn(&PerfModel)->() + Send + Sync + 'static
|
||||
{
|
||||
Self::new(exit, move |perf| { let _ = call(perf); std::thread::sleep(time); })
|
||||
}
|
||||
|
||||
/// Spawn a TUI thread that runs `callt least one, then repeats
|
||||
/// until `exit`, using polling to run every `time` msec.
|
||||
pub fn new_poll <F> (
|
||||
exit: Arc<AtomicBool>, time: Duration, call: F
|
||||
) -> Result<Self, std::io::Error>
|
||||
where F: Fn(&PerfModel)->() + Send + Sync + 'static
|
||||
{
|
||||
Self::new(exit, move |perf| { if poll(time).is_ok() { let _ = call(perf); } })
|
||||
}
|
||||
|
||||
pub fn join (self) -> Result<(), Box<dyn std::any::Any + Send>> {
|
||||
self.join.join()
|
||||
}
|
||||
}
|
||||
|
||||
/// Define an enum containing commands, and implement [Command] trait for over given `State`.
|
||||
#[macro_export] macro_rules! def_command (
|
||||
($Command:ident: |$state:ident: $State:ty| {
|
||||
|
|
@ -39,3 +98,48 @@ use crate::*;
|
|||
}
|
||||
}
|
||||
}
|
||||
impl<S, T: Action<S>> Action<S> for Option<T> { fn action (&self, _: &mut S) -> Perhaps<Self> { Ok(None) } }
|
||||
|
||||
impl_default!(PerfModel: Self {
|
||||
enabled: true,
|
||||
clock: quanta::Clock::new(),
|
||||
used: Default::default(),
|
||||
window: Default::default(),
|
||||
});
|
||||
|
||||
impl PerfModel {
|
||||
pub fn get_t0 (&self) -> Option<u64> {
|
||||
if self.enabled {
|
||||
Some(self.clock.raw())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn get_t1 (&self, t0: Option<u64>) -> Option<std::time::Duration> {
|
||||
if let Some(t0) = t0 {
|
||||
if self.enabled {
|
||||
Some(self.clock.delta(t0, self.clock.raw()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn update (&self, t0: Option<u64>, microseconds: f64) {
|
||||
if let Some(t0) = t0 {
|
||||
let t1 = self.clock.raw();
|
||||
self.used.store(self.clock.delta_as_nanos(t0, t1) as f64, Relaxed);
|
||||
self.window.store(microseconds, Relaxed,);
|
||||
}
|
||||
}
|
||||
pub fn percentage (&self) -> Option<f64> {
|
||||
let window = self.window.load(Relaxed) * 1000.0;
|
||||
if window > 0.0 {
|
||||
let used = self.used.load(Relaxed);
|
||||
Some(100.0 * used / window)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ mod tengri_macros;
|
|||
mod tengri_struct; pub use self::tengri_struct::*;
|
||||
mod tengri_trait; pub use self::tengri_trait::*;
|
||||
mod tengri_impl; pub use self::tengri_impl::*;
|
||||
mod tengri_fns; pub use self::tengri_fns::*;
|
||||
|
||||
#[cfg(test)] pub(crate) use proptest_derive::Arbitrary;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,182 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
/// Interpret layout operation.
|
||||
///
|
||||
/// ```
|
||||
/// # use tengri::*;
|
||||
/// struct Target { xywh: XYWH<u16> /*platform-specific*/}
|
||||
/// impl tengri::Out for Target {
|
||||
/// type Unit = u16;
|
||||
/// fn area (&self) -> XYWH<u16> { self.xywh }
|
||||
/// fn area_mut (&mut self) -> &mut XYWH<u16> { &mut self.xywh }
|
||||
/// fn show <'t, T: Draw<Self> + ?Sized> (&mut self, area: XYWH<u16>, content: &'t T) {}
|
||||
/// }
|
||||
///
|
||||
/// struct State {/*app-specific*/}
|
||||
/// impl<'b> Namespace<'b, bool> for State {}
|
||||
/// impl<'b> Namespace<'b, u16> for State {}
|
||||
/// impl Understand<Target, ()> for State {}
|
||||
///
|
||||
/// # fn main () -> tengri::Usually<()> {
|
||||
/// let state = State {};
|
||||
/// let mut target = Target { xywh: Default::default() };
|
||||
/// eval_view(&state, &mut target, &"")?;
|
||||
/// eval_view(&state, &mut target, &"(when true (text hello))")?;
|
||||
/// eval_view(&state, &mut target, &"(either true (text hello) (text world))")?;
|
||||
/// // TODO test all
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
#[cfg(feature = "dsl")] pub fn eval_view <'a, O: Screen + 'a, S> (
|
||||
state: &S, output: &mut O, expr: &'a impl Expression
|
||||
) -> Usually<bool> where
|
||||
S: Understand<O, ()>
|
||||
+ for<'b>Namespace<'b, bool>
|
||||
+ for<'b>Namespace<'b, O::Unit>
|
||||
{
|
||||
// First element of expression is used for dispatch.
|
||||
// Dispatch is proto-namespaced using separator character
|
||||
let head = expr.head()?;
|
||||
let mut frags = head.src()?.unwrap_or_default().split("/");
|
||||
// The rest of the tokens in the expr are arguments.
|
||||
// Their meanings depend on the dispatched operation
|
||||
let args = expr.tail();
|
||||
let arg0 = args.head();
|
||||
let tail0 = args.tail();
|
||||
let arg1 = tail0.head();
|
||||
let tail1 = tail0.tail();
|
||||
let arg2 = tail1.head();
|
||||
// And we also have to do the above binding dance
|
||||
// so that the Perhaps<token>s remain in scope.
|
||||
match frags.next() {
|
||||
|
||||
Some("when") => output.place(&When::new(
|
||||
state.namespace(arg0?)?.unwrap(),
|
||||
Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap())
|
||||
)),
|
||||
|
||||
Some("either") => output.place(&Either::new(
|
||||
state.namespace(arg0?)?.unwrap(),
|
||||
Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap()),
|
||||
Thunk::new(move|output: &mut O|state.understand(output, &arg2).unwrap())
|
||||
)),
|
||||
|
||||
Some("bsp") => output.place(&{
|
||||
let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap());
|
||||
let b = Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap());
|
||||
match frags.next() {
|
||||
Some("n") => Bsp::n(a, b),
|
||||
Some("s") => Bsp::s(a, b),
|
||||
Some("e") => Bsp::e(a, b),
|
||||
Some("w") => Bsp::w(a, b),
|
||||
Some("a") => Bsp::a(a, b),
|
||||
Some("b") => Bsp::b(a, b),
|
||||
frag => unimplemented!("bsp/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
Some("align") => output.place(&{
|
||||
let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap());
|
||||
match frags.next() {
|
||||
Some("n") => Align::n(a),
|
||||
Some("s") => Align::s(a),
|
||||
Some("e") => Align::e(a),
|
||||
Some("w") => Align::w(a),
|
||||
Some("x") => Align::x(a),
|
||||
Some("y") => Align::y(a),
|
||||
Some("c") => Align::c(a),
|
||||
frag => unimplemented!("align/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
Some("fill") => output.place(&{
|
||||
let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap());
|
||||
match frags.next() {
|
||||
Some("xy") | None => Fill::XY(a),
|
||||
Some("x") => Fill::X(a),
|
||||
Some("y") => Fill::Y(a),
|
||||
frag => unimplemented!("fill/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
Some("fixed") => output.place(&{
|
||||
let axis = frags.next();
|
||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
|
||||
let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap());
|
||||
match axis {
|
||||
Some("xy") | None => Fixed::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
||||
Some("x") => Fixed::X(state.namespace(arg0?)?.unwrap(), cb),
|
||||
Some("y") => Fixed::Y(state.namespace(arg0?)?.unwrap(), cb),
|
||||
frag => unimplemented!("fixed/{frag:?} ({expr:?}) ({head:?}) ({:?})",
|
||||
head.src()?.unwrap_or_default().split("/").next())
|
||||
}
|
||||
}),
|
||||
|
||||
Some("min") => output.place(&{
|
||||
let axis = frags.next();
|
||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
|
||||
let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap());
|
||||
match axis {
|
||||
Some("xy") | None => Min::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
||||
Some("x") => Min::X(state.namespace(arg0?)?.unwrap(), cb),
|
||||
Some("y") => Min::Y(state.namespace(arg0?)?.unwrap(), cb),
|
||||
frag => unimplemented!("min/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
Some("max") => output.place(&{
|
||||
let axis = frags.next();
|
||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
|
||||
let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap());
|
||||
match axis {
|
||||
Some("xy") | None => Max::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
||||
Some("x") => Max::X(state.namespace(arg0?)?.unwrap(), cb),
|
||||
Some("y") => Max::Y(state.namespace(arg0?)?.unwrap(), cb),
|
||||
frag => unimplemented!("max/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
Some("push") => output.place(&{
|
||||
let axis = frags.next();
|
||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
|
||||
let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap());
|
||||
match axis {
|
||||
Some("xy") | None => Push::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
||||
Some("x") => Push::X(state.namespace(arg0?)?.unwrap(), cb),
|
||||
Some("y") => Push::Y(state.namespace(arg0?)?.unwrap(), cb),
|
||||
frag => unimplemented!("push/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
_ => return Ok(false)
|
||||
|
||||
};
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Trim string with [unicode_width].
|
||||
pub fn trim_string (max_width: usize, input: impl AsRef<str>) -> String {
|
||||
let input = input.as_ref();
|
||||
let mut output = Vec::with_capacity(input.len());
|
||||
let mut width: usize = 1;
|
||||
let mut chars = input.chars();
|
||||
while let Some(c) = chars.next() {
|
||||
if width > max_width {
|
||||
break
|
||||
}
|
||||
output.push(c);
|
||||
width += c.width().unwrap_or(0);
|
||||
}
|
||||
return output.into_iter().collect()
|
||||
}
|
||||
|
||||
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();
|
||||
while let Some(c) = chars.next() {
|
||||
width += c.width().unwrap_or(0) as u16;
|
||||
if width > max {
|
||||
break
|
||||
}
|
||||
}
|
||||
return width
|
||||
}
|
||||
1358
src/tengri_impl.rs
1358
src/tengri_impl.rs
File diff suppressed because it is too large
Load diff
|
|
@ -1,56 +1,5 @@
|
|||
use crate::*;
|
||||
|
||||
/// Define an enum containing commands, and implement [Command] trait for over given `State`.
|
||||
#[macro_export] macro_rules! def_command (
|
||||
($Command:ident: |$state:ident: $State:ty| {
|
||||
// FIXME: support attrs (docstrings)
|
||||
$($Variant:ident$({$($arg:ident:$Arg:ty),+ $(,)?})?=>$body:expr),* $(,)?
|
||||
})=>{
|
||||
#[derive(Debug)] pub enum $Command {
|
||||
// FIXME: support attrs (docstrings)
|
||||
$($Variant $({ $($arg: $Arg),* })?),*
|
||||
}
|
||||
impl ::tengri::Command<$State> for $Command {
|
||||
fn execute (&self, $state: &mut $State) -> Perhaps<Self> {
|
||||
match self {
|
||||
$(Self::$Variant $({ $($arg),* })? => $body,)*
|
||||
_ => unimplemented!("Command<{}>: {self:?}", stringify!($State)),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/// Implement [Handle] for given `State` and `handler`.
|
||||
#[macro_export] macro_rules! handle {
|
||||
(|$self:ident:$State:ty,$input:ident|$handler:expr) => {
|
||||
impl<E: Engine> ::tengri::Handle<E> for $State {
|
||||
fn handle (&mut $self, $input: &E) -> Perhaps<E::Handled> {
|
||||
$handler
|
||||
}
|
||||
}
|
||||
};
|
||||
($E:ty: |$self:ident:$State:ty,$input:ident|$handler:expr) => {
|
||||
impl ::tengri::Handle<$E> for $State {
|
||||
fn handle (&mut $self, $input: &$E) ->
|
||||
Perhaps<<$E as ::tengri::Input>::Handled>
|
||||
{
|
||||
$handler
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! tui_main {
|
||||
($expr:expr) => {
|
||||
fn main () -> Usually<()> {
|
||||
let engine = ::tengri::Tui::new(Box::new(stdout()))?;
|
||||
let state = ::std::sync::Arc::new(std::sync::RwLock::new($expr));
|
||||
engine.run(true, &state)?;
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! has_color {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,5 @@
|
|||
use crate::*;
|
||||
|
||||
/// Memoize a rendering.
|
||||
///
|
||||
/// ```
|
||||
/// let _ = tengri::Memo::new((), ());
|
||||
/// ```
|
||||
#[derive(Debug, Default)] pub struct Memo<T, U> {
|
||||
pub value: T,
|
||||
pub view: Arc<RwLock<U>>
|
||||
}
|
||||
|
||||
pub use self::spatial::*; mod spatial {
|
||||
use crate::*;
|
||||
|
||||
|
|
@ -165,23 +155,7 @@ pub use self::spatial::*; mod spatial {
|
|||
pub value_align: Option<Direction>,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)] pub struct Exit(Arc<AtomicBool>);
|
||||
impl Exit {
|
||||
pub fn run <T> (run: impl Fn()->Usually<T>) -> Usually<T> {
|
||||
run(Self(Arc::new(AtomicBool::new(false))))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)] pub struct Thread {
|
||||
/// Exit flag.
|
||||
pub exit: Arc<AtomicBool>,
|
||||
/// Performance counter.
|
||||
pub perf: Arc<PerfModel>,
|
||||
/// Use this to wait for the thread to finish.
|
||||
pub join: JoinHandle<()>,
|
||||
}
|
||||
|
||||
:wqa
|
||||
#[cfg(feature = "tui")] pub use self::terminal::*;
|
||||
#[cfg(feature = "tui")] mod terminal {
|
||||
use crate::*;
|
||||
|
|
|
|||
771
src/tui.rs
771
src/tui.rs
|
|
@ -1,4 +1,6 @@
|
|||
use crate::*;
|
||||
use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
|
||||
use rand::distributions::uniform::UniformSampler;
|
||||
|
||||
#[cfg(feature = "tui")] pub use self::tui_fns::*;
|
||||
|
||||
|
|
@ -198,3 +200,772 @@ pub fn eval_view_tui <'a, S> (
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl Coord for u16 { fn plus (self, other: Self) -> Self { self.saturating_add(other) } }
|
||||
impl_from!(BigBuffer: |size:(usize, usize)| Self::new(size.0, size.1));
|
||||
impl_from!(ItemTheme: |base: ItemColor| Self::from_item_color(base));
|
||||
impl_from!(ItemTheme: |base: Color| Self::from_tui_color(base));
|
||||
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) });
|
||||
impl_debug!(BigBuffer |self, f| { write!(f, "[BB {}x{} ({})]", self.width, self.height, self.content.len()) });
|
||||
impl Screen for Buffer { type Unit = u16; }
|
||||
impl TuiOut for Buffer { fn tui_out (&mut self) -> &mut Buffer { self } }
|
||||
impl TuiOut for Buffer { fn tui_out (&mut self) -> &mut Buffer { self } }
|
||||
|
||||
//impl<T: AsMut<Buffer>> TuiOut for T { fn tui_out (&mut self) -> &mut Buffer { self.as_mut() } }
|
||||
|
||||
impl Tui {
|
||||
/// True if done
|
||||
pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) }
|
||||
/// Prepare before run
|
||||
pub fn setup (&self) -> Usually<()> { tui_setup(&mut*self.backend.write().unwrap()) }
|
||||
/// Clean up after run
|
||||
pub fn teardown (&self) -> Usually<()> { tui_teardown(&mut*self.backend.write().unwrap()) }
|
||||
/// Apply changes to the display buffer.
|
||||
pub fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer { tui_flip(self, self.buffer, buffer, size) }
|
||||
/// Create the engine.
|
||||
pub fn new (output: Box<dyn Write + Send + Sync>) -> Usually<Self> {
|
||||
let backend = CrosstermBackend::new(output);
|
||||
let Size { width, height } = backend.size()?;
|
||||
Ok(Self {
|
||||
exited: Arc::new(AtomicBool::new(false)),
|
||||
buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }).into(),
|
||||
area: XYWH(0, 0, width, height),
|
||||
perf: Default::default(),
|
||||
backend: backend.into(),
|
||||
event: None,
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
/// Run an app in the engine.
|
||||
pub fn run <T> (mut self, join: bool, state: &Arc<RwLock<T>>) -> Usually<Arc<Self>> where
|
||||
T: Act<Tui, T> + Draw<Buffer> + Send + Sync + 'static
|
||||
{
|
||||
self.setup()?;
|
||||
let tui = Arc::new(self);
|
||||
let input_poll = Duration::from_millis(100);
|
||||
let output_sleep = Duration::from_millis(10); // == 1/MAXFPS (?)
|
||||
let _input_thread = tui_input(&tui, state, input_poll)?;
|
||||
let render_thread = tui_output(&tui, state, output_sleep)?;
|
||||
if join { // Run until render thread ends:
|
||||
let result = render_thread.join();
|
||||
tui.teardown()?;
|
||||
match result {
|
||||
Ok(result) => println!("\n\rRan successfully: {result:?}\n\r"),
|
||||
Err(error) => panic!("\n\rDraw thread failed: error={error:?}.\n\r"),
|
||||
}
|
||||
}
|
||||
Ok(tui)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl TuiEvent {
|
||||
pub fn from_crossterm (event: Event) -> Self { Self(event) }
|
||||
#[cfg(feature = "dsl")] pub fn from_dsl (dsl: impl Language) -> Perhaps<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)
|
||||
.unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Event> for TuiEvent {
|
||||
fn from (e: Event) -> Self {
|
||||
Self(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<char> for TuiEvent {
|
||||
fn from (c: char) -> Self {
|
||||
Self(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TuiKey {
|
||||
const SPLIT: char = '/';
|
||||
#[cfg(feature = "dsl")] pub fn from_dsl (dsl: impl Language) -> Usually<Self> {
|
||||
if let Some(word) = dsl.word()? {
|
||||
let word = word.trim();
|
||||
Ok(if word == ":char" {
|
||||
Self(None, KeyModifiers::NONE)
|
||||
} else if word.chars().nth(0) == Some('@') {
|
||||
let mut key = None;
|
||||
let mut modifiers = KeyModifiers::NONE;
|
||||
let mut tokens = word[1..].split(Self::SPLIT).peekable();
|
||||
while let Some(token) = tokens.next() {
|
||||
if tokens.peek().is_some() {
|
||||
match token {
|
||||
"ctrl" | "Ctrl" | "c" | "C" => modifiers |= KeyModifiers::CONTROL,
|
||||
"alt" | "Alt" | "m" | "M" => modifiers |= KeyModifiers::ALT,
|
||||
"shift" | "Shift" | "s" | "S" => {
|
||||
modifiers |= KeyModifiers::SHIFT;
|
||||
// + TODO normalize character case, BackTab, etc.
|
||||
},
|
||||
_ => panic!("unknown modifier {token}"),
|
||||
}
|
||||
} else {
|
||||
key = if token.len() == 1 {
|
||||
Some(KeyCode::Char(token.chars().next().unwrap()))
|
||||
} else {
|
||||
Some(named_key(token).unwrap_or_else(||panic!("unknown character {token}")))
|
||||
}
|
||||
}
|
||||
}
|
||||
Self(key, modifiers)
|
||||
} else {
|
||||
return Err(format!("TuiKey: unexpected: {word}").into())
|
||||
})
|
||||
} else {
|
||||
return Err(format!("TuiKey: unspecified").into())
|
||||
}
|
||||
}
|
||||
pub fn to_crossterm (&self) -> Option<Event> {
|
||||
self.0.map(|code|Event::Key(KeyEvent {
|
||||
code,
|
||||
modifiers: self.1,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl BigBuffer {
|
||||
pub fn new (width: usize, height: usize) -> Self {
|
||||
Self { width, height, content: vec![Cell::default(); width*height] }
|
||||
}
|
||||
pub fn get (&self, x: usize, y: usize) -> Option<&Cell> {
|
||||
let i = self.index_of(x, y);
|
||||
self.content.get(i)
|
||||
}
|
||||
pub fn get_mut (&mut self, x: usize, y: usize) -> Option<&mut Cell> {
|
||||
let i = self.index_of(x, y);
|
||||
self.content.get_mut(i)
|
||||
}
|
||||
pub fn index_of (&self, x: usize, y: usize) -> usize {
|
||||
y * self.width + x
|
||||
}
|
||||
}
|
||||
|
||||
impl PerfModel {
|
||||
fn cycle <F: Fn(&Self)->T, T> (&self, call: &F) -> T {
|
||||
let t0 = self.get_t0();
|
||||
let result = call(self);
|
||||
let _t1 = self.get_t1(t0).unwrap();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Phat<T> {
|
||||
pub const LO: &'static str = "▄";
|
||||
pub const HI: &'static str = "▀";
|
||||
/// A phat line
|
||||
pub fn lo (fg: Color, bg: Color) -> impl Draw<Buffer> {
|
||||
Fixed::Y(1, Tui::fg_bg(fg, bg, Repeat::X(Self::LO)))
|
||||
}
|
||||
/// A phat line
|
||||
pub fn hi (fg: Color, bg: Color) -> impl Draw<Buffer> {
|
||||
Fixed::Y(1, Tui::fg_bg(fg, bg, Repeat::X(Self::HI)))
|
||||
}
|
||||
}
|
||||
impl Scrollbar {
|
||||
const ICON_DEC_V: &[char] = &['▲'];
|
||||
const ICON_INC_V: &[char] = &['▼'];
|
||||
const ICON_DEC_H: &[char] = &[' ', '🞀', ' '];
|
||||
const ICON_INC_H: &[char] = &[' ', '🞂', ' '];
|
||||
}
|
||||
impl Layout<Tui> for &str {
|
||||
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
|
||||
to.centered_xy([width_chars_max(to.w(), self), 1])
|
||||
}
|
||||
}
|
||||
impl Layout<Tui> for String {
|
||||
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
|
||||
self.as_str().layout(to)
|
||||
}
|
||||
}
|
||||
impl Layout<Tui> for Arc<str> {
|
||||
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
|
||||
self.as_ref().layout(to)
|
||||
}
|
||||
}
|
||||
impl<'a, T: AsRef<str>> Layout<Tui> for TrimString<T> {
|
||||
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
|
||||
Layout::layout(&self.as_ref(), to)
|
||||
}
|
||||
}
|
||||
impl<'a, T: AsRef<str>> Layout<Tui> for TrimStringRef<'a, T> {
|
||||
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
|
||||
XYWH(to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Screen> Draw<T> for u64 {
|
||||
fn draw (&self, _to: &mut T) { todo!() }
|
||||
}
|
||||
|
||||
impl Draw<Buffer> for f64 {
|
||||
fn draw (&self, _to: &mut Buffer) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Draw<Buffer> for Repeat<'_> {
|
||||
fn draw (&self, to: &mut Buffer) {
|
||||
let XYWH(x, y, w, h) = to.area();
|
||||
let mut buf = to.buffer.write().unwrap();
|
||||
match self {
|
||||
Self::X(c) => {
|
||||
for x in x..x+w {
|
||||
if let Some(cell) = buf.cell_mut(Position::from((x, y))) {
|
||||
cell.set_symbol(&c);
|
||||
}
|
||||
}
|
||||
},
|
||||
Self::Y(c) => {
|
||||
for y in y..y+h {
|
||||
if let Some(cell) = buf.cell_mut(Position::from((x, y))) {
|
||||
cell.set_symbol(&c);
|
||||
}
|
||||
}
|
||||
},
|
||||
Self::XY(c) => {
|
||||
let a = c.len();
|
||||
for (_v, y) in (y..y+h).enumerate() {
|
||||
for (u, x) in (x..x+w).enumerate() {
|
||||
if let Some(cell) = buf.cell_mut(Position::from((x, y))) {
|
||||
let u = u % a;
|
||||
cell.set_symbol(&c[u..u+1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Draw<Buffer> for Scrollbar {
|
||||
fn draw (&self, to: &mut Buffer) {
|
||||
let XYWH(x1, y1, w, h) = to.area();
|
||||
let mut buf = to.buffer.write().unwrap();
|
||||
match self {
|
||||
Self::X { .. } => {
|
||||
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::ICON_DEC_H.len()) {
|
||||
cell.set_fg(Rgb(255, 255, 255));
|
||||
cell.set_bg(Rgb(0, 0, 0));
|
||||
cell.set_char(Self::ICON_DEC_H[i as usize]);
|
||||
} else if i > (w as usize - Self::ICON_INC_H.len()) {
|
||||
cell.set_fg(Rgb(255, 255, 255));
|
||||
cell.set_bg(Rgb(0, 0, 0));
|
||||
cell.set_char(Self::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('╌');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Self::Y { .. } => {
|
||||
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::ICON_DEC_V.len()) {
|
||||
cell.set_fg(Rgb(255, 255, 255));
|
||||
cell.set_bg(Rgb(0, 0, 0));
|
||||
cell.set_char(Self::ICON_DEC_V[i as usize]);
|
||||
} else if (i as usize) > (h as usize - Self::ICON_INC_V.len()) {
|
||||
cell.set_fg(Rgb(255, 255, 255));
|
||||
cell.set_bg(Rgb(0, 0, 0));
|
||||
cell.set_char(Self::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('╎'); // ━
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Draw<Buffer> for &str {
|
||||
fn draw (&self, to: &mut Buffer) {
|
||||
let XYWH(x, y, w, ..) = self.layout(to.area());
|
||||
to.text(&self, x, y, w)
|
||||
}
|
||||
}
|
||||
|
||||
impl Draw<Buffer> for String {
|
||||
fn draw (&self, to: &mut Buffer) {
|
||||
self.as_str().draw(to)
|
||||
}
|
||||
}
|
||||
|
||||
impl Draw<Buffer> for Arc<str> {
|
||||
fn draw (&self, to: &mut Buffer) { self.as_ref().draw(to) }
|
||||
}
|
||||
|
||||
impl<T: Draw<Buffer>> Draw<Buffer> for Foreground<Color, T> {
|
||||
fn draw (&self, to: &mut Buffer) {
|
||||
let area = self.layout(to.area());
|
||||
to.fill_fg(area, self.0);
|
||||
to.show(area, &self.1);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Draw<Buffer>> Draw<Buffer> for Background<Color, T> {
|
||||
fn draw (&self, to: &mut Buffer) {
|
||||
let area = self.layout(to.area());
|
||||
to.fill_bg(area, self.0);
|
||||
to.show(area, &self.1);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Draw<Buffer>> Draw<Buffer> for Modify<T> {
|
||||
fn draw (&self, to: &mut Buffer) {
|
||||
to.fill_mod(to.area(), self.0, self.1);
|
||||
self.2.draw(to)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Draw<Buffer>> Draw<Buffer> for Styled<T> {
|
||||
fn draw (&self, to: &mut Buffer) {
|
||||
to.place(&self.1);
|
||||
// TODO write style over area
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: BorderStyle> Draw<Buffer> for Border<S> {
|
||||
fn draw (&self, to: &mut Buffer) {
|
||||
let Border(enabled, style) = self;
|
||||
if *enabled {
|
||||
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());
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AsRef<str>> Draw<Buffer> for TrimString<T> {
|
||||
fn draw (&self, to: &mut Buffer) { Draw::draw(&self.as_ref(), to) }
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> Draw<Buffer> for TrimStringRef<'_, T> {
|
||||
fn draw (&self, target: &mut Buffer) {
|
||||
let area = target.area();
|
||||
let mut buf = target.buffer.write().unwrap();
|
||||
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
|
||||
}
|
||||
if let Some(cell) = buf.cell_mut(Position {
|
||||
x: area.x() + width - 1,
|
||||
y: area.y()
|
||||
}) {
|
||||
cell.set_char(c);
|
||||
}
|
||||
width += c.width().unwrap_or(0) as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TUI helper defs.
|
||||
impl Tui {
|
||||
pub const fn fg <T> (color: Color, w: T) -> Foreground<Color, T> { Foreground(color, w) }
|
||||
pub const fn bg <T> (color: Color, w: T) -> Background<Color, T> { Background(color, w) }
|
||||
pub const fn fg_bg <T> (fg: Color, bg: Color, w: T) -> Background<Color, Foreground<Color, T>> { Background(bg, Foreground(fg, w)) }
|
||||
pub const fn modify <T> (enable: bool, modifier: Modifier, w: T) -> Modify<T> { Modify(enable, modifier, w) }
|
||||
pub const fn bold <T> (enable: bool, w: T) -> Modify<T> { Self::modify(enable, Modifier::BOLD, w) }
|
||||
pub const fn border <S, T> (enable: bool, style: S, w: T) -> Bordered<S, T> { Bordered(enable, style, w) }
|
||||
|
||||
pub const fn null () -> Color { Color::Reset }
|
||||
pub const fn red () -> Color { Color::Rgb(255,0, 0) }
|
||||
pub const fn orange () -> Color { Color::Rgb(255,128,0) }
|
||||
pub const fn yellow () -> Color { Color::Rgb(255,255,0) }
|
||||
pub const fn brown () -> Color { Color::Rgb(128,255,0) }
|
||||
pub const fn green () -> Color { Color::Rgb(0,255,0) }
|
||||
pub const fn electric () -> Color { Color::Rgb(0,255,128) }
|
||||
pub const fn g (g: u8) -> Color { Color::Rgb(g, g, g) }
|
||||
//fn bg0 () -> Color { Color::Rgb(20, 20, 20) }
|
||||
//fn bg () -> Color { Color::Rgb(28, 35, 25) }
|
||||
//fn border_bg () -> Color { Color::Rgb(40, 50, 30) }
|
||||
//fn border_fg (f: bool) -> Color { if f { Self::bo1() } else { Self::bo2() } }
|
||||
//fn title_fg (f: bool) -> Color { if f { Self::ti1() } else { Self::ti2() } }
|
||||
//fn separator_fg (_: bool) -> Color { Color::Rgb(0, 0, 0) }
|
||||
//fn mode_bg () -> Color { Color::Rgb(150, 160, 90) }
|
||||
//fn mode_fg () -> Color { Color::Rgb(255, 255, 255) }
|
||||
//fn status_bar_bg () -> Color { Color::Rgb(28, 35, 25) }
|
||||
//fn bo1 () -> Color { Color::Rgb(100, 110, 40) }
|
||||
//fn bo2 () -> Color { Color::Rgb(70, 80, 50) }
|
||||
//fn ti1 () -> Color { Color::Rgb(150, 160, 90) }
|
||||
//fn ti2 () -> Color { Color::Rgb(120, 130, 100) }
|
||||
}
|
||||
pub fn named_key (token: &str) -> Option<KeyCode> {
|
||||
use KeyCode::*;
|
||||
Some(match token {
|
||||
"up" => Up,
|
||||
"down" => Down,
|
||||
"left" => Left,
|
||||
"right" => Right,
|
||||
"esc" | "escape" => Esc,
|
||||
"enter" | "return" => Enter,
|
||||
"delete" | "del" => Delete,
|
||||
"backspace" => Backspace,
|
||||
"tab" => Tab,
|
||||
"space" => Char(' '),
|
||||
"comma" => Char(','),
|
||||
"period" => Char('.'),
|
||||
"plus" => Char('+'),
|
||||
"minus" | "dash" => Char('-'),
|
||||
"equal" | "equals" => Char('='),
|
||||
"underscore" => Char('_'),
|
||||
"backtick" => Char('`'),
|
||||
"lt" => Char('<'),
|
||||
"gt" => Char('>'),
|
||||
"cbopen" | "openbrace" => Char('{'),
|
||||
"cbclose" | "closebrace" => Char('}'),
|
||||
"bropen" | "openbracket" => Char('['),
|
||||
"brclose" | "closebracket" => Char(']'),
|
||||
"pgup" | "pageup" => PageUp,
|
||||
"pgdn" | "pagedown" => PageDown,
|
||||
"f1" => F(1),
|
||||
"f2" => F(2),
|
||||
"f3" => F(3),
|
||||
"f4" => F(4),
|
||||
"f5" => F(5),
|
||||
"f6" => F(6),
|
||||
"f7" => F(7),
|
||||
"f8" => F(8),
|
||||
"f9" => F(9),
|
||||
"f10" => F(10),
|
||||
"f11" => F(11),
|
||||
"f12" => F(12),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
impl<O: Screen, Color, Item: Layout<O>> Layout<O> for Foreground<Color, Item> {
|
||||
fn layout (&self, to: XYWH<O::Unit>) -> XYWH<O::Unit> { self.1.layout(to) }
|
||||
}
|
||||
impl<O: Screen, Color, Item: Layout<O>> Layout<O> for Background<Color, Item> {
|
||||
fn layout (&self, to: XYWH<O::Unit>) -> XYWH<O::Unit> { self.1.layout(to) }
|
||||
}
|
||||
impl Tryptich<(), (), ()> {
|
||||
pub fn center (h: u16) -> Self {
|
||||
Self { h, top: false, left: (0, ()), middle: (0, ()), right: (0, ()) }
|
||||
}
|
||||
pub fn top (h: u16) -> Self {
|
||||
Self { h, top: true, left: (0, ()), middle: (0, ()), right: (0, ()) }
|
||||
}
|
||||
}
|
||||
impl<A, B, C> Tryptich<A, B, C> {
|
||||
pub fn left <D> (self, w: u16, content: D) -> Tryptich<D, B, C> {
|
||||
Tryptich { left: (w, content), ..self }
|
||||
}
|
||||
pub fn middle <D> (self, w: u16, content: D) -> Tryptich<A, D, C> {
|
||||
Tryptich { middle: (w, content), ..self }
|
||||
}
|
||||
pub fn right <D> (self, w: u16, content: D) -> Tryptich<A, B, D> {
|
||||
Tryptich { right: (w, content), ..self }
|
||||
}
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// let _ = tengri::button_2("", "", true);
|
||||
/// let _ = tengri::button_2("", "", false);
|
||||
/// ```
|
||||
pub fn button_2 <'a> (key: impl Draw<Buffer>, label: impl Draw<Buffer>, editing: bool) -> impl Draw<Buffer> {
|
||||
Tui::bold(true, Bsp::e(
|
||||
Tui::fg_bg(Tui::orange(), Tui::g(0), Bsp::e(Tui::fg(Tui::g(0), &"▐"), Bsp::e(key, Tui::fg(Tui::g(96), &"▐")))),
|
||||
When::new(!editing, Tui::fg_bg(Tui::g(255), Tui::g(96), label))))
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// let _ = tengri::button_3("", "", "", true);
|
||||
/// let _ = tengri::button_3("", "", "", false);
|
||||
/// ```
|
||||
pub fn button_3 <'a> (
|
||||
key: impl Draw<Buffer>, label: impl Draw<Buffer>, value: impl Draw<Buffer>, editing: bool,
|
||||
) -> impl Draw<Buffer> {
|
||||
Tui::bold(true, Bsp::e(
|
||||
Tui::fg_bg(Tui::orange(), Tui::g(0),
|
||||
Bsp::e(Tui::fg(Tui::g(0), &"▐"), Bsp::e(key, Tui::fg(if editing { Tui::g(128) } else { Tui::g(96) }, "▐")))),
|
||||
Bsp::e(
|
||||
When::new(!editing, Bsp::e(Tui::fg_bg(Tui::g(255), Tui::g(96), label), Tui::fg_bg(Tui::g(128), Tui::g(96), &"▐"),)),
|
||||
Bsp::e(Tui::fg_bg(Tui::g(224), Tui::g(128), value), Tui::fg_bg(Tui::g(128), Reset, &"▌"), ))))
|
||||
}
|
||||
|
||||
macro_rules! border {
|
||||
($($T:ident {
|
||||
$nw:literal $n:literal $ne:literal $w:literal $e:literal $sw:literal $s:literal $se:literal
|
||||
$($x:tt)*
|
||||
}),+) => {$(
|
||||
impl BorderStyle for $T {
|
||||
const NW: &'static str = $nw;
|
||||
const N: &'static str = $n;
|
||||
const NE: &'static str = $ne;
|
||||
const W: &'static str = $w;
|
||||
const E: &'static str = $e;
|
||||
const SW: &'static str = $sw;
|
||||
const S: &'static str = $s;
|
||||
const SE: &'static str = $se;
|
||||
$($x)*
|
||||
fn enabled (&self) -> bool { self.0 }
|
||||
}
|
||||
#[derive(Copy, Clone)] pub struct $T(pub bool, pub Style);
|
||||
impl Layout<Tui> for $T {}
|
||||
impl Draw<Buffer> for $T {
|
||||
fn draw (&self, to: &mut Buffer) {
|
||||
if self.enabled() { let _ = BorderStyle::draw(self, to); }
|
||||
}
|
||||
}
|
||||
)+}
|
||||
}
|
||||
|
||||
border! {
|
||||
Square {
|
||||
"┌" "─" "┐"
|
||||
"│" "│"
|
||||
"└" "─" "┘" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
},
|
||||
SquareBold {
|
||||
"┏" "━" "┓"
|
||||
"┃" "┃"
|
||||
"┗" "━" "┛" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
},
|
||||
TabLike {
|
||||
"╭" "─" "╮"
|
||||
"│" "│"
|
||||
"│" " " "│" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
},
|
||||
Lozenge {
|
||||
"╭" "─" "╮"
|
||||
"│" "│"
|
||||
"╰" "─" "╯" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
},
|
||||
Brace {
|
||||
"╭" "" "╮"
|
||||
"│" "│"
|
||||
"╰" "" "╯" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
},
|
||||
LozengeDotted {
|
||||
"╭" "┅" "╮"
|
||||
"┇" "┇"
|
||||
"╰" "┅" "╯" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
},
|
||||
Quarter {
|
||||
"▎" "▔" "🮇"
|
||||
"▎" "🮇"
|
||||
"▎" "▁" "🮇" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
},
|
||||
QuarterV {
|
||||
"▎" "" "🮇"
|
||||
"▎" "🮇"
|
||||
"▎" "" "🮇" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
},
|
||||
Chamfer {
|
||||
"🭂" "▔" "🭍"
|
||||
"▎" "🮇"
|
||||
"🭓" "▁" "🭞" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
},
|
||||
Corners {
|
||||
"🬆" "" "🬊" // 🬴 🬸
|
||||
"" ""
|
||||
"🬱" "" "🬵" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
},
|
||||
CornersTall {
|
||||
"🭽" "" "🭾"
|
||||
"" ""
|
||||
"🭼" "" "🭿" fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
},
|
||||
Outer {
|
||||
"🭽" "▔" "🭾"
|
||||
"▏" "▕"
|
||||
"🭼" "▁" "🭿"
|
||||
const W0: &'static str = "[";
|
||||
const E0: &'static str = "]";
|
||||
const N0: &'static str = "⎴";
|
||||
const S0: &'static str = "⎵";
|
||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
},
|
||||
Thick {
|
||||
"▄" "▄" "▄"
|
||||
"█" "█"
|
||||
"▀" "▀" "▀"
|
||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
},
|
||||
Rugged {
|
||||
"▄" "▂" "▄"
|
||||
"▐" "▌"
|
||||
"▀" "🮂" "▀"
|
||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
},
|
||||
Skinny {
|
||||
"▗" "▄" "▖"
|
||||
"▐" "▌"
|
||||
"▝" "▀" "▘"
|
||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
},
|
||||
Brackets {
|
||||
"⎡" "" "⎤"
|
||||
"" ""
|
||||
"⎣" "" "⎦"
|
||||
const W0: &'static str = "[";
|
||||
const E0: &'static str = "]";
|
||||
const N0: &'static str = "⎴";
|
||||
const S0: &'static str = "⎵";
|
||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
},
|
||||
Reticle {
|
||||
"⎡" "" "⎤"
|
||||
"" ""
|
||||
"⎣" "" "⎦"
|
||||
const W0: &'static str = "╟";
|
||||
const E0: &'static str = "╢";
|
||||
const N0: &'static str = "┯";
|
||||
const S0: &'static str = "┷";
|
||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn okhsl_to_rgb (color: Okhsl<f32>) -> Color {
|
||||
let Srgb { red, green, blue, .. }: Srgb<f32> = Srgb::from_color_unclamped(color);
|
||||
Color::Rgb((red * 255.0) as u8, (green * 255.0) as u8, (blue * 255.0) as u8,)
|
||||
}
|
||||
|
||||
pub fn rgb_to_okhsl (color: Color) -> Okhsl<f32> {
|
||||
if let Color::Rgb(r, g, b) = color {
|
||||
Okhsl::from_color(Srgb::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0))
|
||||
} else {
|
||||
unreachable!("only Color::Rgb is supported")
|
||||
}
|
||||
}
|
||||
|
||||
// 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) }
|
||||
}
|
||||
pub fn random () -> Self {
|
||||
let mut rng = ::rand::thread_rng();
|
||||
let lo = Okhsl::new(-180.0, 0.01, 0.25);
|
||||
let hi = Okhsl::new( 180.0, 0.9, 0.5);
|
||||
UniformOkhsl::new(lo, hi).sample(&mut rng).into()
|
||||
}
|
||||
pub fn random_dark () -> Self {
|
||||
let mut rng = ::rand::thread_rng();
|
||||
let lo = Okhsl::new(-180.0, 0.025, 0.075);
|
||||
let hi = Okhsl::new( 180.0, 0.5, 0.150);
|
||||
UniformOkhsl::new(lo, hi).sample(&mut rng).into()
|
||||
}
|
||||
pub fn random_near (color: Self, distance: f32) -> Self {
|
||||
color.mix(Self::random(), distance)
|
||||
}
|
||||
pub fn mix (&self, other: Self, distance: f32) -> Self {
|
||||
if distance > 1.0 { panic!("color mixing takes distance between 0.0 and 1.0"); }
|
||||
self.okhsl.mix(other.okhsl, distance).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemTheme {
|
||||
#[cfg(feature = "tui")] pub const G: [Self;256] = {
|
||||
let mut builder = konst::array::ArrayBuilder::new();
|
||||
while !builder.is_full() {
|
||||
let index = builder.len() as u8;
|
||||
let light = (index as f64 * 1.15) as u8;
|
||||
let lighter = (index as f64 * 1.7) as u8;
|
||||
let lightest = (index as f64 * 1.85) as u8;
|
||||
let dark = (index as f64 * 0.9) as u8;
|
||||
let darker = (index as f64 * 0.6) as u8;
|
||||
let darkest = (index as f64 * 0.3) as u8;
|
||||
builder.push(ItemTheme {
|
||||
base: ItemColor::from_tui(Color::Rgb(index, index, index )),
|
||||
light: ItemColor::from_tui(Color::Rgb(light, light, light, )),
|
||||
lighter: ItemColor::from_tui(Color::Rgb(lighter, lighter, lighter, )),
|
||||
lightest: ItemColor::from_tui(Color::Rgb(lightest, lightest, lightest, )),
|
||||
dark: ItemColor::from_tui(Color::Rgb(dark, dark, dark, )),
|
||||
darker: ItemColor::from_tui(Color::Rgb(darker, darker, darker, )),
|
||||
darkest: ItemColor::from_tui(Color::Rgb(darkest, darkest, darkest, )),
|
||||
});
|
||||
}
|
||||
builder.build()
|
||||
};
|
||||
pub fn random () -> Self { ItemColor::random().into() }
|
||||
pub fn random_near (color: Self, distance: f32) -> Self {
|
||||
color.base.mix(ItemColor::random(), distance).into()
|
||||
}
|
||||
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)
|
||||
};
|
||||
Self {
|
||||
base: color,
|
||||
light: color,
|
||||
lighter: color,
|
||||
lightest: color,
|
||||
dark: color,
|
||||
darker: color,
|
||||
darkest: color,
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "tui")] pub fn from_tui_color (base: Color) -> Self {
|
||||
Self::from_item_color(ItemColor::from_tui(base))
|
||||
}
|
||||
pub fn from_item_color (base: ItemColor) -> Self {
|
||||
let mut light = base.okhsl;
|
||||
light.lightness = (light.lightness * 1.3).min(1.0);
|
||||
let mut lighter = light;
|
||||
lighter.lightness = (lighter.lightness * 1.3).min(1.0);
|
||||
let mut lightest = base.okhsl;
|
||||
lightest.lightness = 0.95;
|
||||
let mut dark = base.okhsl;
|
||||
dark.lightness = (dark.lightness * 0.75).max(0.0);
|
||||
dark.saturation = (dark.saturation * 0.75).max(0.0);
|
||||
let mut darker = dark;
|
||||
darker.lightness = (darker.lightness * 0.66).max(0.0);
|
||||
darker.saturation = (darker.saturation * 0.66).max(0.0);
|
||||
let mut darkest = darker;
|
||||
darkest.lightness = 0.1;
|
||||
darkest.saturation = (darkest.saturation * 0.50).max(0.0);
|
||||
Self {
|
||||
base,
|
||||
light: light.into(),
|
||||
lighter: lighter.into(),
|
||||
lightest: lightest.into(),
|
||||
dark: dark.into(),
|
||||
darker: darker.into(),
|
||||
darkest: darkest.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue