use core::borrow::Borrow;
use crate::geometry::{AbsoluteAxis, AbstractAxis, InBothAbsAxis};
use crate::geometry::{Line, Point, Rect, Size};
use crate::style::{AlignItems, AlignSelf, AvailableSpace, Overflow, Position};
use crate::tree::{Layout, LayoutInput, LayoutOutput, LayoutPartialTreeExt, NodeId, RunMode, SizingMode};
use crate::util::debug::debug_log;
use crate::util::sys::{f32_max, GridTrackVec, Vec};
use crate::util::MaybeMath;
use crate::util::{MaybeResolve, ResolveOrZero};
use crate::{
style_helpers::*, AlignContent, BoxGenerationMode, BoxSizing, CoreStyle, GridContainerStyle, GridItemStyle,
JustifyContent, LayoutGridContainer,
};
use alignment::{align_and_position_item, align_tracks};
use explicit_grid::{compute_explicit_grid_size_in_axis, initialize_grid_tracks};
use implicit_grid::compute_grid_size_estimate;
use placement::place_grid_items;
use track_sizing::{
determine_if_item_crosses_flexible_or_intrinsic_tracks, resolve_item_track_indexes, track_sizing_algorithm,
};
use types::{CellOccupancyMatrix, GridTrack};
pub(crate) use types::{GridCoordinate, GridLine, OriginZeroLine};
mod alignment;
mod explicit_grid;
mod implicit_grid;
mod placement;
mod track_sizing;
mod types;
mod util;
pub fn compute_grid_layout(tree: &mut impl LayoutGridContainer, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
let LayoutInput { known_dimensions, parent_size, available_space, run_mode, .. } = inputs;
let style = tree.get_grid_container_style(node);
let aspect_ratio = style.aspect_ratio();
let padding = style.padding().resolve_or_zero(parent_size.width);
let border = style.border().resolve_or_zero(parent_size.width);
let padding_border = padding + border;
let padding_border_size = padding_border.sum_axes();
let box_sizing_adjustment =
if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
let min_size = style
.min_size()
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let max_size = style
.max_size()
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let preferred_size = if inputs.sizing_mode == SizingMode::InherentSize {
style
.size()
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(style.aspect_ratio())
.maybe_add(box_sizing_adjustment)
} else {
Size::NONE
};
let scrollbar_gutter = style.overflow().transpose().map(|overflow| match overflow {
Overflow::Scroll => style.scrollbar_width(),
_ => 0.0,
});
let mut content_box_inset = padding_border;
content_box_inset.right += scrollbar_gutter.x;
content_box_inset.bottom += scrollbar_gutter.y;
let align_content = style.align_content().unwrap_or(AlignContent::Stretch);
let justify_content = style.justify_content().unwrap_or(JustifyContent::Stretch);
let align_items = style.align_items();
let justify_items = style.justify_items();
let grid_template_columms = style.grid_template_columns();
let grid_template_rows = style.grid_template_rows();
let grid_auto_columms = style.grid_auto_columns();
let grid_auto_rows = style.grid_auto_rows();
let constrained_available_space = known_dimensions
.or(preferred_size)
.map(|size| size.map(AvailableSpace::Definite))
.unwrap_or(available_space)
.maybe_clamp(min_size, max_size)
.maybe_max(padding_border_size);
let available_grid_space = Size {
width: constrained_available_space
.width
.map_definite_value(|space| space - content_box_inset.horizontal_axis_sum()),
height: constrained_available_space
.height
.map_definite_value(|space| space - content_box_inset.vertical_axis_sum()),
};
let outer_node_size =
known_dimensions.or(preferred_size).maybe_clamp(min_size, max_size).maybe_max(padding_border_size);
let mut inner_node_size = Size {
width: outer_node_size.width.map(|space| space - content_box_inset.horizontal_axis_sum()),
height: outer_node_size.height.map(|space| space - content_box_inset.vertical_axis_sum()),
};
debug_log!("parent_size", dbg:parent_size);
debug_log!("outer_node_size", dbg:outer_node_size);
debug_log!("inner_node_size", dbg:inner_node_size);
if let (RunMode::ComputeSize, Some(width), Some(height)) = (run_mode, outer_node_size.width, outer_node_size.height)
{
return LayoutOutput::from_outer_size(Size { width, height });
}
let get_child_styles_iter =
|node| tree.child_ids(node).map(|child_node: NodeId| tree.get_grid_child_style(child_node));
let child_styles_iter = get_child_styles_iter(node);
let auto_fit_container_size = outer_node_size
.or(max_size)
.or(min_size)
.maybe_clamp(min_size, max_size)
.maybe_max(padding_border_size)
.maybe_sub(content_box_inset.sum_axes());
let explicit_col_count = compute_explicit_grid_size_in_axis(
&style,
grid_template_columms.borrow(),
auto_fit_container_size,
AbsoluteAxis::Horizontal,
);
let explicit_row_count = compute_explicit_grid_size_in_axis(
&style,
grid_template_rows.borrow(),
auto_fit_container_size,
AbsoluteAxis::Vertical,
);
let (est_col_counts, est_row_counts) =
compute_grid_size_estimate(explicit_col_count, explicit_row_count, child_styles_iter);
let mut items = Vec::with_capacity(tree.child_count(node));
let mut cell_occupancy_matrix = CellOccupancyMatrix::with_track_counts(est_col_counts, est_row_counts);
let in_flow_children_iter = || {
tree.child_ids(node)
.enumerate()
.map(|(index, child_node)| (index, child_node, tree.get_grid_child_style(child_node)))
.filter(|(_, _, style)| {
style.box_generation_mode() != BoxGenerationMode::None && style.position() != Position::Absolute
})
};
place_grid_items(
&mut cell_occupancy_matrix,
&mut items,
in_flow_children_iter,
style.grid_auto_flow(),
align_items.unwrap_or(AlignItems::Stretch),
justify_items.unwrap_or(AlignItems::Stretch),
);
let final_col_counts = *cell_occupancy_matrix.track_counts(AbsoluteAxis::Horizontal);
let final_row_counts = *cell_occupancy_matrix.track_counts(AbsoluteAxis::Vertical);
let mut columns = GridTrackVec::new();
let mut rows = GridTrackVec::new();
initialize_grid_tracks(
&mut columns,
final_col_counts,
grid_template_columms.borrow(),
grid_auto_columms.borrow(),
style.gap().width,
|column_index| cell_occupancy_matrix.column_is_occupied(column_index),
);
initialize_grid_tracks(
&mut rows,
final_row_counts,
grid_template_rows.borrow(),
grid_auto_rows.borrow(),
style.gap().height,
|row_index| cell_occupancy_matrix.row_is_occupied(row_index),
);
drop(grid_template_rows);
drop(grid_template_columms);
drop(grid_auto_rows);
drop(grid_auto_columms);
drop(style);
resolve_item_track_indexes(&mut items, final_col_counts, final_row_counts);
determine_if_item_crosses_flexible_or_intrinsic_tracks(&mut items, &columns, &rows);
let has_baseline_aligned_item = items.iter().any(|item| item.align_self == AlignSelf::Baseline);
track_sizing_algorithm(
tree,
AbstractAxis::Inline,
min_size.get(AbstractAxis::Inline),
max_size.get(AbstractAxis::Inline),
align_content,
available_grid_space,
inner_node_size,
&mut columns,
&mut rows,
&mut items,
|track: &GridTrack, parent_size: Option<f32>| track.max_track_sizing_function.definite_value(parent_size),
has_baseline_aligned_item,
);
let initial_column_sum = columns.iter().map(|track| track.base_size).sum::<f32>();
inner_node_size.width = inner_node_size.width.or_else(|| initial_column_sum.into());
items.iter_mut().for_each(|item| item.available_space_cache = None);
track_sizing_algorithm(
tree,
AbstractAxis::Block,
min_size.get(AbstractAxis::Block),
max_size.get(AbstractAxis::Block),
justify_content,
available_grid_space,
inner_node_size,
&mut rows,
&mut columns,
&mut items,
|track: &GridTrack, _| Some(track.base_size),
false, );
let initial_row_sum = rows.iter().map(|track| track.base_size).sum::<f32>();
inner_node_size.height = inner_node_size.height.or_else(|| initial_row_sum.into());
debug_log!("initial_column_sum", dbg:initial_column_sum);
debug_log!(dbg: columns.iter().map(|track| track.base_size).collect::<Vec<_>>());
debug_log!("initial_row_sum", dbg:initial_row_sum);
debug_log!(dbg: rows.iter().map(|track| track.base_size).collect::<Vec<_>>());
let resolved_style_size = known_dimensions.or(preferred_size);
let container_border_box = Size {
width: resolved_style_size
.get(AbstractAxis::Inline)
.unwrap_or_else(|| initial_column_sum + content_box_inset.horizontal_axis_sum())
.maybe_clamp(min_size.width, max_size.width)
.max(padding_border_size.width),
height: resolved_style_size
.get(AbstractAxis::Block)
.unwrap_or_else(|| initial_row_sum + content_box_inset.vertical_axis_sum())
.maybe_clamp(min_size.height, max_size.height)
.max(padding_border_size.height),
};
let container_content_box = Size {
width: f32_max(0.0, container_border_box.width - content_box_inset.horizontal_axis_sum()),
height: f32_max(0.0, container_border_box.height - content_box_inset.vertical_axis_sum()),
};
if run_mode == RunMode::ComputeSize {
return LayoutOutput::from_outer_size(container_border_box);
}
if !available_grid_space.width.is_definite() {
for column in &mut columns {
let min: Option<f32> =
column.min_track_sizing_function.resolved_percentage_size(container_content_box.width);
let max: Option<f32> =
column.max_track_sizing_function.resolved_percentage_size(container_content_box.width);
column.base_size = column.base_size.maybe_clamp(min, max);
}
}
if !available_grid_space.height.is_definite() {
for row in &mut rows {
let min: Option<f32> = row.min_track_sizing_function.resolved_percentage_size(container_content_box.height);
let max: Option<f32> = row.max_track_sizing_function.resolved_percentage_size(container_content_box.height);
row.base_size = row.base_size.maybe_clamp(min, max);
}
}
let mut rerun_column_sizing;
let has_percentage_column = columns.iter().any(|track| track.uses_percentage());
let parent_width_indefinite = !available_space.width.is_definite();
rerun_column_sizing = parent_width_indefinite && has_percentage_column;
if !rerun_column_sizing {
let min_content_contribution_changed =
items.iter_mut().filter(|item| item.crosses_intrinsic_column).any(|item| {
let available_space = item.available_space(
AbstractAxis::Inline,
&rows,
inner_node_size.height,
|track: &GridTrack, _| Some(track.base_size),
);
let new_min_content_contribution =
item.min_content_contribution(AbstractAxis::Inline, tree, available_space, inner_node_size);
let has_changed = Some(new_min_content_contribution) != item.min_content_contribution_cache.width;
item.available_space_cache = Some(available_space);
item.min_content_contribution_cache.width = Some(new_min_content_contribution);
item.max_content_contribution_cache.width = None;
item.minimum_contribution_cache.width = None;
has_changed
});
rerun_column_sizing = min_content_contribution_changed;
} else {
items.iter_mut().for_each(|item| {
item.available_space_cache = None;
item.min_content_contribution_cache.width = None;
item.max_content_contribution_cache.width = None;
item.minimum_contribution_cache.width = None;
});
}
if rerun_column_sizing {
track_sizing_algorithm(
tree,
AbstractAxis::Inline,
min_size.get(AbstractAxis::Inline),
max_size.get(AbstractAxis::Inline),
align_content,
available_grid_space,
inner_node_size,
&mut columns,
&mut rows,
&mut items,
|track: &GridTrack, _| Some(track.base_size),
has_baseline_aligned_item,
);
let mut rerun_row_sizing;
let has_percentage_row = rows.iter().any(|track| track.uses_percentage());
let parent_height_indefinite = !available_space.height.is_definite();
rerun_row_sizing = parent_height_indefinite && has_percentage_row;
if !rerun_row_sizing {
let min_content_contribution_changed =
items.iter_mut().filter(|item| item.crosses_intrinsic_column).any(|item| {
let available_space = item.available_space(
AbstractAxis::Block,
&columns,
inner_node_size.width,
|track: &GridTrack, _| Some(track.base_size),
);
let new_min_content_contribution =
item.min_content_contribution(AbstractAxis::Block, tree, available_space, inner_node_size);
let has_changed = Some(new_min_content_contribution) != item.min_content_contribution_cache.height;
item.available_space_cache = Some(available_space);
item.min_content_contribution_cache.height = Some(new_min_content_contribution);
item.max_content_contribution_cache.height = None;
item.minimum_contribution_cache.height = None;
has_changed
});
rerun_row_sizing = min_content_contribution_changed;
} else {
items.iter_mut().for_each(|item| {
item.available_space_cache = None;
item.min_content_contribution_cache.height = None;
item.max_content_contribution_cache.height = None;
item.minimum_contribution_cache.height = None;
});
}
if rerun_row_sizing {
track_sizing_algorithm(
tree,
AbstractAxis::Block,
min_size.get(AbstractAxis::Block),
max_size.get(AbstractAxis::Block),
justify_content,
available_grid_space,
inner_node_size,
&mut rows,
&mut columns,
&mut items,
|track: &GridTrack, _| Some(track.base_size),
false, );
}
}
align_tracks(
container_content_box.get(AbstractAxis::Inline),
Line { start: padding.left, end: padding.right },
Line { start: border.left, end: border.right },
&mut columns,
justify_content,
);
align_tracks(
container_content_box.get(AbstractAxis::Block),
Line { start: padding.top, end: padding.bottom },
Line { start: border.top, end: border.bottom },
&mut rows,
align_content,
);
#[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
let mut item_content_size_contribution = Size::ZERO;
items.sort_by_key(|item| item.source_order);
let container_alignment_styles = InBothAbsAxis { horizontal: justify_items, vertical: align_items };
for (index, item) in items.iter_mut().enumerate() {
let grid_area = Rect {
top: rows[item.row_indexes.start as usize + 1].offset,
bottom: rows[item.row_indexes.end as usize].offset,
left: columns[item.column_indexes.start as usize + 1].offset,
right: columns[item.column_indexes.end as usize].offset,
};
#[cfg_attr(not(feature = "content_size"), allow(unused_variables))]
let (content_size_contribution, y_position, height) = align_and_position_item(
tree,
item.node,
index as u32,
grid_area,
container_alignment_styles,
item.baseline_shim,
);
item.y_position = y_position;
item.height = height;
#[cfg(feature = "content_size")]
{
item_content_size_contribution = item_content_size_contribution.f32_max(content_size_contribution);
}
}
let mut order = items.len() as u32;
(0..tree.child_count(node)).for_each(|index| {
let child = tree.get_child_id(node, index);
let child_style = tree.get_grid_child_style(child);
if child_style.box_generation_mode() == BoxGenerationMode::None {
drop(child_style);
tree.set_unrounded_layout(child, &Layout::with_order(order));
tree.perform_child_layout(
child,
Size::NONE,
Size::NONE,
Size::MAX_CONTENT,
SizingMode::InherentSize,
Line::FALSE,
);
order += 1;
return;
}
if child_style.position() == Position::Absolute {
let maybe_col_indexes = child_style
.grid_column()
.into_origin_zero(final_col_counts.explicit)
.resolve_absolutely_positioned_grid_tracks()
.map(|maybe_grid_line| {
maybe_grid_line.map(|line: OriginZeroLine| line.into_track_vec_index(final_col_counts))
});
let maybe_row_indexes = child_style
.grid_row()
.into_origin_zero(final_row_counts.explicit)
.resolve_absolutely_positioned_grid_tracks()
.map(|maybe_grid_line| {
maybe_grid_line.map(|line: OriginZeroLine| line.into_track_vec_index(final_row_counts))
});
let grid_area = Rect {
top: maybe_row_indexes.start.map(|index| rows[index].offset).unwrap_or(border.top),
bottom: maybe_row_indexes
.end
.map(|index| rows[index].offset)
.unwrap_or(container_border_box.height - border.bottom - scrollbar_gutter.y),
left: maybe_col_indexes.start.map(|index| columns[index].offset).unwrap_or(border.left),
right: maybe_col_indexes
.end
.map(|index| columns[index].offset)
.unwrap_or(container_border_box.width - border.right - scrollbar_gutter.x),
};
drop(child_style);
#[cfg_attr(not(feature = "content_size"), allow(unused_variables))]
let (content_size_contribution, _, _) =
align_and_position_item(tree, child, order, grid_area, container_alignment_styles, 0.0);
#[cfg(feature = "content_size")]
{
item_content_size_contribution = item_content_size_contribution.f32_max(content_size_contribution);
}
order += 1;
}
});
if items.is_empty() {
return LayoutOutput::from_outer_size(container_border_box);
}
let grid_container_baseline: f32 = {
items.sort_by_key(|item| item.row_indexes.start);
let first_row = items[0].row_indexes.start;
let first_row_items = &items[0..].split(|item| item.row_indexes.start != first_row).next().unwrap();
let row_has_baseline_item = first_row_items.iter().any(|item| item.align_self == AlignSelf::Baseline);
let item = if row_has_baseline_item {
first_row_items.iter().find(|item| item.align_self == AlignSelf::Baseline).unwrap()
} else {
&first_row_items[0]
};
item.y_position + item.baseline.unwrap_or(item.height)
};
LayoutOutput::from_sizes_and_baselines(
container_border_box,
item_content_size_contribution,
Point { x: None, y: Some(grid_container_baseline) },
)
}