use crate::node_id::NodeId;
use crate::style::{BoxSizing, KeylineType, Scalar, Style};
use crate::widget_tree::WidgetTree;
use super::common::{get_node_style, resolve_scalar_to_cells};
use super::region::{Region, border_spacing};
use super::resolve_1d::{Edge, layout_resolve_1d};
fn grid_scalar_to_edge(scalar: &Scalar, parent_size: u16, viewport_size: u16) -> Edge {
match scalar {
Scalar::Auto => Edge {
size: None,
fraction: 1,
min_size: 0,
},
Scalar::Fraction(f) => Edge {
size: None,
fraction: f.ceil().max(1.0) as u16,
min_size: 0,
},
_ => {
let cells = resolve_scalar_to_cells(scalar, parent_size, viewport_size);
Edge {
size: Some(cells),
fraction: 1,
min_size: 0,
}
}
}
}
fn build_grid_track_edges(
scalars: Option<&[Scalar]>,
count: usize,
parent_size: u16,
viewport_size: u16,
) -> Vec<Edge> {
let default_scalar = Scalar::Fraction(1.0);
(0..count)
.map(|i| {
let scalar = match scalars {
Some(s) if !s.is_empty() => &s[i % s.len()],
_ => &default_scalar,
};
grid_scalar_to_edge(scalar, parent_size, viewport_size)
})
.collect()
}
fn compute_rows_with_spans(tree: &WidgetTree, children: &[NodeId], num_cols: usize) -> usize {
let mut max_row_end = 0usize;
let mut occupied: Vec<Vec<bool>> = Vec::new();
for &child in children {
let style = get_node_style(tree, child);
let cs = (style.column_span.unwrap_or(1).max(1) as usize).min(num_cols);
let rs = style.row_span.unwrap_or(1).max(1) as usize;
let (start_col, start_row) = find_next_grid_slot(&occupied, cs, rs, num_cols);
while occupied.len() < start_row + rs {
occupied.push(vec![false; num_cols]);
}
for r in start_row..start_row + rs {
for c in start_col..start_col + cs {
occupied[r][c] = true;
}
}
max_row_end = max_row_end.max(start_row + rs);
}
max_row_end.max(1)
}
fn find_next_grid_slot(
occupied: &[Vec<bool>],
col_span: usize,
row_span: usize,
num_cols: usize,
) -> (usize, usize) {
if col_span == 0 || row_span == 0 || col_span > num_cols {
return (0, 0);
}
let mut row = 0usize;
let mut col = 0usize;
loop {
if col + col_span > num_cols {
col = 0;
row += 1;
continue;
}
let fits = (row..row + row_span).all(|r| {
if r >= occupied.len() {
return true; }
(col..col + col_span).all(|c| !occupied[r][c])
});
if fits {
return (col, row);
}
col += 1;
}
}
pub fn layout_grid(
tree: &mut WidgetTree,
children: &[NodeId],
available: Region,
viewport: (u16, u16),
parent_style: &Style,
) {
if children.is_empty() {
return;
}
let keyline_enabled = parent_style
.keyline
.is_some_and(|k| k.keyline_type != KeylineType::None);
let grid_available = if keyline_enabled && available.width > 2 && available.height > 2 {
Region::new(
available.x.saturating_add(1),
available.y.saturating_add(1),
available.width.saturating_sub(2),
available.height.saturating_sub(2),
)
} else {
available
};
let num_cols = parent_style.grid_size_columns.unwrap_or(1).max(1) as usize;
let gutter_h = parent_style.grid_gutter_horizontal.unwrap_or(0);
let gutter_v = parent_style.grid_gutter_vertical.unwrap_or(0);
let any_spans = children.iter().any(|&c| {
let s = get_node_style(tree, c);
s.column_span.unwrap_or(1) > 1 || s.row_span.unwrap_or(1) > 1
});
let min_rows = (children.len() + num_cols - 1) / num_cols;
let num_rows = if any_spans {
let span_rows = compute_rows_with_spans(tree, children, num_cols);
match parent_style.grid_size_rows {
Some(r) => span_rows.max(r.max(1) as usize),
None => span_rows,
}
} else {
match parent_style.grid_size_rows {
Some(r) => (r.max(1) as usize).max(min_rows),
None => min_rows,
}
};
let total_gutter_v = if num_cols > 1 {
(num_cols as u16 - 1).saturating_mul(gutter_v)
} else {
0
};
let col_budget = grid_available.width.saturating_sub(total_gutter_v);
let col_edges = build_grid_track_edges(
parent_style.grid_columns.as_deref(),
num_cols,
grid_available.width,
viewport.0,
);
let col_widths = layout_resolve_1d(col_budget, &col_edges);
let total_gutter_h = if num_rows > 1 {
(num_rows as u16 - 1).saturating_mul(gutter_h)
} else {
0
};
let row_budget = grid_available.height.saturating_sub(total_gutter_h);
let row_edges = build_grid_track_edges(
parent_style.grid_rows.as_deref(),
num_rows,
grid_available.height,
viewport.1,
);
let row_heights = layout_resolve_1d(row_budget, &row_edges);
let mut col_offsets = Vec::with_capacity(num_cols);
{
let mut x: u16 = 0;
for c in 0..num_cols {
col_offsets.push(x);
x = x.saturating_add(col_widths[c]);
if c + 1 < num_cols {
x = x.saturating_add(gutter_v);
}
}
}
let mut row_offsets = Vec::with_capacity(num_rows);
{
let mut y: u16 = 0;
for r in 0..num_rows {
row_offsets.push(y);
y = y.saturating_add(row_heights[r]);
if r + 1 < num_rows {
y = y.saturating_add(gutter_h);
}
}
}
let mut occupied = vec![vec![false; num_cols]; num_rows];
let mut next_row = 0usize;
let mut next_col = 0usize;
for &child in children.iter() {
let style = get_node_style(tree, child);
let col_span = (style.column_span.unwrap_or(1).max(1) as usize).min(num_cols);
let row_span = (style.row_span.unwrap_or(1).max(1) as usize).min(num_rows);
let (start_col, start_row) = if any_spans {
let pos = find_next_grid_slot(&occupied, col_span, row_span, num_cols);
next_row = pos.1;
next_col = pos.0;
pos
} else {
let c = next_col;
let r = next_row;
next_col += 1;
if next_col >= num_cols {
next_col = 0;
next_row += 1;
}
(c, r)
};
if start_row >= num_rows {
break; }
let end_col = (start_col + col_span).min(num_cols);
let end_row = (start_row + row_span).min(num_rows);
for r in start_row..end_row {
for c in start_col..end_col {
occupied[r][c] = true;
}
}
let cell_x = col_offsets[start_col];
let cell_y = row_offsets[start_row];
let last_col = end_col - 1;
let last_row = end_row - 1;
let cell_w = (col_offsets[last_col] + col_widths[last_col]).saturating_sub(cell_x);
let cell_h = (row_offsets[last_row] + row_heights[last_row]).saturating_sub(cell_y);
let margin = style.effective_margin();
let padding = style.effective_padding();
let (bt, bb, bl, br) = border_spacing(&style);
let box_sizing = style.box_sizing.unwrap_or(BoxSizing::BorderBox);
let layout_x = grid_available
.x
.saturating_add(cell_x)
.saturating_add(margin.left);
let layout_y = grid_available
.y
.saturating_add(cell_y)
.saturating_add(margin.top);
let mut layout_w = cell_w.saturating_sub(margin.left + margin.right);
let mut layout_h = cell_h.saturating_sub(margin.top + margin.bottom);
if let Some(ref s) = style.max_width {
let max_w = resolve_scalar_to_cells(s, available.width, viewport.0);
let max_w_outer = if box_sizing == BoxSizing::BorderBox {
max_w
} else {
max_w.saturating_add(bl + br + padding.left + padding.right)
};
layout_w = layout_w.min(max_w_outer);
}
if let Some(ref s) = style.min_width {
let min_w = resolve_scalar_to_cells(s, available.width, viewport.0);
let min_w_outer = if box_sizing == BoxSizing::BorderBox {
min_w
} else {
min_w.saturating_add(bl + br + padding.left + padding.right)
};
layout_w = layout_w.max(min_w_outer);
}
if let Some(ref s) = style.max_height {
let max_h = resolve_scalar_to_cells(s, available.height, viewport.1);
let max_h_outer = if box_sizing == BoxSizing::BorderBox {
max_h
} else {
max_h.saturating_add(bt + bb + padding.top + padding.bottom)
};
layout_h = layout_h.min(max_h_outer);
}
if let Some(ref s) = style.min_height {
let min_h = resolve_scalar_to_cells(s, available.height, viewport.1);
let min_h_outer = if box_sizing == BoxSizing::BorderBox {
min_h
} else {
min_h.saturating_add(bt + bb + padding.top + padding.bottom)
};
layout_h = layout_h.max(min_h_outer);
}
let content_x = layout_x.saturating_add(bl + padding.left);
let content_y = layout_y.saturating_add(bt + padding.top);
let content_w = layout_w.saturating_sub(bl + br + padding.left + padding.right);
let content_h = layout_h.saturating_sub(bt + bb + padding.top + padding.bottom);
if let Some(node) = tree.get_mut(child) {
node.layout_rect = Region::new(layout_x, layout_y, layout_w, layout_h).to_rect();
node.content_rect = Region::new(content_x, content_y, content_w, content_h).to_rect();
}
}
}