still dark; refactor and document layout crate

This commit is contained in:
🪞👃🪞 2024-12-31 19:57:03 +01:00
parent ed72ab1635
commit 675d376100
12 changed files with 510 additions and 659 deletions

View file

@ -2,3 +2,19 @@
this crate exposes several layout operators
which are generic over `tek_engine::Engine`.
* `Fill` makes the content's dimension equal to the container's.
* `Fixed` assigns a fixed dimension to its content.
* `Shrink` reduces the dimension of the content
* `Expand` increases the dimension of the content
* `Min` enforces minimum dimension for the content
* `Max` enforces maximum dimension for the content
* `Push` moves the content in the positive direction
* `Pull` moves the content in the negative direction
* `Margin` grows each dimension from both ends
* `Padding` shrinks each dimension from both ends
* `Align` pins the content along an axis of the container
* `When` renders a content conditionally
* `Either` alternates between two contents
* `Map` transforms each content
* `Reduce` transforms all contents into one

76
layout/src/align.rs Normal file
View file

@ -0,0 +1,76 @@
use crate::*;
#[derive(Default, Debug, Copy, Clone)]
pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W }
pub struct Align<E: Engine, T: Content<E>>(Alignment, T, PhantomData<E>);
impl<E: Engine, T: Content<E>> Align<E, T> {
pub fn c (w: T) -> Self { Self(Alignment::Center, w, Default::default()) }
pub fn x (w: T) -> Self { Self(Alignment::X, w, Default::default()) }
pub fn y (w: T) -> Self { Self(Alignment::Y, w, Default::default()) }
pub fn n (w: T) -> Self { Self(Alignment::N, w, Default::default()) }
pub fn s (w: T) -> Self { Self(Alignment::S, w, Default::default()) }
pub fn e (w: T) -> Self { Self(Alignment::E, w, Default::default()) }
pub fn w (w: T) -> Self { Self(Alignment::W, w, Default::default()) }
pub fn nw (w: T) -> Self { Self(Alignment::NW, w, Default::default()) }
pub fn sw (w: T) -> Self { Self(Alignment::SW, w, Default::default()) }
pub fn ne (w: T) -> Self { Self(Alignment::NE, w, Default::default()) }
pub fn se (w: T) -> Self { Self(Alignment::SE, w, Default::default()) }
}
impl<E: Engine, T: Content<E>> Content<E> for Align<E, T> {
fn area (&self, outer: E::Area) -> E::Area {
align_areas(self.0, outer.xywh(), Content::area(&self.content(), outer).xywh()).into()
}
fn render (&self, render: &mut E::Output) {
render.place(self.area(render.area()), &self.content())
}
}
pub fn align_areas<N: Coordinate>(alignment: Alignment, on: [N;4], it: [N;4]) -> [N;4] {
let [cfx, cfy] = on.center();
let [cmx, cmy] = it.center();
let center = |cf, cm, m: N|if cf >= cm { m + (cf - cm) } else { m.minus(cm - cf) };
let center_x = center(cfx, cmx, it.x());
let center_y = center(cfy, cmy, it.y());
let east_x = on.x() + on.w().minus(it.w());
let south_y = on.y() + on.h().minus(it.h());
match alignment {
Alignment::Center => [center_x, center_y, it.w(), it.h()],
Alignment::X => [center_x, it.y(), it.w(), it.h()],
Alignment::Y => [it.x(), center_y, it.w(), it.h()],
Alignment::NW => [on.x(), on.y(), it.w(), it.h()],
Alignment::N => [center_x, on.y(), it.w(), it.h()],
Alignment::NE => [east_x, on.y(), it.w(), it.h()],
Alignment::E => [east_x, center_y, it.w(), it.h()],
Alignment::SE => [east_x, south_y, it.w(), it.h()],
Alignment::S => [center_x, south_y, it.w(), it.h()],
Alignment::SW => [on.x(), south_y, it.w(), it.h()],
Alignment::W => [on.x(), center_y, it.w(), it.h()],
}
}
//fn align<E: Engine, T: Content<E>, N: Coordinate, R: Area<N> + From<[N;4]>> (align: &Align<E, T>, outer: R, content: R) -> Option<R> {
//if outer.w() < content.w() || outer.h() < content.h() {
//None
//} else {
//let [ox, oy, ow, oh] = outer.xywh();
//let [ix, iy, iw, ih] = content.xywh();
//Some(match align {
//Align::Center(_) => [ox + (ow - iw) / 2.into(), oy + (oh - ih) / 2.into(), iw, ih,].into(),
//Align::X(_) => [ox + (ow - iw) / 2.into(), iy, iw, ih,].into(),
//Align::Y(_) => [ix, oy + (oh - ih) / 2.into(), iw, ih,].into(),
//Align::NW(_) => [ox, oy, iw, ih,].into(),
//Align::N(_) => [ox + (ow - iw) / 2.into(), oy, iw, ih,].into(),
//Align::NE(_) => [ox + ow - iw, oy, iw, ih,].into(),
//Align::W(_) => [ox, oy + (oh - ih) / 2.into(), iw, ih,].into(),
//Align::E(_) => [ox + ow - iw, oy + (oh - ih) / 2.into(), iw, ih,].into(),
//Align::SW(_) => [ox, oy + oh - ih, iw, ih,].into(),
//Align::S(_) => [ox + (ow - iw) / 2.into(), oy + oh - ih, iw, ih,].into(),
//Align::SE(_) => [ox + ow - iw, oy + oh - ih, iw, ih,].into(),
//_ => unreachable!()
//})
//}
//}

144
layout/src/bsp.rs Normal file
View file

