use super::flex::Axis;
use super::ir::{Bbox, PlacedNode};
use super::{Ctx, child_path, layout_inst, prim, primitives};
use crate::error::Error;
use crate::resolve::{AttrMap, Program, ResolvedInst, ResolvedValue};
use crate::span::Span;
pub(super) fn is_tree(attrs: &AttrMap) -> bool {
matches!(attrs.get("layout"), Some(ResolvedValue::Ident(s)) if s == "tree")
}
#[derive(Clone, Copy, PartialEq)]
enum Dir {
Column,
Row,
Bilateral,
}
fn direction(attrs: &AttrMap) -> Dir {
match attrs.get("direction") {
Some(ResolvedValue::Ident(s)) if s == "row" => Dir::Row,
Some(ResolvedValue::Ident(s)) if s == "bilateral" => Dir::Bilateral,
_ => Dir::Column,
}
}
#[derive(Clone, Copy, PartialEq)]
enum Half {
Right,
Left,
}
fn side_of(n: &PlacedNode) -> Half {
if n.type_chain.iter().any(|t| t == "side-left") {
Half::Left
} else {
Half::Right
}
}
fn is_topic_inst(inst: &ResolvedInst) -> bool {
inst.type_chain.iter().any(|t| t == "topic")
}
fn level_of(n: &PlacedNode) -> usize {
n.type_chain
.iter()
.find_map(|t| t.strip_prefix("level-").and_then(|d| d.parse().ok()))
.unwrap_or(0)
}
pub(super) fn layout_node(
inst: &ResolvedInst,
path: &str,
program: &Program,
) -> Result<PlacedNode, Error> {
let (children, bbox) = arrange(&inst.attrs, &inst.children, path, program, inst.span)?;
Ok(prim::container(inst, bbox, children))
}
pub(super) fn layout_root(program: &Program) -> Result<(Vec<PlacedNode>, Bbox), Error> {
arrange(
&program.scene.attrs,
&program.scene.nodes,
"",
program,
Span::empty(),
)
}
fn arrange(
attrs: &AttrMap,
inst_children: &[ResolvedInst],
path: &str,
program: &Program,
span: Span,
) -> Result<(Vec<PlacedNode>, Bbox), Error> {
let dir = direction(attrs);
let (generation, sibling) = primitives::gap(attrs, span)?;
let mut cards: Vec<PlacedNode> = Vec::new();
let mut content: Vec<PlacedNode> = Vec::new();
for c in inst_children {
if is_topic_inst(c) {
flatten_cards(c, &child_path(path, c), program, &mut cards)?;
} else {
content.push(layout_inst(c, &child_path(path, c), program, Ctx::sheet())?);
}
}
let (nodes, roots) = match dir {
Dir::Bilateral => place_bilateral(&mut cards, generation, sibling),
Dir::Row => place_generations(&mut cards, Axis::Row, generation, sibling),
Dir::Column => place_generations(&mut cards, Axis::Column, generation, sibling),
};
let mut children = nest(cards, &nodes, &roots);
let content_axis = if dir == Dir::Row {
Axis::Row
} else {
Axis::Column
};
let cluster = union_all(&children);
place_content(&mut children, content, cluster, content_axis, sibling)?;
let pad = primitives::padding(attrs, span)?;
let body = union_all(&children);
let bbox = body.expand(pad.top, pad.right, pad.bottom, pad.left);
Ok((children, bbox))
}
fn flatten_cards(
inst: &ResolvedInst,
path: &str,
program: &Program,
cards: &mut Vec<PlacedNode>,
) -> Result<(), Error> {
cards.push(layout_card(inst, path, program)?);
for c in &inst.children {
if is_topic_inst(c) {
flatten_cards(c, &child_path(path, c), program, cards)?;
}
}
Ok(())
}
fn layout_card(inst: &ResolvedInst, path: &str, program: &Program) -> Result<PlacedNode, Error> {
let mut card = inst.clone();
card.children.retain(|c| !is_topic_inst(c));
layout_inst(&card, path, program, Ctx::sheet())
}
fn union_all(nodes: &[PlacedNode]) -> Bbox {
fn go(nodes: &[PlacedNode], ox: f64, oy: f64, acc: &mut Bbox) {
for n in nodes {
let (x, y) = (ox + n.cx, oy + n.cy);
*acc = acc.union(n.bbox.shifted(x, y));
go(&n.children, x, y, acc);
}
}
let mut acc = Bbox::empty();
go(nodes, 0.0, 0.0, &mut acc);
acc
}
struct Node {
card: usize,
children: Vec<usize>,
subtree_cross: f64,
}
fn reconstruct(cards: &[PlacedNode]) -> (Vec<Node>, Vec<usize>, Vec<usize>) {
let mut nodes: Vec<Node> = (0..cards.len())
.map(|card| Node {
card,
children: Vec::new(),
subtree_cross: 0.0,
})
.collect();
let mut roots: Vec<usize> = Vec::new();
let levels: Vec<usize> = cards.iter().map(level_of).collect();
let mut stack: Vec<usize> = Vec::new();
for (i, &lvl) in levels.iter().enumerate() {
stack.truncate(lvl);
match stack.last() {
Some(&parent) if lvl > 0 => nodes[parent].children.push(i),
_ => roots.push(i),
}
stack.push(i);
}
(nodes, roots, levels)
}
fn band_centres(cards: &[PlacedNode], levels: &[usize], axis: Axis, generation: f64) -> Vec<f64> {
let main = |c: &PlacedNode| match axis {
Axis::Column => c.bbox.h(),
Axis::Row => c.bbox.w(),
};
let max_level = *levels.iter().max().unwrap_or(&0);
let mut band_size = vec![0.0_f64; max_level + 1];
for (i, &lvl) in levels.iter().enumerate() {
band_size[lvl] = band_size[lvl].max(main(&cards[i]));
}
let mut centres = vec![0.0_f64; max_level + 1];
for d in 1..=max_level {
centres[d] = centres[d - 1] + band_size[d - 1] / 2.0 + generation + band_size[d] / 2.0;
}
centres
}
fn measure(
i: usize,
nodes: &mut [Node],
cards: &[PlacedNode],
cross: &dyn Fn(&PlacedNode) -> f64,
gap: f64,
) {
let kids = nodes[i].children.clone();
for &k in &kids {
measure(k, nodes, cards, cross, gap);
}
let block: f64 = if kids.is_empty() {
0.0
} else {
kids.iter().map(|&k| nodes[k].subtree_cross).sum::<f64>() + gap * (kids.len() - 1) as f64
};
nodes[i].subtree_cross = cross(&cards[nodes[i].card]).max(block);
}
#[allow(clippy::too_many_arguments)]
fn assign(
i: usize,
centre: f64,
nodes: &[Node],
cards: &mut [PlacedNode],
levels: &[usize],
band_centre: &[f64],
axis: Axis,
gap: f64,
) {
let card = nodes[i].card;
let main_c = band_centre[levels[card]];
match axis {
Axis::Column => {
cards[card].cx = centre;
cards[card].cy = main_c;
}
Axis::Row => {
cards[card].cx = main_c;
cards[card].cy = centre;
}
}
let block = children_block(&nodes[i].children, nodes, gap);
let mut cursor = centre - block / 2.0;
for &k in &nodes[i].children {
let slot = nodes[k].subtree_cross;
assign(
k,
cursor + slot / 2.0,
nodes,
cards,
levels,
band_centre,
axis,
gap,
);
cursor += slot + gap;
}
}
fn children_block(children: &[usize], nodes: &[Node], gap: f64) -> f64 {
if children.is_empty() {
return 0.0;
}
children
.iter()
.map(|&k| nodes[k].subtree_cross)
.sum::<f64>()
+ gap * (children.len() - 1) as f64
}
#[allow(clippy::too_many_arguments)]
fn pack(
children: &[usize],
centre: f64,
nodes: &[Node],
cards: &mut [PlacedNode],
levels: &[usize],
band_centre: &[f64],
axis: Axis,
gap: f64,
) {
let block = children_block(children, nodes, gap);
let mut cursor = centre - block / 2.0;
for &k in children {
let slot = nodes[k].subtree_cross;
assign(
k,
cursor + slot / 2.0,
nodes,
cards,
levels,
band_centre,
axis,
gap,
);
cursor += slot + gap;
}
}
fn place_generations(
cards: &mut [PlacedNode],
axis: Axis,
generation: f64,
sibling: f64,
) -> (Vec<Node>, Vec<usize>) {
let (mut nodes, roots, levels) = reconstruct(cards);
if cards.is_empty() {
return (nodes, roots);
}
let cross = |c: &PlacedNode| match axis {
Axis::Column => c.bbox.w(),
Axis::Row => c.bbox.h(),
};
let band_centre = band_centres(cards, &levels, axis, generation);
let mut cursor = 0.0;
for &r in &roots {
measure(r, &mut nodes, cards, &cross, sibling);
let span = nodes[r].subtree_cross;
assign(
r,
cursor + span / 2.0,
&nodes,
cards,
&levels,
&band_centre,
axis,
sibling,
);
cursor += span + sibling;
}
(nodes, roots)
}
fn place_bilateral(
cards: &mut [PlacedNode],
generation: f64,
sibling: f64,
) -> (Vec<Node>, Vec<usize>) {
let (mut nodes, roots, levels) = reconstruct(cards);
let Some(&root) = roots.first() else {
return (nodes, roots);
};
let axis = Axis::Row;
let cross = |c: &PlacedNode| c.bbox.h();
let band_right = band_centres(cards, &levels, axis, generation);
let band_left: Vec<f64> = band_right.iter().map(|x| -x).collect();
let first = nodes[root].children.clone();
let (mut right, mut left) = (Vec::new(), Vec::new());
for &k in &first {
measure(k, &mut nodes, cards, &cross, sibling);
match side_of(&cards[nodes[k].card]) {
Half::Right => right.push(k),
Half::Left => left.push(k),
}
}
pack(
&right,
0.0,
&nodes,
cards,
&levels,
&band_right,
axis,
sibling,
);
pack(
&left, 0.0, &nodes, cards, &levels, &band_left, axis, sibling,
);
let rc = nodes[root].card;
cards[rc].cx = band_right[0];
cards[rc].cy = 0.0;
(nodes, roots)
}
fn nest(cards: Vec<PlacedNode>, nodes: &[Node], roots: &[usize]) -> Vec<PlacedNode> {
fn build(i: usize, nodes: &[Node], slots: &mut [Option<PlacedNode>]) -> PlacedNode {
let mut me = slots[nodes[i].card].take().expect("card placed once");
let (mx, my) = (me.cx, me.cy);
for &k in &nodes[i].children {
let mut child = build(k, nodes, slots);
child.cx -= mx;
child.cy -= my;
me.children.push(child);
}
me
}
let mut slots: Vec<Option<PlacedNode>> = cards.into_iter().map(Some).collect();
roots.iter().map(|&r| build(r, nodes, &mut slots)).collect()
}
fn place_content(
children: &mut Vec<PlacedNode>,
content: Vec<PlacedNode>,
cluster: Bbox,
axis: Axis,
sibling: f64,
) -> Result<(), Error> {
let mut flow_cursor = match axis {
Axis::Column => cluster.min_y,
Axis::Row => cluster.min_x,
};
for mut c in content {
if let Some(pin) = super::anchors::read_pin(&c.attrs, c.span)? {
let (cx, cy) = pin.target(cluster, c.bbox);
c.cx = cx;
c.cy = cy;
} else {
match axis {
Axis::Column => {
c.cx = (cluster.min_x + cluster.max_x) / 2.0;
flow_cursor -= sibling + c.bbox.h() / 2.0;
c.cy = flow_cursor;
flow_cursor -= c.bbox.h() / 2.0;
}
Axis::Row => {
c.cy = (cluster.min_y + cluster.max_y) / 2.0;
flow_cursor -= sibling + c.bbox.w() / 2.0;
c.cx = flow_cursor;
flow_cursor -= c.bbox.w() / 2.0;
}
}
}
if let Some((dx, dy)) = super::anchors::translate(&c.attrs, c.span)? {
c.cx += dx;
c.cy += dy;
}
children.push(c);
}
Ok(())
}
#[cfg(test)]
mod tests;