wip: refactor(tengri): routines!

This commit is contained in:
same mf who else 2026-03-09 02:06:30 +02:00
parent 8f0a2accce
commit 4e8d58d793
9 changed files with 1495 additions and 1669 deletions

View file

@ -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() } }