@ -0,0 +1,144 @@
use crate::*;
/// Renders multiple things on top of each other,
#[macro_export] macro_rules! lay {
($($expr:expr),* $(,)?) => {{
let bsp = ();
$(let bsp = Bsp::b(bsp, $expr);)*;
bsp
}}
}
/// Stack southward.
#[macro_export] macro_rules! col {
($($expr:expr),* $(,)?) => {{
let bsp = ();
$(let bsp = Bsp::s(bsp, $expr);)*;
bsp
}};
}
/// Stack northward.
#[macro_export] macro_rules! col_up {
($($expr:expr),* $(,)?) => {{
let bsp = ();
$(let bsp = Bsp::n(bsp, $expr);)*;
bsp
}}
}
/// Stack eastward.
#[macro_export] macro_rules! row {
($($expr:expr),* $(,)?) => {{
let bsp = ();
$(let bsp = Bsp::e(bsp, $expr);)*;
bsp
}};
}
pub enum Bsp<E: Engine, X: Content<E>, Y: Content<E>> {
/// X is north of Y
North(Option<f64>, Option<X>, Option<Y>),
/// X is south of Y
South(Option<f64>, Option<X>, Option<Y>),
/// X is east of Y
East(Option<f64>, Option<X>, Option<Y>),
/// X is west of Y
West(Option<f64>, Option<X>, Option<Y>),
/// X is above Y
Above(Option<X>, Option<Y>),
/// X is below Y
Below(Option<X>, Option<Y>),
/// Should be avoided.
Null(PhantomData<E>),
}
impl<E: Engine, X: Content<E>, Y: Content<E>> Bsp<E, X, Y> {
pub fn n (x: X, y: Y) -> Self { Self::North(None, Some(x), Some(y)) }
pub fn s (x: X, y: Y) -> Self { Self::South(None, Some(x), Some(y)) }
pub fn e (x: X, y: Y) -> Self { Self::East(None, Some(x), Some(y)) }
pub fn w (x: X, y: Y) -> Self { Self::West(None, Some(x), Some(y)) }
pub fn a (x: X, y: Y) -> Self { Self::Above(Some(x), Some(y)) }
pub fn b (x: X, y: Y) -> Self { Self::Below(Some(x), Some(y)) }
}
impl<E: Engine, X: Content<E>, Y: Content<E>> Default for Bsp<E, X, Y> {
fn default () -> Self {
Self::Null(Default::default())
}
}
impl<E: Engine, X: Content<E>, Y: Content<E>> Content<E> for Bsp<E, X, Y> {
fn area (&self, outer: E::Area) -> E::Area {
match self {
Self::Null(_) => [0.into(), 0.into(), 0.into(), 0.into()].into(),
Self::North(_, a, b) => {
let a = a.area(outer);
let b = b.area(North.split_fixed(outer, a.y() + a.h()).1.into());
[a.x().min(b.x()), a.y().min(b.y()), a.w().max(b.w()), a.h() + b.h()].into()
}
Self::South(_, a, b) => {
let a = a.area(outer);
let b = b.area(South.split_fixed(outer, a.y() + a.h()).1.into());
[a.x().min(b.x()), a.y().min(b.y()), a.w().max(b.w()), a.h() + b.h()].into()
},
Self::East(_, a, b) => {
let a = a.area(outer);
let b = b.area(East.split_fixed(outer, a.x() + a.w()).1.into());
[a.x().min(b.x()), a.y().min(b.y()), a.w() + b.w(), a.h().max(b.h())].into()
},
Self::West(_, a, b) => {
let a = a.area(outer);
let b = b.area(West.split_fixed(outer, a.x() + a.w()).1.into());
[a.x().min(b.x()), a.y().min(b.y()), a.w() + b.w(), a.h().max(b.h())].into()
},
Self::Above(a, b) | Self::Below(a, b) => {
let a = a.area(outer);
let b = b.area(outer);
[a.x().min(b.x()), a.y().min(b.y()), a.w().max(b.w()), a.h().max(b.h())].into()
}
}
}
fn render (&self, to: &mut E::Output) {
let area = to.area().clone();
match self {
Self::North(_, a, b) => {
let area_a = a.area(area);
let area_b = b.area(North.split_fixed(area, area_a.y() + area_a.h()).1.into());
to.place(area_a, a);
to.place(area_b, b);
},
Self::South(_, a, b) => {
let area_a = a.area(area).clone();
let area_b = b.area(South.split_fixed(area, area_a.y() + area_a.h()).1.into()).clone();
to.place(area_a, a);
to.place(area_b, b);
},
Self::East(_, a, b) => {
let area_a = a.area(area);
let area_b = b.area(East.split_fixed(area, area_a.x() + area_a.w()).1.into());
to.place(area_a, a);
to.place(area_b, b);
},
Self::West(_, a, b) => {
let area_a = a.area(area);
let area_b = b.area(West.split_fixed(area, area_a.x() + area_a.w()).1.into());
to.place(area_a, a);
to.place(area_b, b);
},
Self::Above(a, b) => {
let area_a = a.area(area);
let area_b = b.area(area);
to.place(area_b, b);
to.place(area_a, a);
},
Self::Below(a, b) => {
let area_a = a.area(area);
let area_b = b.area(area);
to.place(area_a, a);
to.place(area_b, b);
},
Self::Null(_) => {}
}
}
}

View file

