Skip to content

Commit

Permalink
BoxSizing::ContentBox for Grid layout
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoburns committed Jul 7, 2024
1 parent 2d0b94d commit f4c853d
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 88 deletions.
16 changes: 14 additions & 2 deletions src/compute/grid/alignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::util::{MaybeMath, MaybeResolve, ResolveOrZero};

#[cfg(feature = "content_size")]
use crate::compute::common::content_size::compute_content_size_contribution;
use crate::BoxSizing;

/// Align the grid tracks within the grid according to the align-content (rows) or
/// justify-content (columns) property. This only does anything if the size of the
Expand Down Expand Up @@ -79,14 +80,25 @@ pub(super) fn align_and_position_item(
let padding = style.padding.map(|p| p.resolve_or_zero(Some(grid_area_size.width)));
let border = style.border.map(|p| p.resolve_or_zero(Some(grid_area_size.width)));
let padding_border_size = (padding + border).sum_axes();
let inherent_size = style.size.maybe_resolve(grid_area_size).maybe_apply_aspect_ratio(aspect_ratio);
let box_sizing_adjustment =
if style.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
let inherent_size = style
.size
.maybe_resolve(grid_area_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let min_size = style
.min_size
.maybe_resolve(grid_area_size)
.maybe_add(box_sizing_adjustment)
.or(padding_border_size.map(Some))
.maybe_max(padding_border_size)
.maybe_apply_aspect_ratio(aspect_ratio);
let max_size = style.max_size.maybe_resolve(grid_area_size).maybe_apply_aspect_ratio(aspect_ratio);
let max_size = style
.max_size
.maybe_resolve(grid_area_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);

// Resolve default alignment styles if they are set on neither the parent or the node itself
// Note: if the child has a preferred aspect ratio but neither width or height are set, then the width is stretched
Expand Down
35 changes: 13 additions & 22 deletions src/compute/grid/explicit_grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use num_traits::float::FloatCore;
/// Compute the number of rows and columns in the explicit grid
pub(crate) fn compute_explicit_grid_size_in_axis(
style: &Style,
preferred_size: Size<Option<f32>>,
inner_container_size: Size<Option<f32>>,
axis: AbsoluteAxis,
) -> u16 {
// Load the grid-template-rows or grid-template-columns definition (depending on the axis)
Expand Down Expand Up @@ -92,20 +92,11 @@ pub(crate) fn compute_explicit_grid_size_in_axis(
// Otherwise, if the grid container has a definite min size in the relevant axis:
// - then the number of repetitions is the smallest possible positive integer that fulfills that minimum requirement
// Otherwise, the specified track list repeats only once.
let style_size = preferred_size.get_abs(axis);
let style_min_size = style.min_size.get_abs(axis).into_option();
let style_max_size = style.max_size.get_abs(axis).into_option();

let outer_container_size = style_size.maybe_min(style_max_size).or(style_max_size).or(style_min_size);
let inner_container_size = outer_container_size.map(|size| {
let padding_sum = style.padding.resolve_or_zero(outer_container_size).grid_axis_sum(axis);
let border_sum = style.border.resolve_or_zero(outer_container_size).grid_axis_sum(axis);
size - padding_sum - border_sum
});
let size_is_maximum = style_size.is_some() || style_max_size.is_some();
let size_is_maximum =
style.size.get_abs(axis).into_option().is_some() || style.max_size.get_abs(axis).into_option().is_some();

// Determine the number of repetitions
let num_repetitions: u16 = match inner_container_size {
let num_repetitions: u16 = match inner_container_size.get_abs(axis) {
None => 1,
Some(inner_container_size) => {
let parent_size = Some(inner_container_size);
Expand Down Expand Up @@ -352,9 +343,9 @@ mod test {
grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
..Default::default()
};
let preferred_size = grid_style.size.map(|s| s.into_option());
let width = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Horizontal);
let height = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Vertical);
let inner_container_size = Size { width: Some(120.0), height: Some(80.0) };
let width = compute_explicit_grid_size_in_axis(&grid_style, inner_container_size, AbsoluteAxis::Horizontal);
let height = compute_explicit_grid_size_in_axis(&grid_style, inner_container_size, AbsoluteAxis::Vertical);
assert_eq!(width, 3);
assert_eq!(height, 4);
}
Expand All @@ -369,9 +360,9 @@ mod test {
grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
..Default::default()
};
let preferred_size = grid_style.size.map(|s| s.into_option());
let width = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Horizontal);
let height = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Vertical);
let inner_container_size = Size { width: Some(140.0), height: Some(90.0) };
let width = compute_explicit_grid_size_in_axis(&grid_style, inner_container_size, AbsoluteAxis::Horizontal);
let height = compute_explicit_grid_size_in_axis(&grid_style, inner_container_size, AbsoluteAxis::Vertical);
assert_eq!(width, 4);
assert_eq!(height, 5);
}
Expand Down Expand Up @@ -457,9 +448,9 @@ mod test {
grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
..Default::default()
};
let preferred_size = grid_style.size.map(|s| s.into_option());
let width = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Horizontal);
let height = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Vertical);
let inner_container_size = Size { width: Some(100.0), height: Some(80.0) };
let width = compute_explicit_grid_size_in_axis(&grid_style, inner_container_size, AbsoluteAxis::Horizontal);
let height = compute_explicit_grid_size_in_axis(&grid_style, inner_container_size, AbsoluteAxis::Vertical);
assert_eq!(width, 5); // 40px horizontal padding
assert_eq!(height, 4); // 20px vertical padding
}
Expand Down
152 changes: 92 additions & 60 deletions src/compute/grid/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
use crate::geometry::{AbsoluteAxis, AbstractAxis, InBothAbsAxis};
use crate::geometry::{Line, Point, Rect, Size};
use crate::style::{AlignContent, AlignItems, AlignSelf, AvailableSpace, Display, Overflow, Position};
use crate::style_helpers::*;
use crate::tree::{Layout, LayoutInput, LayoutOutput, RunMode, SizingMode};
use crate::tree::{LayoutPartialTree, LayoutPartialTreeExt, NodeId};
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::*, BoxSizing};
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;
Expand Down Expand Up @@ -38,28 +38,109 @@ mod util;
pub fn compute_grid_layout(tree: &mut impl LayoutPartialTree, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
let LayoutInput { known_dimensions, parent_size, available_space, run_mode, .. } = inputs;

let get_child_styles_iter = |node| tree.child_ids(node).map(|child_node: NodeId| tree.get_style(child_node));
let style = tree.get_style(node).clone();
let child_styles_iter = get_child_styles_iter(node);

// 1. Compute "available grid space"
// https://www.w3.org/TR/css-grid-1/#available-grid-space
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)
style
.size
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(style.aspect_ratio)
.maybe_add(box_sizing_adjustment)
} else {
Size::NONE
};

