use std::collections::HashMap;
use super::{
Axis, Cell, CellId, Extent, GridNode, Inset, Layout, Node, Placement, Track, WidthHint,
};
use crate::geometry::{Rect, Size};
const MAX_ITER: usize = 5;
const EPSILON: f64 = 0.5;
const DAMPING: f64 = 0.5;
pub(super) fn solve(root: &GridNode, viewport: Size, dpi: f64) -> Layout {
let root_cell = Rect::new(0.0, 0.0, viewport.width, viewport.height);
let prepared = prepare(root, dpi);
let node_count = prepared.nodes.len();
let mut seeds = prepared.seeds;
let has_refs = tree_has_track_refs(root);
let has_respect_auto = tree_has_respect_with_auto_rows(root);
let mut widths = PassResults::default();
let mut heights = PassResults::default();
let needs_iteration = !seeds.is_empty() || has_refs || has_respect_auto;
let iter_cap = if needs_iteration { MAX_ITER } else { 1 };
for iter in 0..iter_cap.max(1) {
let resolved = Resolved {
grid_index: &prepared.grid_index,
widths: if iter == 0 { None } else { Some(&widths) },
heights: if iter == 0 { None } else { Some(&heights) },
};
let mut new_widths = PassResults::new(node_count);
{
let mut walk = MinWalk::new(
MinWidth { seeds: &seeds },
&prepared.nodes,
&resolved,
dpi,
node_count,
);
width_pass_grid(
root,
0,
Horizontal::band(&root_cell),
Vertical::band(&root_cell),
&mut walk,
&mut new_widths,
);
}
let mut new_heights = PassResults::new(node_count);
{
let mut walk = MinWalk::new(
MinHeight {
widths: &new_widths,
},
&prepared.nodes,
&resolved,
dpi,
node_count,
);
height_pass_grid(
root,
0,
Vertical::band(&root_cell),
&new_widths,
&mut walk,
&mut new_heights,
);
}
if seeds.is_empty() && !has_refs && !has_respect_auto {
widths = new_widths;
heights = new_heights;
break;
}
let needs_stability_check = has_refs || has_respect_auto;
let stable = !needs_stability_check
|| (results_match(&widths, &new_widths) && results_match(&heights, &new_heights));
let mut new_seeds = Vec::new();
compute_new_seeds(root, 0, &prepared.nodes, &new_heights, dpi, &mut new_seeds);
let seeds_converged = converged(&seeds, &new_seeds);
widths = new_widths;
heights = new_heights;
if (seeds.is_empty() || seeds_converged) && stable {
break;
}
if iter == iter_cap - 1 {
break;
}
for (idx, new) in new_seeds {
let prev = seeds.get(idx).unwrap_or(0.0);
seeds.set(idx, DAMPING * new + (1.0 - DAMPING) * prev);
}
}
let mut rects = HashMap::new();
emit_rects(root, 0, &prepared.nodes, &widths, &heights, &mut rects);
Layout {
root: root_cell,
rects,
}
}
#[derive(Clone, Copy, Debug, Default)]
struct Band {
start: f64,
end: f64,
}
impl Band {
fn size(self) -> f64 {
(self.end - self.start).max(0.0)
}
}
trait AxisView {
fn tracks(node: &GridNode) -> &[Track];
fn gap(node: &GridNode) -> &Extent;
fn respected(node: &GridNode, track: usize) -> bool;
fn placement_start(placement: &Placement) -> u16;
fn placement_span(placement: &Placement) -> u16;
fn inset_leading(inset: &Inset) -> Option<&Extent>;
fn inset_trailing(inset: &Inset) -> Option<&Extent>;
fn inset_size(inset: &Inset) -> Option<&Extent>;
fn band(rect: &Rect) -> Band;
}
struct Horizontal;
struct Vertical;
impl AxisView for Horizontal {
fn tracks(node: &GridNode) -> &[Track] {
&node.cols
}
fn gap(node: &GridNode) -> &Extent {
&node.gap.0
}
fn respected(node: &GridNode, track: usize) -> bool {
node.respect.col_respected(track)
}
fn placement_start(placement: &Placement) -> u16 {
placement.col
}
fn placement_span(placement: &Placement) -> u16 {
placement.col_span
}
fn inset_leading(inset: &Inset) -> Option<&Extent> {
inset.left.as_ref()
}
fn inset_trailing(inset: &Inset) -> Option<&Extent> {
inset.right.as_ref()
}
fn inset_size(inset: &Inset) -> Option<&Extent> {
inset.width.as_ref()
}
fn band(rect: &Rect) -> Band {
Band {
start: rect.x0,
end: rect.x1,
}
}
}
impl AxisView for Vertical {
fn tracks(node: &GridNode) -> &[Track] {
&node.rows
}
fn gap(node: &GridNode) -> &Extent {
&node.gap.1
}
fn respected(node: &GridNode, track: usize) -> bool {
node.respect.row_respected(track)
}
fn placement_start(placement: &Placement) -> u16 {
placement.row
}
fn placement_span(placement: &Placement) -> u16 {
placement.row_span
}
fn inset_leading(inset: &Inset) -> Option<&Extent> {
inset.top.as_ref()
}
fn inset_trailing(inset: &Inset) -> Option<&Extent> {
inset.bottom.as_ref()
}
fn inset_size(inset: &Inset) -> Option<&Extent> {
inset.height.as_ref()
}
fn band(rect: &Rect) -> Band {
Band {
start: rect.y0,
end: rect.y1,
}
}
}
type NodeIdx = usize;
struct NodeMap {
children: Vec<Vec<NodeIdx>>,
}
impl NodeMap {
fn len(&self) -> usize {
self.children.len()
}
fn child(&self, parent: NodeIdx, slot: usize) -> NodeIdx {
self.children[parent][slot]
}
}
#[derive(Default)]
struct Seeds {
values: Vec<Option<f64>>,
nodes: Vec<NodeIdx>,
}
impl Seeds {
fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
fn get(&self, idx: NodeIdx) -> Option<f64> {
self.values.get(idx).copied().flatten()
}
fn set(&mut self, idx: NodeIdx, value: f64) {
if self.values[idx].is_none() {
self.nodes.push(idx);
}
self.values[idx] = Some(value);
}
}
struct Prepared {
nodes: NodeMap,
grid_index: HashMap<CellId, NodeIdx>,
seeds: Seeds,
}
fn prepare(root: &GridNode, dpi: f64) -> Prepared {
let mut nodes = NodeMap {
children: vec![Vec::new()],
};
let mut grid_index = HashMap::new();
let mut initial_seeds: Vec<(NodeIdx, f64)> = Vec::new();
if let Some(id) = root.id {
grid_index.insert(id, 0);
}
walk_prepare(
root,
0,
dpi,
&mut nodes,
&mut grid_index,
&mut initial_seeds,
);
let mut seeds = Seeds {
values: vec![None; nodes.len()],
nodes: Vec::with_capacity(initial_seeds.len()),
};
for (idx, seed) in initial_seeds {
seeds.set(idx, seed);
}
Prepared {
nodes,
grid_index,
seeds,
}
}
fn walk_prepare(
node: &GridNode,
idx: NodeIdx,
dpi: f64,
nodes: &mut NodeMap,
grid_index: &mut HashMap<CellId, NodeIdx>,
seeds: &mut Vec<(NodeIdx, f64)>,
) {
for (_placement, child) in &node.children {
let child_idx = nodes.children.len();
nodes.children.push(Vec::new());
nodes.children[idx].push(child_idx);
match child {
Node::Grid(g) => {
if let Some(id) = g.id {
grid_index.insert(id, child_idx);
}
walk_prepare(g, child_idx, dpi, nodes, grid_index, seeds);
}
Node::Cell(c) => {
if let WidthHint::NeedsHeight { seed } = c.measure.width_hint(dpi) {
seeds.push((child_idx, seed));
}
}
}
}
}
struct Resolved<'a> {
grid_index: &'a HashMap<CellId, NodeIdx>,
widths: Option<&'a PassResults>,
heights: Option<&'a PassResults>,
}
impl Resolved<'_> {
fn prev_row_height(&self, idx: NodeIdx, row: usize) -> Option<f64> {
self.heights?.tracks(idx)?.sizes.get(row).copied()
}
fn track_size(&self, grid: CellId, axis: Axis, track: u16, span: u16) -> Option<f64> {
let idx = *self.grid_index.get(&grid)?;
let band = match axis {
Axis::Width => self.widths?.tracks(idx)?,
Axis::Height => self.heights?.tracks(idx)?,
};
let span = span.max(1) as usize;
let start = (track.saturating_sub(1)) as usize;
let end = (start + span).min(band.sizes.len());
let start = start.min(end);
if start >= end {
return Some(0.0);
}
let sum: f64 = band.sizes[start..end].iter().sum();
let gap_count = (end - start).saturating_sub(1) as f64;
Some(sum + gap_count * band.gap)
}
}
fn tree_has_respect_with_auto_rows(node: &GridNode) -> bool {
use crate::layout::Respect;
let respect_active = !matches!(node.respect, Respect::None);
if respect_active && node.rows.iter().any(|t| matches!(t, Track::Auto)) {
return true;
}
for (_placement, child) in &node.children {
if let Node::Grid(g) = child {
if tree_has_respect_with_auto_rows(g) {
return true;
}
}
}
false
}
fn tree_has_track_refs(node: &GridNode) -> bool {
if length_has_track_ref(&node.gap.0) || length_has_track_ref(&node.gap.1) {
return true;
}
for t in node.cols.iter().chain(node.rows.iter()) {
if let Track::Fixed(l) = t {
if length_has_track_ref(l) {
return true;
}
}
}
for (placement, child) in &node.children {
if inset_has_track_ref(&placement.inset) {
return true;
}
if let Node::Grid(g) = child {
if tree_has_track_refs(g) {
return true;
}
}
}
false
}
fn length_has_track_ref(l: &Extent) -> bool {
match l {
Extent::Sum { .. } => false,
Extent::Min(a, b) | Extent::Max(a, b) => length_has_track_ref(a) || length_has_track_ref(b),
Extent::TrackOf { .. } => true,
}
}
fn inset_has_track_ref(inset: &Inset) -> bool {
[
&inset.left,
&inset.right,
&inset.top,
&inset.bottom,
&inset.width,
&inset.height,
]
.iter()
.any(|opt| opt.as_ref().is_some_and(length_has_track_ref))
}
fn results_match(a: &PassResults, b: &PassResults) -> bool {
if a.grids.len() != b.grids.len() {
return false;
}
for (av, bv) in a.grids.iter().zip(b.grids.iter()) {
match (av, bv) {
(None, None) => {}
(Some(av), Some(bv)) => {
if av.sizes.len() != bv.sizes.len() {
return false;
}
for (a_size, b_size) in av.sizes.iter().zip(bv.sizes.iter()) {
if (a_size - b_size).abs() > EPSILON {
return false;
}
}
}
_ => return false,
}
}
true
}
struct TrackBand {
sizes: Vec<f64>,
gap: f64,
span: Band,
per_fr: f64,
}
#[derive(Default)]
struct PassResults {
grids: Vec<Option<TrackBand>>,
cells: Vec<Band>,
}
impl PassResults {
fn new(count: usize) -> Self {
Self {
grids: (0..count).map(|_| None).collect(),
cells: vec![Band::default(); count],
}
}
fn tracks(&self, idx: NodeIdx) -> Option<&TrackBand> {
self.grids.get(idx)?.as_ref()
}
}
struct CrossRespect {
fr_respected: f64,
scale: f64,
}
impl CrossRespect {
const INACTIVE: CrossRespect = CrossRespect {
fr_respected: 0.0,
scale: 0.0,
};
}
struct AxisSolution {
sizes: Vec<f64>,
gap: f64,
total: f64,
free: f64,
per_fr: f64,
}
impl AxisSolution {
fn span(&self, origin: f64, avail: f64) -> Band {
let start = origin + ((avail - self.total) * 0.5).max(0.0);
Band {
start,
end: start + self.total,
}
}
}
fn resolve_tracks<A: AxisView>(
node: &GridNode,
avail: f64,
auto: &[f64],
cross: CrossRespect,
dpi: f64,
resolved: &Resolved,
) -> AxisSolution {
let tracks = A::tracks(node);
let gap = length_to_px(A::gap(node), dpi, avail, resolved);
let gap_total = saturating_gap_total(tracks.len(), gap);
let fixed = sum_fixed_track_size(tracks, dpi, avail, resolved);
let fr_sum = sum_fr(tracks);
let auto_total: f64 = auto.iter().sum();
let free = (avail - fixed - auto_total - gap_total).max(0.0);
let per_fr_default = if fr_sum > 0.0 { free / fr_sum } else { 0.0 };
let (fr_respected, fr_unrespected) = split_fr(tracks, |i| A::respected(node, i));
let respect_active = fr_respected > 0.0 && cross.fr_respected > 0.0;
let fr_total_resp = fr_respected + fr_unrespected;
let resp_scale_main = if respect_active && fr_total_resp > 0.0 {
free / fr_total_resp
} else {
0.0
};
let resp_scale = if respect_active {
resp_scale_main.min(cross.scale)
} else {
0.0
};
let respected_total = fr_respected * resp_scale;
let unresp_scale = if respect_active && fr_unrespected > 0.0 {
((free - respected_total).max(0.0)) / fr_unrespected
} else if !respect_active {
per_fr_default
} else {
0.0
};
let sizes: Vec<f64> = tracks
.iter()
.enumerate()
.map(|(i, t)| match t {
Track::Fixed(l) => length_to_px(l, dpi, avail, resolved),
Track::Fr(f) => {
let scale = if respect_active && A::respected(node, i) {
resp_scale
} else {
unresp_scale
};
*f * scale
}
Track::Auto => auto[i],
})
.collect();
let total = sizes.iter().sum::<f64>() + gap_total;
let per_fr = if respect_active {
resp_scale
} else {
per_fr_default
};
AxisSolution {
sizes,
gap,
total,
free,
per_fr,
}
}
trait MinSizer {
type Axis: AxisView;
fn leaf(&self, cell: &Cell, node: NodeIdx, cross: f64, dpi: f64) -> f64;
fn cross(&self, parent: NodeIdx, placement: &Placement, dpi: f64, resolved: &Resolved) -> f64;
}
struct MinWidth<'a> {
seeds: &'a Seeds,
}
impl MinSizer for MinWidth<'_> {
type Axis = Horizontal;
fn leaf(&self, cell: &Cell, node: NodeIdx, _cross: f64, dpi: f64) -> f64 {
match cell.measure.width_hint(dpi) {
WidthHint::Min(w) => w,
WidthHint::NeedsHeight { seed } => self.seeds.get(node).unwrap_or(seed),
}
}
fn cross(
&self,
_parent: NodeIdx,
_placement: &Placement,
_dpi: f64,
_resolved: &Resolved,
) -> f64 {
0.0
}
}
struct MinHeight<'a> {
widths: &'a PassResults,
}
impl MinSizer for MinHeight<'_> {
type Axis = Vertical;
fn leaf(&self, cell: &Cell, _node: NodeIdx, cross: f64, dpi: f64) -> f64 {
cell.measure.height_at(cross, dpi)
}
fn cross(&self, parent: NodeIdx, placement: &Placement, dpi: f64, resolved: &Resolved) -> f64 {
let gw = self.widths.tracks(parent).expect("grid widths recorded");
child_range::<Horizontal>(&gw.sizes, gw.gap, gw.span.start, placement, dpi, resolved).size()
}
}
struct MinWalk<'a, S: MinSizer> {
sizer: S,
nodes: &'a NodeMap,
resolved: &'a Resolved<'a>,
dpi: f64,
memo: Vec<Option<f64>>,
}
impl<'a, S: MinSizer> MinWalk<'a, S> {
fn new(
sizer: S,
nodes: &'a NodeMap,
resolved: &'a Resolved<'a>,
dpi: f64,
node_count: usize,
) -> Self {
Self {
sizer,
nodes,
resolved,
dpi,
memo: vec![None; node_count],
}
}
fn auto_tracks(&mut self, node: &GridNode, idx: NodeIdx) -> Vec<f64> {
let tracks = S::Axis::tracks(node);
let mut out = vec![0.0; tracks.len()];
for (slot, (placement, child)) in node.children.iter().enumerate() {
if S::Axis::placement_span(placement).max(1) != 1 {
continue;
}
let track = (S::Axis::placement_start(placement).saturating_sub(1)) as usize;
if track >= tracks.len() {
continue;
}
if !matches!(tracks[track], Track::Auto) {
continue;
}
let cross = self.sizer.cross(idx, placement, self.dpi, self.resolved);
let child_idx = self.nodes.child(idx, slot);
let contrib = self.child_min(child, child_idx, &placement.inset, cross);
if contrib > out[track] {
out[track] = contrib;
}
}
out
}
fn child_min(&mut self, child: &Node, idx: NodeIdx, inset: &Inset, cross: f64) -> f64 {
if let Some(size) = S::Axis::inset_size(inset) {
return length_to_px_abs(size, self.dpi, self.resolved);
}
let leading = S::Axis::inset_leading(inset)
.map_or(0.0, |v| length_to_px_abs(v, self.dpi, self.resolved));
let trailing = S::Axis::inset_trailing(inset)
.map_or(0.0, |v| length_to_px_abs(v, self.dpi, self.resolved));
let inner = match child {
Node::Grid(g) => self.grid_min(g, idx),
Node::Cell(c) => self.sizer.leaf(c, idx, cross, self.dpi),
};
leading + inner + trailing
}
fn grid_min(&mut self, g: &GridNode, idx: NodeIdx) -> f64 {
if let Some(cached) = self.memo[idx] {
return cached;
}
let gap = length_to_px_abs(S::Axis::gap(g), self.dpi, self.resolved);
let gap_total = saturating_gap_total(S::Axis::tracks(g).len(), gap);
let auto = self.auto_tracks(g, idx);
let total = S::Axis::tracks(g)
.iter()
.enumerate()
.map(|(i, t)| match t {
Track::Fixed(l) => length_to_px_abs(l, self.dpi, self.resolved),
_ => auto[i],
})
.sum::<f64>()
+ gap_total;
self.memo[idx] = Some(total);
total
}
}
fn width_pass_grid(
node: &GridNode,
idx: NodeIdx,
x: Band,
y: Band,
walk: &mut MinWalk<'_, MinWidth<'_>>,
out: &mut PassResults,
) {
let dpi = walk.dpi;
let resolved = walk.resolved;
let nodes = walk.nodes;
let avail_w = x.size();
let avail_h = y.size();
let prev_rows: Vec<f64> = node
.rows
.iter()
.enumerate()
.map(|(i, t)| match t {
Track::Auto => resolved.prev_row_height(idx, i).unwrap_or(0.0),
_ => 0.0,
})
.collect();
let provisional = resolve_tracks::<Vertical>(
node,
avail_h,
&prev_rows,
CrossRespect::INACTIVE,
dpi,
resolved,
);
let (row_fr_respected, row_fr_unrespected) =
split_fr(&node.rows, |i| node.respect.row_respected(i));
let row_fr_total_resp = row_fr_respected + row_fr_unrespected;
let cross = CrossRespect {
fr_respected: row_fr_respected,
scale: if row_fr_total_resp > 0.0 {
provisional.free / row_fr_total_resp
} else {
0.0
},
};
let auto = walk.auto_tracks(node, idx);
let solution = resolve_tracks::<Horizontal>(node, avail_w, &auto, cross, dpi, resolved);
let span = solution.span(x.start, avail_w);
out.grids[idx] = Some(TrackBand {
sizes: solution.sizes.clone(),
gap: solution.gap,
span,
per_fr: solution.per_fr,
});
for (slot, (placement, child)) in node.children.iter().enumerate() {
let child_x = child_range::<Horizontal>(
&solution.sizes,
solution.gap,
span.start,
placement,
dpi,
resolved,
);
let child_y = child_range::<Vertical>(
&provisional.sizes,
provisional.gap,
y.start,
placement,
dpi,
resolved,
);
let child_idx = nodes.child(idx, slot);
match child {
Node::Grid(g) => width_pass_grid(g, child_idx, child_x, child_y, walk, out),
Node::Cell(_) => out.cells[child_idx] = child_x,
}
}
}
fn height_pass_grid(
node: &GridNode,
idx: NodeIdx,
y: Band,
widths: &PassResults,
walk: &mut MinWalk<'_, MinHeight<'_>>,
out: &mut PassResults,
) {
let dpi = walk.dpi;
let resolved = walk.resolved;
let nodes = walk.nodes;
let avail = y.size();
let gw = widths.tracks(idx).expect("grid widths recorded");
let (col_fr_respected, _col_fr_unrespected) =
split_fr(&node.cols, |i| node.respect.col_respected(i));
let cross = CrossRespect {
fr_respected: col_fr_respected,
scale: gw.per_fr,
};
let auto = walk.auto_tracks(node, idx);
let solution = resolve_tracks::<Vertical>(node, avail, &auto, cross, dpi, resolved);
let span = solution.span(y.start, avail);
out.grids[idx] = Some(TrackBand {
sizes: solution.sizes.clone(),
gap: solution.gap,
span,
per_fr: solution.per_fr,
});
for (slot, (placement, child)) in node.children.iter().enumerate() {
let child_y = child_range::<Vertical>(
&solution.sizes,
solution.gap,
span.start,
placement,
dpi,
resolved,
);
let child_idx = nodes.child(idx, slot);
match child {
Node::Grid(g) => height_pass_grid(g, child_idx, child_y, widths, walk, out),
Node::Cell(_) => out.cells[child_idx] = child_y,
}
}
}
fn emit_rects(
node: &GridNode,
idx: NodeIdx,
nodes: &NodeMap,
widths: &PassResults,
heights: &PassResults,
out: &mut HashMap<CellId, Rect>,
) {
let gw = widths.tracks(idx).expect("grid widths recorded");
let gh = heights.tracks(idx).expect("grid heights recorded");
if let Some(id) = node.id {
out.insert(
id,
Rect::new(gw.span.start, gh.span.start, gw.span.end, gh.span.end),
);
}
for (slot, (_placement, child)) in node.children.iter().enumerate() {
let child_idx = nodes.child(idx, slot);
match child {
Node::Grid(g) => emit_rects(g, child_idx, nodes, widths, heights, out),
Node::Cell(c) => {
if let Some(id) = c.id {
let x = widths.cells[child_idx];
let y = heights.cells[child_idx];
out.insert(id, Rect::new(x.start, y.start, x.end, y.end));
}
}
}
}
}
fn compute_new_seeds(
node: &GridNode,
idx: NodeIdx,
nodes: &NodeMap,
heights: &PassResults,
dpi: f64,
out: &mut Vec<(NodeIdx, f64)>,
) {
for (slot, (_placement, child)) in node.children.iter().enumerate() {
let child_idx = nodes.child(idx, slot);
match child {
Node::Grid(g) => compute_new_seeds(g, child_idx, nodes, heights, dpi, out),
Node::Cell(c) => {
if matches!(c.measure.width_hint(dpi), WidthHint::NeedsHeight { .. }) {
let h = heights.cells[child_idx].size();
out.push((child_idx, c.measure.width_at(h, dpi)));
}
}
}
}
}
fn converged(old: &Seeds, new: &[(NodeIdx, f64)]) -> bool {
if old.nodes.len() != new.len() {
return false;
}
for (idx, v_new) in new {
let v_old = old.get(*idx).unwrap_or(f64::INFINITY);
if (v_new - v_old).abs() > EPSILON {
return false;
}
}
true
}
fn sum_fixed_track_size(tracks: &[Track], dpi: f64, axis: f64, resolved: &Resolved) -> f64 {
tracks
.iter()
.filter_map(|t| match t {
Track::Fixed(l) => Some(length_to_px(l, dpi, axis, resolved)),
_ => None,
})
.sum()
}
fn sum_fr(tracks: &[Track]) -> f64 {
tracks
.iter()
.filter_map(|t| match t {
Track::Fr(f) => Some(*f),
_ => None,
})
.sum()
}
fn split_fr<F: Fn(usize) -> bool>(tracks: &[Track], respected: F) -> (f64, f64) {
let mut resp = 0.0;
let mut unresp = 0.0;
for (i, t) in tracks.iter().enumerate() {
if let Track::Fr(f) = t {
if respected(i) {
resp += *f;
} else {
unresp += *f;
}
}
}
(resp, unresp)
}
fn saturating_gap_total(track_count: usize, gap: f64) -> f64 {
if track_count <= 1 {
0.0
} else {
(track_count - 1) as f64 * gap
}
}
fn child_range<A: AxisView>(
sizes: &[f64],
gap: f64,
grid_start: f64,
placement: &Placement,
dpi: f64,
resolved: &Resolved,
) -> Band {
let span = A::placement_span(placement).max(1);
let start = (A::placement_start(placement).saturating_sub(1)) as usize;
let end_excl = (start + span as usize).min(sizes.len());
let start = start.min(sizes.len());
let cell_start = grid_start + track_offset(sizes, gap, start);
let cell_end = if end_excl == 0 {
cell_start
} else {
grid_start + track_end(sizes, gap, end_excl - 1)
};
let avail = (cell_end - cell_start).max(0.0);
let inset = &placement.inset;
resolve_axis(
cell_start,
avail,
A::inset_leading(inset),
A::inset_trailing(inset),
A::inset_size(inset),
dpi,
resolved,
)
}
fn track_offset(sizes: &[f64], gap: f64, idx: usize) -> f64 {
let mut acc = 0.0;
for (i, s) in sizes.iter().enumerate() {
if i >= idx {
break;
}
acc += s + gap;
}
acc
}
fn track_end(sizes: &[f64], gap: f64, idx: usize) -> f64 {
let mut acc = 0.0;
for (i, s) in sizes.iter().enumerate() {
if i > idx {
break;
}
acc += s;
if i < idx {
acc += gap;
}
}
acc
}
fn resolve_axis(
origin: f64,
avail: f64,
leading: Option<&Extent>,
trailing: Option<&Extent>,
size: Option<&Extent>,
dpi: f64,
resolved: &Resolved,
) -> Band {
let l = leading.map_or(0.0, |v| length_to_px(v, dpi, avail, resolved));
let t = trailing.map_or(0.0, |v| length_to_px(v, dpi, avail, resolved));
let (start, end) = match size {
None => {
let start = origin + l;
let end = (origin + avail - t).max(start);
(start, end)
}
Some(w) => {
let w_px = length_to_px(w, dpi, avail, resolved);
match (leading.is_some(), trailing.is_some()) {
(true, _) => (origin + l, origin + l + w_px),
(false, true) => {
let end = origin + avail - t;
(end - w_px, end)
}
(false, false) => (origin, origin + w_px),
}
}
};
Band { start, end }
}
fn length_to_px(l: &Extent, dpi: f64, axis_size: f64, resolved: &Resolved) -> f64 {
match l {
Extent::Sum {
px,
inches,
percent,
} => px + inches * dpi + percent * axis_size,
Extent::Min(a, b) => {
length_to_px(a, dpi, axis_size, resolved).min(length_to_px(b, dpi, axis_size, resolved))
}
Extent::Max(a, b) => {
length_to_px(a, dpi, axis_size, resolved).max(length_to_px(b, dpi, axis_size, resolved))
}
Extent::TrackOf {
grid,
axis,
track,
span,
} => resolved
.track_size(*grid, *axis, *track, *span)
.unwrap_or(0.0),
}
}
fn length_to_px_abs(l: &Extent, dpi: f64, resolved: &Resolved) -> f64 {
match l {
Extent::Sum { px, inches, .. } => px + inches * dpi,
Extent::Min(a, b) => {
length_to_px_abs(a, dpi, resolved).min(length_to_px_abs(b, dpi, resolved))
}
Extent::Max(a, b) => {
length_to_px_abs(a, dpi, resolved).max(length_to_px_abs(b, dpi, resolved))
}
Extent::TrackOf {
grid,
axis,
track,
span,
} => resolved
.track_size(*grid, *axis, *track, *span)
.unwrap_or(0.0),
}
}