@ -1,88 +1,14 @@
//! Groupings of elements.
//mod split; pub use self::split::*;
//mod stack; pub use self::stack::*;
use crate::*;
use std::sync::RwLock;
/// A function or closure that emits renderables.
pub trait Collector<E: Engine>: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)) {}
impl<E: Engine> Layout<E> for E {}
/// Any function or closure that emits renderables for the given engine matches [CollectCallback].
impl<E, F> Collector<E> for F
where E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)) {}
pub trait Layout<E: Engine> {
/// Content `item` when `cond` is true.
fn when <A: Content<E>> (cond: bool, item: A) -> When<E, A> {
When(cond, item, Default::default())
}
/// Content `item` if `cond` is true, otherwise render `other`.
fn either <A: Content<E>, B: Content<E>> (cond: bool, a: A, b: B)
-> Either<E, A, B>
{
Either(cond, a, b, Default::default())
}
/// Maps an [Option<T>] through a callback `F`
fn opt <A, F: Fn(A)->R, R: Content<E>> (option: Option<A>, cb: F)
-> Opt<E, A, F, R>
{
Opt(option, cb, Default::default())
}
fn map <T, I, R, F>(iterator: I, callback: F) -> Map<E, T, I, R, F> where
E: Engine,
I: Iterator<Item = T> + Send + Sync,
R: Content<E>,
F: Fn(T, usize)->R + Send + Sync
{
Map(Default::default(), RwLock::new(iterator), callback)
}
//pub fn reduce <E, T, I, R, F>(iterator: I, callback: F) -> Reduce<E, T, I, R, F> where
//E: Engine,
//I: Iterator<Item = T> + Send + Sync,
//R: Content<E>,
//F: Fn(R, T, usize) -> R + Send + Sync
//{
//Reduce(Default::default(), iterator, callback)
//}
}
pub struct Opt<E: Engine, A, F: Fn(A)->R, R: Content<E>>(Option<A>, F, PhantomData<E>);
/// Contents `self.1` when `self.0` is true.
pub struct When<E: Engine, A>(bool, A, PhantomData<E>);
impl<E: Engine, A: Content<E>> Content<E> for When<E, A> {
fn area (&self, to: E::Area) -> E::Area {
let Self(cond, item, ..) = self;
let mut area = E::Area::zero();
if *cond {
let item_area = item.area(to);
area[0] = item_area.x();
area[1] = item_area.y();
area[2] = item_area.w();
area[3] = item_area.h();
}
area.into()
}
fn render (&self, to: &mut E::Output) {
let Self(cond, item, ..) = self;
if *cond { item.render(to) }
}
}
/// Contents `self.1` when `self.0` is true, otherwise renders `self.2`
pub struct Either<E: Engine, A, B>(bool, A, B, PhantomData<E>);
impl<E: Engine, A: Content<E>, B: Content<E>> Content<E> for Either<E, A, B> {
fn area (&self, to: E::Area) -> E::Area {
let Self(cond, a, b, ..) = self;
if *cond { a.area(to) } else { b.area(to) }
}
fn render (&self, to: &mut E::Output) {
let Self(cond, a, b, ..) = self;
if *cond { a.render(to) } else { b.render(to) }
}
}
trait Render<E: Engine> {
pub trait Render<E: Engine> {
fn area (&self, to: E::Area) -> E::Area;
fn render (&self, to: &mut E::Output);
}
@ -95,55 +21,3 @@ impl<E: Engine, C: Content<E>> Render<E> for C {
Content::render(self, to)
}
}
/// A function or closure that emits renderables.
pub trait Collector<E: Engine>: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)) {}
/// Any function or closure that emits renderables for the given engine matches [CollectCallback].
impl<E, F> Collector<E> for F
where E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)) {}
/// Rendering of iterable collections, one-to-one and many-to one.
pub struct Coll;
impl Coll {
}
pub struct Map<E, T, I, R, F>(PhantomData<E>, RwLock<I>, F) where
E: Engine,
I: Iterator<Item = T> + Send + Sync,
R: Content<E>,
F: Fn(T, usize)->R + Send + Sync;
impl<E, T, I, R, F> Content<E> for Map<E, T, I, R, F> where
E: Engine,
I: Iterator<Item = T> + Send + Sync,
R: Content<E>,
F: Fn(T, usize)->R + Send + Sync
{
fn render (&self, to: &mut E::Output) {
let mut index = 0;
for item in &mut*self.1.write().unwrap() {
(self.2)(item, index).render(to);
index += 1;
}
}
}
/*
pub struct Reduce<E, T, I, R, F>(PhantomData<(E, R)>, I, F) where
E: Engine,
I: Iterator<Item = T> + Send + Sync,
R: Content<E>,
F: Fn(R, T, usize) -> R + Send + Sync;
impl<E, T, I, R, F> Content<E> for Reduce<E, T, I, R, F> where
E: Engine,
I: Iterator<Item = T> + Send + Sync,
R: Content<E>,
F: Fn(R, T, usize) -> R + Send + Sync
{
fn render (&self, to: &mut E::Output) {
todo!()
}
}
*/

View file

@ -1,36 +0,0 @@
use crate::*;
/// A binary split with fixed proportion
pub struct Split<E, A, B>(pub bool, pub Direction, pub E::Unit, A, B, PhantomData<E>)
where E: Engine, A: Content<E>, B: Content<E>;
impl<E: Engine, A: Content<E>, B: Content<E>> Split<E, A, B> {
#[inline] pub fn new (flip: bool, direction: Direction, proportion: E::Unit, a: A, b: B) -> Self {
Self(flip, direction, proportion, a, b, Default::default())
}
#[inline] pub fn n (flip: bool, proportion: E::Unit, a: A, b: B) -> Self {
Self::new(flip, North, proportion, a, b)
}
#[inline] pub fn s (flip: bool, proportion: E::Unit, a: A, b: B) -> Self {
Self::new(flip, South, proportion, a, b)
}
#[inline] pub fn e (flip: bool, proportion: E::Unit, a: A, b: B) -> Self {
Self::new(flip, West, proportion, a, b)
}
#[inline] pub fn w (flip: bool, proportion: E::Unit, a: A, b: B) -> Self {
Self::new(flip, East, proportion, a, b)
}
}
impl<E: Engine, A: Content<E>, B: Content<E>> Content<E> for Split<E, A, B> {
fn render (&self, to: &mut E::Output) {
let (a, b) = self.1.split_fixed(to.area(), self.2);
if self.0 {
to.place(a.into(), &self.4);
to.place(b.into(), &self.3);
} else {
to.place(a.into(), &self.3);
to.place(b.into(), &self.4);
}
}
}

View file