// 1. Resolve the explicit grid
// Scrollbar gutters are reserved when the `overflow` property is set to `Overflow::Scroll`.
// However, the axis are switched (transposed) because a node that scrolls vertically needs
// *horizontal* space to be reserved for a scrollbar
let scrollbar_gutter = style.overflow.transpose().map(|overflow| match overflow {
Overflow::Scroll => style.scrollbar_width,
_ => 0.0,
});
// TODO: make side configurable based on the `direction` property
let mut content_box_inset = padding_border;
content_box_inset.right += scrollbar_gutter.x;
content_box_inset.bottom += scrollbar_gutter.y;

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_style(child_node));
let child_styles_iter = get_child_styles_iter(node);

// 2. Resolve the explicit grid

// This is very similar to the inner_node_size except if the inner_node_size is not definite but the node
// has a min- or max- size style then that will be used in it's place.
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());

// Exactly compute the number of rows and columns in the explicit grid.
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 explicit_col_count =
compute_explicit_grid_size_in_axis(&style, auto_fit_container_size, AbsoluteAxis::Horizontal);
let explicit_row_count =
compute_explicit_grid_size_in_axis(&style, auto_fit_container_size, AbsoluteAxis::Vertical);

// 2. Implicit Grid: Estimate Track Counts
// 3. Implicit Grid: Estimate Track Counts
// Estimate the number of rows and columns in the implicit grid (= the entire grid)
// This is necessary as part of placement. Doing it early here is a perf optimisation to reduce allocations.
let (est_col_counts, est_row_counts) =
compute_grid_size_estimate(explicit_col_count, explicit_row_count, child_styles_iter);

// 2. Grid Item Placement
// 4. Grid Item Placement
// Match items (children) to a definite grid position (row start/end and column start/end position)
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);
Expand All @@ -82,7 +163,7 @@ pub fn compute_grid_layout(tree: &mut impl LayoutPartialTree, node: NodeId, inpu
let final_col_counts = *cell_occupancy_matrix.track_counts(AbsoluteAxis::Horizontal);
let final_row_counts = *cell_occupancy_matrix.track_counts(AbsoluteAxis::Vertical);

// 3. Initialize Tracks
// 5. Initialize Tracks
// Initialize (explicit and implicit) grid tracks (and gutters)
// This resolves the min and max track sizing functions for all tracks and gutters
let mut columns = GridTrackVec::new();
Expand All @@ -104,56 +185,7 @@ pub fn compute_grid_layout(tree: &mut impl LayoutPartialTree, node: NodeId, inpu
|row_index| cell_occupancy_matrix.row_is_occupied(row_index),
);

// 4. Compute "available grid space"
// https://www.w3.org/TR/css-grid-1/#available-grid-space
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 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;

// Scrollbar gutters are reserved when the `overflow` property is set to `Overflow::Scroll`.
// However, the axis are switched (transposed) because a node that scrolls vertically needs
// *horizontal* space to be reserved for a scrollbar
let scrollbar_gutter = style.overflow.transpose().map(|overflow| match overflow {
Overflow::Scroll => style.scrollbar_width,
_ => 0.0,
});
// TODO: make side configurable based on the `direction` property
let mut content_box_inset = padding_border;
content_box_inset.right += scrollbar_gutter.x;
content_box_inset.bottom += scrollbar_gutter.y;

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 - 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(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);

// 5. Track Sizing
// 6. Track Sizing

// Convert grid placements in origin-zero coordinates to indexes into the GridTrack (rows and columns) vectors
// This computation is relatively trivial, but it requires the final number of negative (implicit) tracks in
Expand Down
Loading

0 comments on commit f4c853d

Please sign in to comment.