use super::ir::{Bbox, PlacedNode};
use super::primitives;
use crate::error::Error;
use crate::resolve::{AttrMap, ResolvedValue};
use crate::span::Span;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Axis {
Row,
Column,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Dim {
W,
H,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Cross {
Start,
Center,
End,
Stretch,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Main {
Start,
Center,
End,
Stretch,
Evenly,
}
pub fn lay_out_flex(
axis: Axis,
children: &mut [PlacedNode],
attrs: &AttrMap,
span: Span,
avail: (Option<f64>, Option<f64>),
) -> Result<Bbox, Error> {
if children.is_empty() {
return Ok(Bbox::empty());
}
let (gap_y, gap_x) = primitives::gap(attrs, span)?;
let gap = match axis {
Axis::Row => gap_x,
Axis::Column => gap_y,
};
let main = parse_main(attrs);
let cross = parse_cross(attrs);
let (main_dim, cross_dim) = match axis {
Axis::Row => (Dim::W, Dim::H),
Axis::Column => (Dim::H, Dim::W),
};
let (avail_main, avail_cross) = match axis {
Axis::Row => (avail.0, avail.1),
Axis::Column => (avail.1, avail.0),
};
let n = children.len() as f64;
let packed = children.iter().map(|c| len(c, main_dim)).sum::<f64>() + gap * (n - 1.0);
let main_extent = avail_main.map_or(packed, |a| a.max(packed));
let max_cross = children
.iter()
.map(|c| len(c, cross_dim))
.fold(0.0, f64::max);
let cross_extent = avail_cross.map_or(max_cross, |a| a.max(max_cross));
if cross == Cross::Stretch {
for c in children.iter_mut() {
if !dim_set(c, cross_dim) {
set_dim(c, cross_dim, cross_extent);
}
}
}
if main == Main::Stretch {
let slack = (main_extent - packed).max(0.0);
let grow: Vec<usize> = (0..children.len())
.filter(|&i| !dim_set(&children[i], main_dim))
.collect();
if slack > 0.0 && !grow.is_empty() {
let add = slack / grow.len() as f64;
for &i in &grow {
let m = len(&children[i], main_dim) + add;
set_dim(&mut children[i], main_dim, m);
}
}
}
let used = children.iter().map(|c| len(c, main_dim)).sum::<f64>() + gap * (n - 1.0);
let remaining = (main_extent - used).max(0.0);
let (leading, inter) = match main {
Main::Start => (0.0, gap),
Main::Center | Main::Stretch => (remaining / 2.0, gap),
Main::End => (remaining, gap),
Main::Evenly => {
let bodies = children.iter().map(|c| len(c, main_dim)).sum::<f64>();
let eg = (main_extent - bodies) / (n + 1.0);
(eg, eg)
}
};
let mut cursor = -main_extent / 2.0 + leading;
for c in children.iter_mut() {
let m = len(c, main_dim);
let main_center = cursor + m / 2.0;
let cross_center = align_cross(cross_extent, len(c, cross_dim), cross);
place(c, axis, main_center, cross_center);
cursor += m + inter;
}
Ok(match axis {
Axis::Row => Bbox::centered(main_extent, cross_extent),
Axis::Column => Bbox::centered(cross_extent, main_extent),
})
}
fn len(c: &PlacedNode, dim: Dim) -> f64 {
match dim {
Dim::W => c.bbox.w(),
Dim::H => c.bbox.h(),
}
}
fn set_dim(c: &mut PlacedNode, dim: Dim, v: f64) {
c.bbox = match dim {
Dim::W => Bbox::centered(v, c.bbox.h()),
Dim::H => Bbox::centered(c.bbox.w(), v),
};
}
fn dim_set(c: &PlacedNode, dim: Dim) -> bool {
match dim {
Dim::W => c.attrs.get("width").is_some(),
Dim::H => c.attrs.get("height").is_some(),
}
}
fn align_cross(extent: f64, child: f64, cross: Cross) -> f64 {
match cross {
Cross::Start => -extent / 2.0 + child / 2.0,
Cross::Center | Cross::Stretch => 0.0,
Cross::End => extent / 2.0 - child / 2.0,
}
}
fn place(c: &mut PlacedNode, axis: Axis, main_center: f64, cross_center: f64) {
let cbx = (c.bbox.min_x + c.bbox.max_x) / 2.0;
let cby = (c.bbox.min_y + c.bbox.max_y) / 2.0;
match axis {
Axis::Row => {
c.cx = main_center - cbx;
c.cy = cross_center - cby;
}
Axis::Column => {
c.cy = main_center - cby;
c.cx = cross_center - cbx;
}
}
}
fn parse_main(attrs: &AttrMap) -> Main {
match ident(attrs.get("justify")) {
Some("start") => Main::Start,
Some("end") => Main::End,
Some("stretch") => Main::Stretch,
Some("evenly") => Main::Evenly,
_ => Main::Center,
}
}
fn parse_cross(attrs: &AttrMap) -> Cross {
match ident(attrs.get("align")) {
Some("start") => Cross::Start,
Some("end") => Cross::End,
Some("stretch") => Cross::Stretch,
_ => Cross::Center,
}
}
fn ident(v: Option<&ResolvedValue>) -> Option<&str> {
match v {
Some(ResolvedValue::Ident(s)) => Some(s.as_str()),
_ => None,
}
}