@ -1,117 +0,0 @@
use crate::*;
pub struct Stack<E: Engine, F: Collector<E>>(pub F, pub Direction, PhantomData<E>);
impl<E: Engine, F: Collector<E>> Stack<E, F> {
#[inline] pub fn new (direction: Direction, build: F) -> Self {
Self(build, direction, Default::default())
}
#[inline] pub fn right (build: F) -> Self {
Self::new(East, build)
}
#[inline] pub fn down (build: F) -> Self {
Self::new(South, build)
}
#[inline] pub fn up (build: F) -> Self {
Self::new(North, build)
}
}
//#[macro_export] macro_rules! col {
//([$($expr:expr),* $(,)?]) => {
//Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) })
//};
//(![$($expr:expr),* $(,)?]) => {
//Stack::down(|add|{ $(add(&$expr)?;)* Ok(()) })
//};
//($expr:expr) => {
//Stack::down($expr)
//};
//($pat:pat in $collection:expr => $item:expr) => {
//Stack::down(move|add|{ for $pat in $collection { add(&$item)?; } Ok(()) })
//};
//}
//#[macro_export] macro_rules! col_up {
//([$($expr:expr),* $(,)?]) => {
//Stack::up(move|add|{ $(add(&$expr)?;)* Ok(()) })
//};
//(![$($expr:expr),* $(,)?]) => {
//Stack::up(|add|{ $(add(&$expr)?;)* Ok(()) })
//};
//($expr:expr) => {
//Stack::up(expr)
//};
//($pat:pat in $collection:expr => $item:expr) => {
//Stack::up(move |add|{ for $pat in $collection { add(&$item)?; } Ok(()) })
//};
//}
//#[macro_export] macro_rules! row {
//([$($expr:expr),* $(,)?]) => {
//Stack::right(move|add|{ $(add(&$expr)?;)* Ok(()) })
//};
//(![$($expr:expr),* $(,)?]) => {
//Stack::right(|add|{ $(add(&$expr)?;)* Ok(()) })
//};
//($expr:expr) => {
//Stack::right($expr)
//};
//($pat:pat in $collection:expr => $item:expr) => {
//Stack::right(move|add|{ for $pat in $collection { add(&$item)?; } Ok(()) })
//};
//}
//impl<E: Engine, F: Collector<E>> Content<E> for Stack<E, F> {
//fn render (&self, to: &mut E::Output) {
//let area = to.area();
//let mut w = 0.into();
//let mut h = 0.into();
//match self.1 {
//South => {
//(self.0)(&mut |item| {
//if h < area.h() {
//let item = Max::y(area.h() - h, Push::y(h, item));
//let show = item.min_size(area.wh().into())?.map(|s|s.wh());
//if let Some([width, height]) = show {
//item.render(to)?;
//h = h + height;
//if width > w { w = width }
//};
//}
//Ok(())
//})?;
//},
//East => {
//(self.0)(&mut |item| {
//if w < area.w() {
//let item = Max::x(area.w() - w, Push::x(w, item));
//let show = item.min_size(area.wh().into())?.map(|s|s.wh());
//if let Some([width, height]) = show {
//item.render(to)?;
//w = width + w;
//if height > h { h = height }
//};
//}
//Ok(())
//})?;
//},
//North => {
//(self.0)(&mut |item| {
//if h < area.h() {
//let show = item.min_size([area.w(), area.h().minus(h)].into())?.map(|s|s.wh());
//if let Some([width, height]) = show {
//Shrink::y(height, Push::y(area.h() - height, item))
//.render(to)?;
//h = h + height;
//if width > w { w = width }
//};
//}
//Ok(())
//})?;
//},
//_ => todo!()
//};
//Ok(())
//}
//}

View file

