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

385
src/space.rs Normal file
View file

@ -0,0 +1,385 @@
use crate::{*, draw::*};
/// Point with size.
///
/// ```
/// let xywh = tengri::XYWH(0u16, 0, 0, 0);
/// assert_eq!(tengri::XYWH(10u16, 10, 20, 20).center(), tengri::XY(20, 20));
/// ```
///
/// * [ ] TODO: origin field (determines at which corner/side is X0 Y0)
///
#[cfg_attr(test, derive(Arbitrary))] #[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct XYWH<N: Coord>(pub N, pub N, pub N, pub N);
impl<N: Coord> X<N> for XYWH<N> { fn x (&self) -> N { self.0 } fn w (&self) -> N { self.2 } }
impl<N: Coord> Y<N> for XYWH<N> { fn y (&self) -> N { self.0 } fn h (&self) -> N { self.2 } }
impl<N: Coord> XYWH<N> {
pub fn zero () -> Self {
Self(0.into(), 0.into(), 0.into(), 0.into())
}
pub fn center (&self) -> (N, N) {
let Self(x, y, w, h) = *self;
(x.plus(w/2.into()), y.plus(h/2.into()))
}
pub fn centered (&self) -> (N, N) {
let Self(x, y, w, h) = *self;
(x.minus(w/2.into()), y.minus(h/2.into()))
}
pub fn centered_x (&self, n: N) -> Self {
let Self(x, y, w, h) = *self;
let x_center = (x.plus(w / 2.into())).minus(n / 2.into());
let y_center = y.plus(h / 2.into());
XYWH(x_center, y_center, n, 1.into())
}
pub fn centered_y (&self, n: N) -> Self {
let Self(x, y, w, h) = *self;
let x_center = x.plus(w / 2.into());
let y_corner = (y.plus(h / 2.into())).minus(n / 2.into());
XYWH(x_center, y_corner, 1.into(), n)
}
pub fn centered_xy (&self, [n, m]: [N;2]) -> Self {
let Self(x, y, w, h) = *self;
let x_center = (x.plus(w / 2.into())).minus(n / 2.into());
let y_corner = (y.plus(h / 2.into())).minus(m / 2.into());
XYWH(x_center, y_corner, n, m)
}
pub fn split_half (&self, direction: &Split) -> (Self, Self) {
use Split::*;
let XYWH(x, y, w, h) = self.xywh();
match direction {
South => (XYWH(x, y, w, h - h / 2.into()), XYWH(x, y + h / 2.into(), w, h / 2.into())),
East => (XYWH(x, y, w - w / 2.into(), h), XYWH(x + w / 2.into(), y, w / 2.into(), h)),
North => (XYWH(x, y + h / 2.into(), w, h - h / 2.into()), XYWH(x, y, w, h / 2.into())),
West => (XYWH(x + w / 2.into(), y, w - w / 2.into(), h), XYWH(x, y, w / 2.into(), h)),
Above | Below => (XYWH(x, y, w, h), XYWH(x, y, w, h))
}
}
}
/// Something that has `[0, 0]` at a particular point.
pub trait HasOrigin {
fn origin (&self) -> Origin;
}
impl<T: AsRef<Origin>> HasOrigin for T {
fn origin (&self) -> Origin { *self.as_ref() }
}
/// Where is [0, 0] located?
///
/// ```
/// use tengri::draw::Origin;
/// let _ = Origin::NW.align(())
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Debug, Copy, Clone, Default)] pub enum Origin {
#[default] C, X, Y, NW, N, NE, E, SE, S, SW, W
}
impl Origin {
pub fn align <T: Screen> (&self, a: impl Draw<T>) -> impl Draw<T> {
align(*self, a)
}
}
/// ```
/// use tengri::draw::{align, Origin::*};
/// let _ = align(NW, "test");
/// let _ = align(SE, "test");
/// ```
pub fn align <T: Screen> (origin: Origin, a: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T| { todo!() })
}
/// A numeric type that can be used as coordinate.
///
/// FIXME: Replace with `num` crate?
/// FIXME: Use AsRef/AsMut?
///
/// ```
/// use tengri::draw::Coord;
/// let a: u16 = Coord::zero();
/// let b: u16 = a.plus(1);
/// let c: u16 = a.minus(2);
/// let d = a.atomic();
/// ```
pub trait Coord: Send + Sync + Copy
+ Add<Self, Output=Self>
+ Sub<Self, Output=Self>
+ Mul<Self, Output=Self>
+ Div<Self, Output=Self>
+ Ord + PartialEq + Eq
+ Debug + Display + Default
+ From<u16> + Into<u16>
+ Into<usize>
+ Into<f64>
+ std::iter::Step
{
/// Zero in own type.
fn zero () -> Self { 0.into() }
/// Addition.
fn plus (self, other: Self) -> Self;
/// Saturating subtraction.
fn minus (self, other: Self) -> Self { if self >= other { self - other } else { 0.into() } }
/// Convert to [AtomicUsize].
fn atomic (self) -> AtomicUsize { AtomicUsize::new(self.into()) }
}
/// A cardinal direction.
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, PartialEq, Debug, Default)] pub enum Split {
North, South, East, West, Above, #[default] Below
}
pub const fn east <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::East.half(a, b)
}
pub const fn north <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::North.half(a, b)
}
pub const fn west <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::West.half(a, b)
}
pub const fn south <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::South.half(a, b)
}
pub const fn above <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::Above.half(a, b)
}
pub const fn below <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::Below.half(a, b)
}
impl Split {
/// ```
/// use tengri::draw::Split::*;
/// let _ = Above.bsp((), ());
/// let _ = Below.bsp((), ());
/// let _ = North.bsp((), ());
/// let _ = South.bsp((), ());
/// let _ = East.bsp((), ());
/// let _ = West.bsp((), ());
/// ```
pub const fn half <T: Screen> (&self, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|{
let (area_a, area_b) = to.xywh().split_half(self);
let (origin_a, origin_b) = self.origins();
match self {
Self::Below => {
to.place(&origin_b.align(b), Some(area_b));
to.place(&origin_a.align(a), Some(area_b));
},
_ => {
to.place(&origin_a.align(a), Some(area_a));
to.place(&origin_b.align(b), Some(area_a));
}
}
Ok(to.xywh()) // FIXME: compute and return actually used area
})
}
/// Newly split areas begin at the center of the split
/// to maintain centeredness in the user's field of view.
///
/// Use [align] to override that and always start
/// at the top, bottom, etc.
///
/// ```
/// /*
///
/// Split east: Split south:
/// | | | | A |
/// | <-A|B-> | |---------|
/// | | | | B |
///
/// */
/// ```
const fn origins (&self) -> (Origin, Origin) {
use Origin::*;
match self {
Self::South => (S, N),
Self::East => (E, W),
Self::North => (N, S),
Self::West => (W, E),
Self::Above => (C, C),
Self::Below => (C, C),
}
}
/// Iterate over a collection of renderables:
///
/// ```
/// use tengri::draw::{Origin::*, Split::*};
/// let _ = Below.iter([
/// NW.align(W(15).max(W(10).min("Leftbar"))),
/// NE.align(W(12).max(W(10).min("Rightbar"))),
/// Center.align(W(40).max(H(20.max("Center"))))
/// ].iter(), |x|x);
/// ```
pub fn iter <T: Screen, U: Draw<T>, F: Fn(U)->dyn Draw<T>> (
_items: impl Iterator<Item = U>, _cb: F
) -> impl Draw<T> {
thunk(move|_to: &mut T|{ todo!() })
}
}
/// Horizontal axis.
pub trait X<N: Coord> {
fn x (&self) -> N;
fn w (&self) -> N { N::zero() }
fn w_min (&self) -> N { self.w() }
fn w_max (&self) -> N { self.w() }
fn iter_x (&self) -> impl Iterator<Item = N> where Self: HasOrigin {
self.x_west()..self.x_east()
}
fn x_west (&self) -> N where Self: HasOrigin {
use Origin::*;
let w = self.w();
let a = self.origin();
let d = match a { NW|W|SW => 0.into(), N|X|C|Y|S => w/2.into(), NE|E|SE => w };
self.x().minus(d)
}
fn x_east (&self) -> N where Self: HasOrigin {
use Origin::*;
let w = self.w();
let a = self.origin();
let d = match a { NW|W|SW => w, N|X|C|Y|S => w/2.into(), NE|E|SE => 0.into() };
self.x().plus(d)
}
fn x_center (&self) -> N where Self: HasOrigin {
todo!()
}
}
pub const fn x_push <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn x_pull <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn w_max <T: Screen> (w: Option<T::Unit>, a: impl Draw<T>) -> impl Draw<T> {
wh_max(w, None, a)
}
pub const fn w_min <T: Screen> (w: Option<T::Unit>, a: impl Draw<T>) -> impl Draw<T> {
wh_min(w, None, a)
}
pub const fn w_exact <T: Screen> (w: T::Unit, c: impl Draw<T>) -> impl Draw<T> {
wh_exact(Some(w), None, c)
}
/// Shrink drawing area symmetrically.
///
/// ```
/// let padded = tengri::W(3).pad("Hello");
/// ```
pub const fn w_pad <T: Screen> (x: T::Unit, draw: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|draw.draw(todo!()))
}
pub trait Y<N: Coord> {
fn y (&self) -> N;
fn h (&self) -> N { N::zero() }
fn h_min (&self) -> N { self.h() }
fn h_max (&self) -> N { self.h() }
fn iter_y (&self) -> impl Iterator<Item = N> where Self: HasOrigin {
self.y_north()..self.y_south()
}
fn y_north (&self) -> N where Self: HasOrigin {
let a = self.origin();
let h = self.h();
use Origin::*;
let d = match a { NW|N|NE => 0.into(), W|X|C|Y|E => h/2.into(), SW|S|SE => h };
self.y().minus(d)
}
fn y_south (&self) -> N where Self: HasOrigin {
let a = self.origin();
let h = self.h();
use Origin::*;
let d = match a { NW|N|NE => h, W|X|C|Y|E => h/2.into(), SW|S|SE => 0.into() };
self.y().plus(d)
}
fn y_center (&self) -> N where Self: HasOrigin {
todo!()
}
}
pub const fn y_push <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn y_pull <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn h_max <T: Screen> (h: Option<T::Unit>, a: impl Draw<T>) -> impl Draw<T> {
wh_max(None, h, a)
}
pub const fn h_min <T: Screen> (h: Option<T::Unit>, a: impl Draw<T>) -> impl Draw<T> {
wh_min(None, h, a)
}
pub const fn h_exact <T: Screen> (h: T::Unit, c: impl Draw<T>) -> impl Draw<T> {
wh_exact(None, Some(h), c)
}
/// Shrink drawing area symmetrically.
///
/// ```
/// let padded = tengri::W::pad(3, "Hello");
/// ```
pub const fn h_pad <T: Screen> (x: T::Unit, draw: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|draw.draw(todo!()))
}
pub trait Space<N: Coord>: X<N> + Y<N> {
fn xywh (&self) -> XYWH<N> { XYWH(self.x(), self.y(), self.w(), self.h()) }
// FIXME: factor origin
fn lrtb (&self) -> [N;4] { [self.x(), self.y(), self.x()+self.w(), self.y()+self.h()] }
}
impl<N: Coord, T: X<N> + Y<N>> Space<N> for T {}
pub const fn xy_push <T: Screen> (x: T::Unit, y: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
pub const fn xy_pull <T: Screen> (x: T::Unit, y: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
a
}
/// Shrink drawing area symmetrically.
///
/// ```
/// let padded = tengri::WH(3, 5).pad("Hello");
/// ```
pub const fn wh_pad <T: Screen> (w: T::Unit, h: T::Unit, draw: impl Draw<T>)
-> impl Draw<T>
{
thunk(move|to: &mut T|draw.draw(todo!()))
}
/// Only draw content if area is above a certain size.
///
/// ```
/// let min = tengri::wh_min(3, 5, "Hello"); // 5x5
/// ```
pub const fn wh_min <T: Screen> (w: Option<T::Unit>, h: Option<T::Unit>, draw: impl Draw<T>)
-> impl Draw<T>
{
thunk(move|to: &mut T|draw.draw(todo!()))
}
/// Set the maximum width and/or height of the content.
///
/// ```
/// let max = tengri::wh_max(Some(3), Some(5), "Hello");
/// ```
pub const fn wh_max <T: Screen> (w: Option<T::Unit>, h: Option<T::Unit>, draw: impl Draw<T>)
-> impl Draw<T>
{
thunk(move|to: &mut T|draw.draw(todo!()))
}
/// Set the maximum width and/or height of the content.
///
/// ```
/// let exact = tengri::wh_exact(Some(3), Some(5), "Hello");
/// ```
pub const fn wh_exact <T: Screen> (w: Option<T::Unit>, h: Option<T::Unit>, draw: impl Draw<T>)
-> impl Draw<T>
{
thunk(move|to: &mut T|draw.draw(todo!()))
}
/// Limit size of drawing area
/// ```
/// let clipped = tengri::wh_clip(Some(3), Some(5), "Hello");
/// ```
pub const fn wh_clip <T: Screen> (
w: Option<T::Unit>, h: Option<T::Unit>, draw: impl Draw<T>
) -> impl Draw<T> {
thunk(move|to: &mut T|draw.draw(todo!()))
}