diff --git a/src/compute/grid/alignment.rs b/src/compute/grid/alignment.rs index b9a701ffa..3cec376d6 100644 --- a/src/compute/grid/alignment.rs +++ b/src/compute/grid/alignment.rs @@ -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 @@ -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 diff --git a/src/compute/grid/explicit_grid.rs b/src/compute/grid/explicit_grid.rs index 76ff737bf..dbc450627 100644 --- a/src/compute/grid/explicit_grid.rs +++ b/src/compute/grid/explicit_grid.rs @@ -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>, + inner_container_size: Size>, axis: AbsoluteAxis, ) -> u16 { // Load the grid-template-rows or grid-template-columns definition (depending on the axis) @@ -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); @@ -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); } @@ -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); } @@ -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 } diff --git a/src/compute/grid/mod.rs b/src/compute/grid/mod.rs index 656613d34..2fc02e5bf 100644 --- a/src/compute/grid/mod.rs +++ b/src/compute/grid/mod.rs @@ -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; @@ -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); @@ -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(); @@ -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 diff --git a/src/compute/grid/types/grid_item.rs b/src/compute/grid/types/grid_item.rs index 8c33798a6..b77f7de02 100644 --- a/src/compute/grid/types/grid_item.rs +++ b/src/compute/grid/types/grid_item.rs @@ -9,6 +9,7 @@ use crate::style::{ }; use crate::tree::{LayoutPartialTree, LayoutPartialTreeExt, NodeId, SizingMode}; use crate::util::{MaybeMath, MaybeResolve, ResolveOrZero}; +use crate::{BoxSizing, LengthPercentage}; use core::ops::Range; /// Represents a single grid item @@ -32,6 +33,8 @@ pub(in super::super) struct GridItem { /// The item's overflow style pub overflow: Point, + /// The item's box_sizing style + pub box_sizing: BoxSizing, /// The item's size style pub size: Size, /// The item's min_size style @@ -40,6 +43,10 @@ pub(in super::super) struct GridItem { pub max_size: Size, /// The item's aspect_ratio style pub aspect_ratio: Option, + /// The item's padding style + pub padding: Rect, + /// The item's border style + pub border: Rect, /// The item's margin style pub margin: Rect, /// The item's align_self property, or the parent's align_items property is not set @@ -101,10 +108,13 @@ impl GridItem { row: row_span, column: col_span, overflow: style.overflow, + box_sizing: style.box_sizing, size: style.size, min_size: style.min_size, max_size: style.max_size, aspect_ratio: style.aspect_ratio, + padding: style.padding, + border: style.border, margin: style.margin, align_self: style.align_self.unwrap_or(parent_align_items), justify_self: style.justify_self.unwrap_or(parent_justify_items), @@ -232,9 +242,26 @@ impl GridItem { let margins = self.margins_axis_sums_with_baseline_shims(inner_node_size.width); let aspect_ratio = self.aspect_ratio; - let inherent_size = self.size.maybe_resolve(grid_area_size).maybe_apply_aspect_ratio(aspect_ratio); - let min_size = self.min_size.maybe_resolve(grid_area_size).maybe_apply_aspect_ratio(aspect_ratio); - let max_size = self.max_size.maybe_resolve(grid_area_size).maybe_apply_aspect_ratio(aspect_ratio); + let padding = self.padding.resolve_or_zero(grid_area_size); + let border = self.border.resolve_or_zero(grid_area_size); + let padding_border_size = (padding + border).sum_axes(); + let box_sizing_adjustment = + if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO }; + let inherent_size = self + .size + .maybe_resolve(grid_area_size) + .maybe_apply_aspect_ratio(aspect_ratio) + .maybe_add(box_sizing_adjustment); + let min_size = self + .min_size + .maybe_resolve(grid_area_size) + .maybe_apply_aspect_ratio(aspect_ratio) + .maybe_add(box_sizing_adjustment); + let max_size = self + .max_size + .maybe_resolve(grid_area_size) + .maybe_apply_aspect_ratio(aspect_ratio) + .maybe_add(box_sizing_adjustment); let grid_area_minus_item_margins_size = grid_area_size.maybe_sub(margins); @@ -430,13 +457,23 @@ impl GridItem { known_dimensions: Size>, inner_node_size: Size>, ) -> f32 { + let padding = self.padding.resolve_or_zero(inner_node_size); + let border = self.border.resolve_or_zero(inner_node_size); + let padding_border_size = (padding + border).sum_axes(); + let box_sizing_adjustment = + if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO }; let size = self .size .maybe_resolve(inner_node_size) .maybe_apply_aspect_ratio(self.aspect_ratio) + .maybe_add(box_sizing_adjustment) .get(axis) .or_else(|| { - self.min_size.maybe_resolve(inner_node_size).maybe_apply_aspect_ratio(self.aspect_ratio).get(axis) + self.min_size + .maybe_resolve(inner_node_size) + .maybe_apply_aspect_ratio(self.aspect_ratio) + .maybe_add(box_sizing_adjustment) + .get(axis) }) .or_else(|| self.overflow.get(axis).maybe_into_automatic_min_size()) .unwrap_or_else(|| {