@ -50,146 +50,3 @@ impl Direction {
}
}
}
/// Renders multiple things on top of each other,
#[macro_export] macro_rules! lay {
($($expr:expr),* $(,)?) => {{
let bsp = ();
$(let bsp = Bsp::b(bsp, $expr);)*;
bsp
}}
}
/// Stack southward.
#[macro_export] macro_rules! col {
($($expr:expr),* $(,)?) => {{
let bsp = ();
$(let bsp = Bsp::s(bsp, $expr);)*;
bsp
}};
}
/// Stack northward.
#[macro_export] macro_rules! col_up {
($($expr:expr),* $(,)?) => {{
let bsp = ();
$(let bsp = Bsp::n(bsp, $expr);)*;
bsp
}}
}
/// Stack eastward.
#[macro_export] macro_rules! row {
($($expr:expr),* $(,)?) => {{
let bsp = ();
$(let bsp = Bsp::e(bsp, $expr);)*;
bsp
}};
}
pub enum Bsp<E: Engine, X: Content<E>, Y: Content<E>> {
/// X is north of Y
North(Option<f64>, Option<X>, Option<Y>),
/// X is south of Y
South(Option<f64>, Option<X>, Option<Y>),
/// X is east of Y
East(Option<f64>, Option<X>, Option<Y>),
/// X is west of Y
West(Option<f64>, Option<X>, Option<Y>),
/// X is above Y
Above(Option<X>, Option<Y>),
/// X is below Y
Below(Option<X>, Option<Y>),
/// Should be avoided.
Null(PhantomData<E>),
}
impl<E: Engine, X: Content<E>, Y: Content<E>> Bsp<E, X, Y> {
pub fn n (x: X, y: Y) -> Self { Self::North(None, Some(x), Some(y)) }
pub fn s (x: X, y: Y) -> Self { Self::South(None, Some(x), Some(y)) }
pub fn e (x: X, y: Y) -> Self { Self::East(None, Some(x), Some(y)) }
pub fn w (x: X, y: Y) -> Self { Self::West(None, Some(x), Some(y)) }
pub fn a (x: X, y: Y) -> Self { Self::Above(Some(x), Some(y)) }
pub fn b (x: X, y: Y) -> Self { Self::Below(Some(x), Some(y)) }
}
impl<E: Engine, X: Content<E>, Y: Content<E>> Default for Bsp<E, X, Y> {
fn default () -> Self {
Self::Null(Default::default())
}
}
impl<E: Engine, X: Content<E>, Y: Content<E>> Content<E> for Bsp<E, X, Y> {
fn area (&self, outer: E::Area) -> E::Area {
match self {
Self::Null(_) => [0.into(), 0.into(), 0.into(), 0.into()].into(),
Self::North(_, a, b) => {
let a = a.area(outer);
let b = b.area(North.split_fixed(outer, a.y() + a.h()).1.into());
[a.x().min(b.x()), a.y().min(b.y()), a.w().max(b.w()), a.h() + b.h()].into()
}
Self::South(_, a, b) => {
let a = a.area(outer);
let b = b.area(South.split_fixed(outer, a.y() + a.h()).1.into());
[a.x().min(b.x()), a.y().min(b.y()), a.w().max(b.w()), a.h() + b.h()].into()
},
Self::East(_, a, b) => {
let a = a.area(outer);
let b = b.area(East.split_fixed(outer, a.x() + a.w()).1.into());
[a.x().min(b.x()), a.y().min(b.y()), a.w() + b.w(), a.h().max(b.h())].into()
},
Self::West(_, a, b) => {
let a = a.area(outer);
let b = b.area(West.split_fixed(outer, a.x() + a.w()).1.into());
[a.x().min(b.x()), a.y().min(b.y()), a.w() + b.w(), a.h().max(b.h())].into()
},
Self::Above(a, b) | Self::Below(a, b) => {
let a = a.area(outer);
let b = b.area(outer);
[a.x().min(b.x()), a.y().min(b.y()), a.w().max(b.w()), a.h().max(b.h())].into()
}
}
}
fn render (&self, to: &mut E::Output) {
let area = to.area().clone();
match self {
Self::North(_, a, b) => {
let area_a = a.area(area);
let area_b = b.area(North.split_fixed(area, area_a.y() + area_a.h()).1.into());
to.place(area_a, a);
to.place(area_b, b);
},
Self::South(_, a, b) => {
let area_a = a.area(area).clone();
let area_b = b.area(South.split_fixed(area, area_a.y() + area_a.h()).1.into()).clone();
to.place(area_a, a);
to.place(area_b, b);
},
Self::East(_, a, b) => {
let area_a = a.area(area);
let area_b = b.area(East.split_fixed(area, area_a.x() + area_a.w()).1.into());
to.place(area_a, a);
to.place(area_b, b);
},
Self::West(_, a, b) => {
let area_a = a.area(area);
let area_b = b.area(West.split_fixed(area, area_a.x() + area_a.w()).1.into());
to.place(area_a, a);
to.place(area_b, b);
},
Self::Above(a, b) => {
let area_a = a.area(area);
let area_b = b.area(area);
to.place(area_b, b);
to.place(area_a, a);
},
Self::Below(a, b) => {
let area_a = a.area(area);
let area_b = b.area(area);
to.place(area_a, a);
to.place(area_b, b);
},
Self::Null(_) => {}
}
}
}

View file

@ -1,7 +1,12 @@
mod collection; pub use self::collection::*;
mod direction; pub use self::direction::*;
mod measure; pub use self::measure::*;
mod transform; pub use self::transform::*;
//mod collection; pub use self::collection::*;
mod align; pub use self::align::*;
mod bsp; pub use self::bsp::*;
mod direction; pub use self::direction::*;
mod measure; pub use self::measure::*;
mod ops; pub use self::ops::*;
mod transform_xy; pub use self::transform_xy::*;
mod transform_xy_unit; pub use self::transform_xy_unit::*;
pub use ::tek_engine;
pub(crate) use ::tek_engine::*;

117
layout/src/ops.rs Normal file
View file

@ -0,0 +1,117 @@
use crate::*;
use std::sync::RwLock;
impl<E: Engine> Layout<E> for E {}
pub trait Layout<E: Engine> {
/// Content `item` when `cond` is true.
fn when <A: Content<E>> (cond: bool, item: A) -> When<E, A> {
When(cond, item, Default::default())
}
/// Content `item` if `cond` is true, otherwise render `other`.
fn either <A: Content<E>, B: Content<E>> (cond: bool, a: A, b: B)
-> Either<E, A, B>
{
Either(cond, a, b, Default::default())
}
/// Maps an [Option<T>] through a callback `F`
fn opt <A, F: Fn(A)->R, R: Content<E>> (option: Option<A>, cb: F)
-> Opt<E, A, F, R>
{
Opt(option, cb, Default::default())
}
fn map <T, I, R, F>(iterator: I, callback: F) -> Map<E, T, I, R, F> where
E: Engine,
I: Iterator<Item = T> + Send + Sync,
R: Content<E>,
F: Fn(T, usize)->R + Send + Sync
{
Map(Default::default(), RwLock::new(iterator), callback)
}
//pub fn reduce <E, T, I, R, F>(iterator: I, callback: F) -> Reduce<E, T, I, R, F> where
//E: Engine,
//I: Iterator<Item = T> + Send + Sync,
//R: Content<E>,
//F: Fn(R, T, usize) -> R + Send + Sync
//{
//Reduce(Default::default(), iterator, callback)
//}
}
pub struct Opt<E: Engine, A, F: Fn(A)->R, R: Content<E>>(Option<A>, F, PhantomData<E>);
/// Contents `self.1` when `self.0` is true.
pub struct When<E: Engine, A>(bool, A, PhantomData<E>);
impl<E: Engine, A: Content<E>> Content<E> for When<E, A> {
fn area (&self, to: E::Area) -> E::Area {
let Self(cond, item, ..) = self;
let mut area = E::Area::zero();
if *cond {
let item_area = item.area(to);
area[0] = item_area.x();
area[1] = item_area.y();
area[2] = item_area.w();
area[3] = item_area.h();
}
area.into()
}
fn render (&self, to: &mut E::Output) {
let Self(cond, item, ..) = self;
if *cond { item.render(to) }
}
}
/// Contents `self.1` when `self.0` is true, otherwise renders `self.2`
pub struct Either<E: Engine, A, B>(bool, A, B, PhantomData<E>);
impl<E: Engine, A: Content<E>, B: Content<E>> Content<E> for Either<E, A, B> {
fn area (&self, to: E::Area) -> E::Area {
let Self(cond, a, b, ..) = self;
if *cond { a.area(to) } else { b.area(to) }
}
fn render (&self, to: &mut E::Output) {
let Self(cond, a, b, ..) = self;
if *cond { a.render(to) } else { b.render(to) }
}
}
pub struct Map<E, T, I, R, F>(PhantomData<E>, RwLock<I>, F) where
E: Engine,
I: Iterator<Item = T> + Send + Sync,
R: Content<E>,
F: Fn(T, usize)->R + Send + Sync;
impl<E, T, I, R, F> Content<E> for Map<E, T, I, R, F> where
E: Engine,
I: Iterator<Item = T> + Send + Sync,
R: Content<E>,
F: Fn(T, usize)->R + Send + Sync
{
fn render (&self, to: &mut E::Output) {
let mut index = 0;
for item in &mut*self.1.write().unwrap() {
(self.2)(item, index).render(to);
index += 1;
}
}
}
/*
pub struct Reduce<E, T, I, R, F>(PhantomData<(E, R)>, I, F) where
E: Engine,
I: Iterator<Item = T> + Send + Sync,
R: Content<E>,
F: Fn(R, T, usize) -> R + Send + Sync;
impl<E, T, I, R, F> Content<E> for Reduce<E, T, I, R, F> where
E: Engine,
I: Iterator<Item = T> + Send + Sync,
R: Content<E>,
F: Fn(R, T, usize) -> R + Send + Sync
{
fn render (&self, to: &mut E::Output) {
todo!()
}
}
*/

