use super::ir::{Bbox, PlacedNode};
use super::primitives;
use crate::error::Error;
use crate::resolve::{AttrMap, ResolvedValue, VarTable};
use crate::span::Span;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Axis {
Row,
Column,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Align {
Start,
Center,
End,
}
pub fn lay_out_flex(
axis: Axis,
children: &mut [PlacedNode],
attrs: &AttrMap,
vars: &VarTable,
span: Span,
) -> Result<Bbox, Error> {
if children.is_empty() {
return Ok(Bbox::empty());
}
let (gap_y, gap_x) = primitives::gap(attrs, vars, span)?;
let h_align = parse_align(attrs, "h").unwrap_or(Align::Center);
let v_align = parse_align(attrs, "v").unwrap_or(Align::Center);
let cross_size = match axis {
Axis::Row => children.iter().map(|c| c.bbox.h()).fold(0.0_f64, f64::max),
Axis::Column => children.iter().map(|c| c.bbox.w()).fold(0.0_f64, f64::max),
};
let total_main: f64 = children
.iter()
.map(|c| match axis {
Axis::Row => c.bbox.w(),
Axis::Column => c.bbox.h(),
})
.sum::<f64>()
+ gap_main(axis, gap_x, gap_y) * (children.len() as f64 - 1.0);
let mut cursor = -total_main / 2.0;
for child in children.iter_mut() {
let (main_size, cross_size_child) = match axis {
Axis::Row => (child.bbox.w(), child.bbox.h()),
Axis::Column => (child.bbox.h(), child.bbox.w()),
};
let main_origin = cursor + main_size / 2.0 - child_offset(child.bbox, axis);
let cross_origin = cross_align(
cross_size,
cross_size_child,
cross_axis_align(axis, h_align, v_align),
) - child_offset(child.bbox, cross_of(axis));
match axis {
Axis::Row => {
child.cx = main_origin;
child.cy = cross_origin;
}
Axis::Column => {
child.cx = cross_origin;
child.cy = main_origin;
}
}
cursor += main_size + gap_main(axis, gap_x, gap_y);
}
let mut union = children[0].bbox.shifted(children[0].cx, children[0].cy);
for c in children.iter().skip(1) {
union = union.union(c.bbox.shifted(c.cx, c.cy));
}
Ok(union)
}
fn child_offset(bbox: Bbox, axis: Axis) -> f64 {
match axis {
Axis::Row => (bbox.min_x + bbox.max_x) / 2.0,
Axis::Column => (bbox.min_y + bbox.max_y) / 2.0,
}
}
fn gap_main(axis: Axis, gap_x: f64, gap_y: f64) -> f64 {
match axis {
Axis::Row => gap_x,
Axis::Column => gap_y,
}
}
fn cross_of(axis: Axis) -> Axis {
match axis {
Axis::Row => Axis::Column,
Axis::Column => Axis::Row,
}
}
fn cross_axis_align(main_axis: Axis, h_align: Align, v_align: Align) -> Align {
match main_axis {
Axis::Row => v_align,
Axis::Column => h_align,
}
}
fn cross_align(track_size: f64, item_size: f64, align: Align) -> f64 {
match align {
Align::Start => -track_size / 2.0 + item_size / 2.0,
Align::Center => 0.0,
Align::End => track_size / 2.0 - item_size / 2.0,
}
}
fn parse_align(attrs: &AttrMap, name: &str) -> Option<Align> {
match attrs.get(name)? {
ResolvedValue::Ident(s) => match s.as_str() {
"start" => Some(Align::Start),
"center" => Some(Align::Center),
"end" => Some(Align::End),
_ => None,
},
_ => None,
}
}