tek/layout/src/transform.rs

184 lines
7 KiB
Rust

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) -> Option<impl Content<E>> {
Some(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] = self.content().unwrap().area(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) -> Option<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, to|match self {
Self::X(fw, _) => [to.x(), to.y(), *fw, to.h()],
Self::Y(fh, _) => [to.x(), to.y(), to.w(), *fh],
Self::XY(fw, fh, _) => [to.x(), to.y(), *fw, *fh], // tagn
});
transform_xy_unit!(|self: Shrink, to|
[to.x(), to.y(), to.w().minus(self.dx()), to.h().minus(self.dy())]);
transform_xy_unit!(|self: Expand, to|
[to.x(), to.y(), to.w() + self.dx(), to.h() + self.dy()]);
transform_xy_unit!(|self: Min, to|match self {
Self::X(mw, _) => [to.x(), to.y(), to.w().max(*mw), to.h()],
Self::Y(mh, _) => [to.x(), to.y(), to.w(), to.h().max(*mh)],
Self::XY(mw, mh, _) => [to.x(), to.y(), to.w().max(*mw), to.h().max(*mh)]
});
transform_xy_unit!(|self: Max, to|match self {
Self::X(mw, _) => [to.x(), to.y(), to.w().min(*mw), to.h()],
Self::Y(mh, _) => [to.x(), to.y(), to.w(), to.h().min(*mh)],
Self::XY(mw, mh, _) => [to.x(), to.y(), to.w().min(*mw), to.h().min(*mh)],
});
transform_xy_unit!(|self: Push, to|
[to.x() + self.dx(), to.y() + self.dy(), to.w(), to.h()]);
transform_xy_unit!(|self: Pull, to|
[to.x().minus(self.dx()), to.y().minus(self.dy()), to.w(), to.h()]);
transform_xy_unit!(|self: Margin, to|{
let dx = self.dx();
let dy = self.dy();
[to.x().minus(dx), to.y().minus(dy), to.w() + dy + dy, to.h() + dy + dy]
});
transform_xy_unit!(|self: Padding, to|{
let dx = self.dx();
let dy = self.dy();
[to.x() + dx, to.y() + dy, to.w().minus(dy + dy), to.h().minus(dy + dy), ]
});
content_enum!(Align: Center, X, Y, NW, N, NE, E, SE, S, SW, W);
impl<E: Engine, T: Content<E>> Align<E, T> {
pub fn c (w: T) -> Self { Self::Center(w) }
pub fn x (w: T) -> Self { Self::X(w) }
pub fn y (w: T) -> Self { Self::Y(w) }
pub fn n (w: T) -> Self { Self::N(w) }
pub fn s (w: T) -> Self { Self::S(w) }
pub fn e (w: T) -> Self { Self::E(w) }
pub fn w (w: T) -> Self { Self::W(w) }
pub fn nw (w: T) -> Self { Self::NW(w) }
pub fn sw (w: T) -> Self { Self::SW(w) }
pub fn ne (w: T) -> Self { Self::NE(w) }
pub fn se (w: T) -> Self { Self::SE(w) }
}
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!()
})
}
}
impl<E: Engine, T: Content<E>> Content<E> for Align<E, T> {
fn render (&self, to: &mut E::Output) {
let outer_area = to.area();
let content = self.content();
let inner_area = content.area(outer_area);
if let Some(aligned) = align(&self, outer_area.into(), inner_area.into()) {
to.place(aligned, &content)
}
}
}