View file

@ -1,227 +0,0 @@
use crate::*;
/// Defines an enum that wraps the same renderable item
/// but discriminates on the variant which wraps it.
///
/// It also has a special `_Unused` variant which wraps
/// the engine type using `PhantomData` to permit the
/// double generic.
macro_rules! content_enum {
($Enum:ident: $($Variant:ident),+ $(,)?) => {
pub enum $Enum<E: Engine, T: Content<E>> {
_Unused(PhantomData<E>), $($Variant(T)),+
}
}
}
/// Defines an enum that transforms its content
/// along either the X axis, the Y axis, or both.
macro_rules! transform_xy {
($self:ident : $Enum:ident |$to:ident|$area:expr) => {
content_enum!($Enum: X, Y, XY);
impl<E: Engine, T: Content<E>> $Enum<E, T> {
pub fn x (item: T) -> Self { Self::X(item) }
pub fn y (item: T) -> Self { Self::Y(item) }
pub fn xy (item: T) -> Self { Self::XY(item) }
}
impl<E: Engine, T: Content<E>> Content<E> for $Enum<E, T> {
fn content (&self) -> impl Content<E> {
match self {
Self::X(item) => item,
Self::Y(item) => item,
Self::XY(item) => item,
_ => unreachable!()
}
}
fn area (&$self, $to: <E as Engine>::Area) -> <E as Engine>::Area {
$area
}
}
}
}
transform_xy!(self: Fill |to|{
let [x0, y0, wmax, hmax] = to.xywh();
let [x, y, w, h] = Content::area(&self.content(), to).xywh();
return match self {
Self::X(_) => [x0, y, wmax, h],
Self::Y(_) => [x, y0, w, hmax],
Self::XY(_) => [x0, y0, wmax, hmax],
_ => unreachable!()
}.into()
});
/// Defines an enum that transforms its content parametrically
/// along either the X axis, the Y axis, or both
macro_rules! transform_xy_unit {
(|$self:ident : $Enum:ident, $to:ident|$area:expr) => {
pub enum $Enum<E: Engine, T: Content<E>> {
X(E::Unit, T), Y(E::Unit, T), XY(E::Unit, E::Unit, T),
}
impl<E: Engine, T: Content<E>> $Enum<E, T> {
pub fn x (x: E::Unit, item: T) -> Self { Self::X(x, item) }
pub fn y (y: E::Unit, item: T) -> Self { Self::Y(y, item) }
pub fn xy (x: E::Unit, y: E::Unit, item: T) -> Self { Self::XY(x, y, item) }
pub fn dx (&self) -> E::Unit {
match self {
Self::X(x, _) => *x,
Self::Y(_, _) => E::Unit::zero(),
Self::XY(x, _, _) => *x,
}
}
pub fn dy (&self) -> E::Unit {
match self {
Self::X(_, _) => E::Unit::zero(),
Self::Y(y, _) => *y,
Self::XY(_, y, _) => *y,
}
}
}
impl<E: Engine, T: Content<E>> Content<E> for $Enum<E, T> {
fn content (&self) -> impl Content<E> {
Some(match self {
Self::X(_, content) => content,
Self::Y(_, content) => content,
Self::XY(_, _, content) => content,
})
}
fn area (&$self, $to: E::Area) -> E::Area {
$area.into()
}
}
}
}
transform_xy_unit!(|self: Fixed, area|{
let area = self.content().area(area);
match self {
Self::X(fw, _) => [area.x(), area.y(), *fw, area.h()],
Self::Y(fh, _) => [area.x(), area.y(), area.w(), *fh],
Self::XY(fw, fh, _) => [area.x(), area.y(), *fw, *fh], // tagn
}
});
transform_xy_unit!(|self: Shrink, area|{
let area = self.content().area(area);
[area.x(), area.y(), area.w().minus(self.dx()), area.h().minus(self.dy())]
});
transform_xy_unit!(|self: Expand, area|{
let area = self.content().area(area);
[area.x(), area.y(), area.w() + self.dx(), area.h() + self.dy()]
});
transform_xy_unit!(|self: Min, area|{
let area = self.content().area(area);
match self {
Self::X(mw, _) => [area.x(), area.y(), area.w().max(*mw), area.h()],
Self::Y(mh, _) => [area.x(), area.y(), area.w(), area.h().max(*mh)],
Self::XY(mw, mh, _) => [area.x(), area.y(), area.w().max(*mw), area.h().max(*mh)]
}});
transform_xy_unit!(|self: Max, area|{
let area = self.content().area(area);
match self {
Self::X(mw, _) => [area.x(), area.y(), area.w().min(*mw), area.h()],
Self::Y(mh, _) => [area.x(), area.y(), area.w(), area.h().min(*mh)],
Self::XY(mw, mh, _) => [area.x(), area.y(), area.w().min(*mw), area.h().min(*mh)],
}});
transform_xy_unit!(|self: Push, area|{
let area = self.content().area(area);
[area.x() + self.dx(), area.y() + self.dy(), area.w(), area.h()]
});
transform_xy_unit!(|self: Pull, area|{
let area = self.content().area(area);
[area.x().minus(self.dx()), area.y().minus(self.dy()), area.w(), area.h()]
});
transform_xy_unit!(|self: Margin, area|{
let area = self.content().area(area);
let dx = self.dx();
let dy = self.dy();
[area.x().minus(dx), area.y().minus(dy), area.w() + dy + dy, area.h() + dy + dy]
});
transform_xy_unit!(|self: Padding, area|{
let area = self.content().area(area);
let dx = self.dx();
let dy = self.dy();
[area.x() + dx, area.y() + dy, area.w().minus(dy + dy), area.h().minus(dy + dy), ]
});
#[derive(Default, Debug, Copy, Clone)]
pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W }
pub struct Align<E: Engine, T: Content<E>>(Alignment, T, PhantomData<E>);
impl<E: Engine, T: Content<E>> Align<E, T> {
pub fn c (w: T) -> Self { Self(Alignment::Center, w, Default::default()) }
pub fn x (w: T) -> Self { Self(Alignment::X, w, Default::default()) }
pub fn y (w: T) -> Self { Self(Alignment::Y, w, Default::default()) }
pub fn n (w: T) -> Self { Self(Alignment::N, w, Default::default()) }
pub fn s (w: T) -> Self { Self(Alignment::S, w, Default::default()) }
pub fn e (w: T) -> Self { Self(Alignment::E, w, Default::default()) }
pub fn w (w: T) -> Self { Self(Alignment::W, w, Default::default()) }
pub fn nw (w: T) -> Self { Self(Alignment::NW, w, Default::default()) }
pub fn sw (w: T) -> Self { Self(Alignment::SW, w, Default::default()) }
pub fn ne (w: T) -> Self { Self(Alignment::NE, w, Default::default()) }
pub fn se (w: T) -> Self { Self(Alignment::SE, w, Default::default()) }
}
impl<E: Engine, T: Content<E>> Content<E> for Align<E, T> {
fn area (&self, outer: E::Area) -> E::Area {
align_areas(self.0, outer.xywh(), Content::area(&self.content(), outer).xywh()).into()
}
fn render (&self, render: &mut E::Output) {
render.place(self.area(render.area()), &self.content())
}
}
pub fn align_areas<N: Coordinate>(alignment: Alignment, on: [N;4], it: [N;4]) -> [N;4] {
let [cfx, cfy] = on.center();
let [cmx, cmy] = it.center();
let center = |cf, cm, m: N|if cf >= cm { m + (cf - cm) } else { m.minus(cm - cf) };
let center_x = center(cfx, cmx, it.x());
let center_y = center(cfy, cmy, it.y());
let east_x = on.x() + on.w().minus(it.w());
let south_y = on.y() + on.h().minus(it.h());
match alignment {
Alignment::Center => [center_x, center_y, it.w(), it.h()],
Alignment::X => [center_x, it.y(), it.w(), it.h()],
Alignment::Y => [it.x(), center_y, it.w(), it.h()],
Alignment::NW => [on.x(), on.y(), it.w(), it.h()],
Alignment::N => [center_x, on.y(), it.w(), it.h()],
Alignment::NE => [east_x, on.y(), it.w(), it.h()],
Alignment::E => [east_x, center_y, it.w(), it.h()],
Alignment::SE => [east_x, south_y, it.w(), it.h()],
Alignment::S => [center_x, south_y, it.w(), it.h()],
Alignment::SW => [on.x(), south_y, it.w(), it.h()],
Alignment::W => [on.x(), center_y, it.w(), it.h()],
}
}
//fn align<E: Engine, T: Content<E>, N: Coordinate, R: Area<N> + From<[N;4]>> (align: &Align<E, T>, outer: R, content: R) -> Option<R> {
//if outer.w() < content.w() || outer.h() < content.h() {
//None
//} else {
//let [ox, oy, ow, oh] = outer.xywh();
//let [ix, iy, iw, ih] = content.xywh();
//Some(match align {
//Align::Center(_) => [ox + (ow - iw) / 2.into(), oy + (oh - ih) / 2.into(), iw, ih,].into(),
//Align::X(_) => [ox + (ow - iw) / 2.into(), iy, iw, ih,].into(),
//Align::Y(_) => [ix, oy + (oh - ih) / 2.into(), iw, ih,].into(),
//Align::NW(_) => [ox, oy, iw, ih,].into(),
//Align::N(_) => [ox + (ow - iw) / 2.into(), oy, iw, ih,].into(),
//Align::NE(_) => [ox + ow - iw, oy, iw, ih,].into(),
//Align::W(_) => [ox, oy + (oh - ih) / 2.into(), iw, ih,].into(),
//Align::E(_) => [ox + ow - iw, oy + (oh - ih) / 2.into(), iw, ih,].into(),
//Align::SW(_) => [ox, oy + oh - ih, iw, ih,].into(),
//Align::S(_) => [ox + (ow - iw) / 2.into(), oy + oh - ih, iw, ih,].into(),
//Align::SE(_) => [ox + ow - iw, oy + oh - ih, iw, ih,].into(),
//_ => unreachable!()
//})
//}
//}

