use crate::axis::{AbsoluteAxis, AbstractAxis, InBothAbsAxis};
use crate::geometry::{Line, Point, Rect, Size};
use crate::layout::{Layout, RunMode, SizeAndBaselines, SizingMode};
use crate::math::MaybeMath;
use crate::node::Node;
use crate::resolve::{MaybeResolve, ResolveOrZero};
use crate::style::{AlignContent, AlignItems, AlignSelf, AvailableSpace, Display, Position};
use crate::style_helpers::*;
use crate::sys::{GridTrackVec, Vec};
use crate::tree::LayoutTree;
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};
#[cfg(feature = "debug")]
use crate::debug::NODE_LOGGER;
use super::{GenericAlgorithm, LayoutAlgorithm};
mod alignment;
mod explicit_grid;
mod implicit_grid;
mod placement;
mod track_sizing;
mod types;
mod util;
pub(crate) struct CssGridAlgorithm;
impl LayoutAlgorithm for CssGridAlgorithm {
const NAME: &'static str = "CSS GRID";
fn perform_layout(
tree: &mut impl LayoutTree,
node: Node,
known_dimensions: Size<Option<f32>>,
parent_size: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
sizing_mode: SizingMode,
) -> SizeAndBaselines {
compute(tree, node, known_dimensions, parent_size, available_space, RunMode::PeformLayout, sizing_mode)
}
fn measure_size(
tree: &mut impl LayoutTree,
node: Node,
known_dimensions: Size<Option<f32>>,
parent_size: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
sizing_mode: SizingMode,
) -> Size<f32> {
compute(tree, node, known_dimensions, parent_size, available_space, RunMode::ComputeSize, sizing_mode).size
}
}
pub fn compute(
tree: &mut impl LayoutTree,
node: Node,
known_dimensions: Size<Option<f32>>,
parent_size: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
run_mode: RunMode,
sizing_mode: SizingMode,
) -> SizeAndBaselines {
let get_child_styles_iter = |node| tree.children(node).map(|child_node: &Node| tree.style(*child_node));
let style = tree.style(node).clone();
let child_styles_iter = get_child_styles_iter(node);
let preferred_size = if sizing_mode == SizingMode::InherentSize {
style.size.maybe_resolve(parent_size).maybe_apply_aspect_ratio(style.aspect_ratio)
} else {
Size::NONE
};
let explicit_col_count = compute_explicit_grid_size_in_axis(&style, preferred_size, AbsoluteAxis::Horizontal);
let explicit_row_count = compute_explicit_grid_size_in_axis(&style, preferred_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.children(node)
.copied()
.enumerate()
.map(|(index, child_node)| (index, child_node, tree.style(child_node)))
.filter(|(_, _, style)| style.display != Display::None && style.position != Position::Absolute)
};
place_grid_items(
&mut cell_occupancy_matrix,
&mut items,
in_flow_children_iter,
style.grid_auto_flow,
style.align_items.unwrap_or(AlignItems::Stretch),
style.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,
&style.grid_template_columns,
&style.grid_auto_columns,
style.gap.width,
|column_index| cell_occupancy_matrix.column_is_occupied(column_index),
);
initialize_grid_tracks(
&mut rows,
final_row_counts,
&style.grid_template_rows,
&style.grid_auto_rows,
style.gap.height,
|row_index| cell_occupancy_matrix.row_is_occupied(row_index),
);
let padding = style.padding.resolve_or_zero(parent_size.width);
let border = style.border.resolve_or_zero(parent_size.width);
let padding_border_size = (padding + border).sum_axes();
let aspect_ratio = style.aspect_ratio;
let min_size = style.min_size.maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio);
let max_size = style.max_size.maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio);
let size = preferred_size;
let constrained_available_space = known_dimensions
.or(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 - padding.horizontal_axis_sum() - border.horizontal_axis_sum()),
height: constrained_available_space
.height
.map_definite_value(|space| space - padding.vertical_axis_sum() - border.vertical_axis_sum()),
};
let outer_node_size = known_dimensions.or(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 - padding.horizontal_axis_sum() - border.horizontal_axis_sum()),
height: outer_node_size.height.map(|space| space - padding.vertical_axis_sum() - border.vertical_axis_sum()),
};
#[cfg(feature = "debug")]
NODE_LOGGER.labelled_debug_log("parent_size", parent_size);
#[cfg(feature = "debug")]
NODE_LOGGER.labelled_debug_log("outer_node_size", outer_node_size);
#[cfg(feature = "debug")]
NODE_LOGGER.labelled_debug_log("inner_node_size", inner_node_size);
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),
style.grid_align_content(AbstractAxis::Block),
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),
style.grid_align_content(AbstractAxis::Inline),
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());
#[cfg(feature = "debug")]
NODE_LOGGER.labelled_debug_log("initial_column_sum", initial_column_sum);
#[cfg(feature = "debug")]
NODE_LOGGER.labelled_debug_log("initial_row_sum", initial_row_sum);
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 + padding.horizontal_axis_sum() + border.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 + padding.vertical_axis_sum() + border.vertical_axis_sum())
.maybe_clamp(min_size.height, max_size.height)
.max(padding_border_size.height),
};
let container_content_box = Size {
width: container_border_box.width - padding.horizontal_axis_sum() - border.horizontal_axis_sum(),
height: container_border_box.height - padding.vertical_axis_sum() - border.vertical_axis_sum(),
};
if run_mode == RunMode::ComputeSize {
return container_border_box.into();
}
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)
.map(|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
})
.any(|has_changed| 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),
style.grid_align_content(AbstractAxis::Block),
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)
.map(|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
})
.any(|has_changed| 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),
style.grid_align_content(AbstractAxis::Inline),
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,
style.justify_content.unwrap_or(AlignContent::Stretch),
);
align_tracks(
container_content_box.get(AbstractAxis::Block),
Line { start: padding.top, end: padding.bottom },
Line { start: border.top, end: border.bottom },
&mut rows,
style.align_content.unwrap_or(AlignContent::Stretch),
);
items.sort_by_key(|item| item.source_order);
let container_alignment_styles = InBothAbsAxis { horizontal: style.justify_items, vertical: style.align_items };
for (index, item) in items.iter().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,
};
align_and_position_item(
tree,
item.node,
index as u32,
grid_area,
container_alignment_styles,
item.baseline_shim,
);
}
let mut order = items.len() as u32;
(0..tree.child_count(node)).for_each(|index| {
let child = tree.child(node, index);
let child_style = tree.style(child);
if child_style.display == Display::None {
*tree.layout_mut(child) = Layout::with_order(order);
GenericAlgorithm::perform_layout(
tree,
child,
Size::NONE,
Size::NONE,
Size::MAX_CONTENT,
SizingMode::InherentSize,
);
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),
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),
};
align_and_position_item(tree, child, order, grid_area, container_alignment_styles, 0.0);
order += 1;
}
});
if items.is_empty() {
return SizeAndBaselines { size: container_border_box, first_baselines: Point::NONE };
}
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]
};
let layout = tree.layout_mut(item.node);
layout.location.y + item.baseline.unwrap_or(layout.size.height)
};
SizeAndBaselines {
size: container_border_box,
first_baselines: Point { x: None, y: Some(grid_container_baseline) },
}
}