View file

@ -0,0 +1,41 @@
use crate::*;
/// Defines an enum that transforms its content
/// along either the X axis, the Y axis, or both.
///
/// The `_Unused` variant wraps the `Engine` type
/// using `PhantomData` to permit the double generic.
macro_rules! transform_xy {
($self:ident : $Enum:ident |$to:ident|$area:expr) => {
pub enum $Enum<E: Engine, T: Content<E>> { _Unused(PhantomData<E>), X(T), Y(T), XY(T) }
impl<E: Engine, T: Content<E>> $Enum<E, T> {
pub fn x (item: T) -> Self { Self::X(item) }
pub fn y (item: T) -> Self { Self::Y(item) }
pub fn xy (item: T) -> Self { Self::XY(item) }
}
impl<E: Engine, T: Content<E>> Content<E> for $Enum<E, T> {
fn content (&self) -> impl Content<E> {
match self {
Self::X(item) => item,
Self::Y(item) => item,
Self::XY(item) => item,
_ => unreachable!()
}
}
fn area (&$self, $to: <E as Engine>::Area) -> <E as Engine>::Area {
$area
}
}
}
}
transform_xy!(self: Fill |to|{
let [x0, y0, wmax, hmax] = to.xywh();
let [x, y, w, h] = Content::area(&self.content(), to).xywh();
return match self {
Self::X(_) => [x0, y, wmax, h],
Self::Y(_) => [x, y0, w, hmax],
Self::XY(_) => [x0, y0, wmax, hmax],
_ => unreachable!()
}.into()
});

View file

@ -0,0 +1,101 @@
use crate::*;
/// Defines an enum that parametrically transforms its content
/// along either the X axis, the Y axis, or both.
macro_rules! transform_xy_unit {
(|$self:ident : $Enum:ident, $to:ident|$area:expr) => {
pub enum $Enum<E: Engine, T: Content<E>> {
X(E::Unit, T), Y(E::Unit, T), XY(E::Unit, E::Unit, T),
}
impl<E: Engine, T: Content<E>> $Enum<E, T> {
pub fn x (x: E::Unit, item: T) -> Self { Self::X(x, item) }
pub fn y (y: E::Unit, item: T) -> Self { Self::Y(y, item) }
pub fn xy (x: E::Unit, y: E::Unit, item: T) -> Self { Self::XY(x, y, item) }
pub fn dx (&self) -> E::Unit {
match self {
Self::X(x, _) => *x,
Self::Y(_, _) => E::Unit::zero(),
Self::XY(x, _, _) => *x,
}
}
pub fn dy (&self) -> E::Unit {
match self {
Self::X(_, _) => E::Unit::zero(),
Self::Y(y, _) => *y,
Self::XY(_, y, _) => *y,
}
}
}
impl<E: Engine, T: Content<E>> Content<E> for $Enum<E, T> {
fn content (&self) -> impl Content<E> {
Some(match self {
Self::X(_, content) => content,
Self::Y(_, content) => content,
Self::XY(_, _, content) => content,
})
}
fn area (&$self, $to: E::Area) -> E::Area {
$area.into()
}
}
}
}
transform_xy_unit!(|self: Fixed, area|{
let area = self.content().area(area);
match self {
Self::X(fw, _) => [area.x(), area.y(), *fw, area.h()],
Self::Y(fh, _) => [area.x(), area.y(), area.w(), *fh],
Self::XY(fw, fh, _) => [area.x(), area.y(), *fw, *fh], // tagn
}
});
transform_xy_unit!(|self: Shrink, area|{
let area = self.content().area(area);
[area.x(), area.y(), area.w().minus(self.dx()), area.h().minus(self.dy())]
});
transform_xy_unit!(|self: Expand, area|{
let area = self.content().area(area);
[area.x(), area.y(), area.w() + self.dx(), area.h() + self.dy()]
});
transform_xy_unit!(|self: Min, area|{
let area = self.content().area(area);
match self {
Self::X(mw, _) => [area.x(), area.y(), area.w().max(*mw), area.h()],
Self::Y(mh, _) => [area.x(), area.y(), area.w(), area.h().max(*mh)],
Self::XY(mw, mh, _) => [area.x(), area.y(), area.w().max(*mw), area.h().max(*mh)]
}});
transform_xy_unit!(|self: Max, area|{
let area = self.content().area(area);
match self {
Self::X(mw, _) => [area.x(), area.y(), area.w().min(*mw), area.h()],
Self::Y(mh, _) => [area.x(), area.y(), area.w(), area.h().min(*mh)],
Self::XY(mw, mh, _) => [area.x(), area.y(), area.w().min(*mw), area.h().min(*mh)],
}});
transform_xy_unit!(|self: Push, area|{
let area = self.content().area(area);
[area.x() + self.dx(), area.y() + self.dy(), area.w(), area.h()]
});
transform_xy_unit!(|self: Pull, area|{
let area = self.content().area(area);
[area.x().minus(self.dx()), area.y().minus(self.dy()), area.w(), area.h()]
});
transform_xy_unit!(|self: Margin, area|{
let area = self.content().area(area);
let dx = self.dx();
let dy = self.dy();
[area.x().minus(dx), area.y().minus(dy), area.w() + dy + dy, area.h() + dy + dy]
});
transform_xy_unit!(|self: Padding, area|{
let area = self.content().area(area);
let dx = self.dx();
let dy = self.dy();
[area.x() + dx, area.y() + dy, area.w().minus(dy + dy), area.h().minus(dy + dy), ]
});