Preserve collapsed margins in intrinsic sizing

This commit is contained in:
SGavrl committed 2026-07-30 21:33:49 +00:00
1 parent 90c64b2c31
commit 839f1c2338
3 files changed
+160 -24

No files matched your search

+29 -20
View File
@@ -534,9 +534,35 @@ fn compute_inner(
}
}
// Margin-collapsing metadata is part of a block's intrinsic contribution,
// not only its final child placement. Grid and flex sizing can measure a
// block here and use its exposed start/end margin sets to size an ancestor
// item. Dropping them from ComputeSize makes the ancestor track shorter
// than the block's eventual descendants.
let all_in_flow_children_can_be_collapsed_through =
items.iter().all(|item| item.position == Position::Absolute || item.can_be_collapsed_through);
let can_be_collapsed_through =
!has_styles_preventing_being_collapsed_through && all_in_flow_children_can_be_collapsed_through;
let top_margin = if own_margins_collapse_with_children.start {
first_child_top_margin_set
} else {
let margin_top = raw_margin.top.resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
CollapsibleMarginSet::from_margin(margin_top)
};
let bottom_margin = if own_margins_collapse_with_children.end {
last_child_bottom_margin_set
} else {
let margin_bottom = raw_margin.bottom.resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
CollapsibleMarginSet::from_margin(margin_bottom)
};
// Short-circuit if computing size
if run_mode == RunMode::ComputeSize {
return LayoutOutput::from_outer_size(final_outer_size);
let mut output = LayoutOutput::from_outer_size(final_outer_size);
output.top_margin = top_margin;
output.bottom_margin = bottom_margin;
output.margins_can_collapse_through = can_be_collapsed_through;
return output;
}
// Commit deferred in-flow layouts to the tree. Floated items already wrote their own layouts.
@@ -577,12 +603,6 @@ fn compute_inner(
}
}
// 7. Determine whether this node can be collapsed through
let all_in_flow_children_can_be_collapsed_through =
items.iter().all(|item| item.position == Position::Absolute || item.can_be_collapsed_through);
let can_be_collapsed_through =
!has_styles_preventing_being_collapsed_through && all_in_flow_children_can_be_collapsed_through;
#[cfg_attr(not(feature = "content_size"), allow(unused_variables))]
let content_size = inflow_content_size.f32_max(absolute_content_size);
@@ -591,19 +611,8 @@ fn compute_inner(
#[cfg(feature = "content_size")]
content_size,
first_baselines: Point::NONE,
top_margin: if own_margins_collapse_with_children.start {
first_child_top_margin_set
} else {
let margin_top = raw_margin.top.resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
CollapsibleMarginSet::from_margin(margin_top)
},
bottom_margin: if own_margins_collapse_with_children.end {
last_child_bottom_margin_set
} else {
let margin_bottom =
raw_margin.bottom.resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
CollapsibleMarginSet::from_margin(margin_bottom)
},
top_margin,
bottom_margin,
margins_can_collapse_through: can_be_collapsed_through,
}
}
+47 -4
View File
@@ -4,7 +4,7 @@
use crate::geometry::Size;
use crate::style::AvailableSpace;
use crate::tree::{LayoutInput, LayoutOutput, RunMode};
use crate::tree::{CollapsibleMarginSet, LayoutInput, LayoutOutput, RunMode};
use crate::RequestedAxis;
/// The number of cache entries for each node in the tree
@@ -81,6 +81,12 @@ fn size_mixed_cache_key(kd: Size<Option<f32>>, avs: Size<AvailableSpace>) -> u64
(mixed_cache_key(kd.width, avs.width) as u64) << 32 | mixed_cache_key(kd.height, avs.height) as u64
}
#[inline(always)]
fn vertical_margin_context_key(input: &LayoutInput) -> u8 {
u8::from(input.vertical_margins_are_collapsible.start)
| (u8::from(input.vertical_margins_are_collapsible.end) << 1)
}
/// Space-optimised cache key that packs bits into as small a size as possible
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
@@ -132,6 +138,40 @@ pub(crate) struct CacheEntry<T> {
content: T,
}
/// The block-layout metadata required by a preliminary size contribution.
///
/// A compact entry avoids retaining full baselines/content-size while keeping
/// the collapsible margins that an ancestor grid or flex item must consume.
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
struct MeasureOutput {
size: Size<f32>,
top_margin: CollapsibleMarginSet,
bottom_margin: CollapsibleMarginSet,
margins_can_collapse_through: bool,
vertical_margins_are_collapsible: u8,
}
impl MeasureOutput {
fn new(input: &LayoutInput, output: LayoutOutput) -> Self {
Self {
size: output.size,
top_margin: output.top_margin,
bottom_margin: output.bottom_margin,
margins_can_collapse_through: output.margins_can_collapse_through,
vertical_margins_are_collapsible: vertical_margin_context_key(input),
}
}
fn into_layout_output(self) -> LayoutOutput {
let mut output = LayoutOutput::from_outer_size(self.size);
output.top_margin = self.top_margin;
output.bottom_margin = self.bottom_margin;
output.margins_can_collapse_through = self.margins_can_collapse_through;
output
}
}
/// A cache for caching the results of a sizing a Grid Item or Flexbox Item
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
@@ -139,7 +179,7 @@ pub struct Cache {
/// The cache entry for the node's final layout
final_layout_entry: Option<CacheEntry<LayoutOutput>>,
/// The cache entries for the node's preliminary size measurements
measure_entries: [Option<CacheEntry<Size<f32>>>; CACHE_SIZE],
measure_entries: [Option<CacheEntry<MeasureOutput>>; CACHE_SIZE],
/// Tracks if all cache entries are empty
is_empty: bool,
}
@@ -230,8 +270,10 @@ impl Cache {
for entry in self.measure_entries.iter().flatten() {
if entry.key.kd_available_space == key.kd_available_space
&& (entry.key.x_axis_parent_size() == key.x_axis_parent_size())
&& entry.content.vertical_margins_are_collapsible
== vertical_margin_context_key(input)
{
return Some(LayoutOutput::from_outer_size(entry.content));
return Some(entry.content.into_layout_output());
}
}
@@ -252,7 +294,8 @@ impl Cache {
RunMode::ComputeSize => {
self.is_empty = false;
let cache_slot = Self::compute_cache_slot(input.known_dimensions, input.available_space);
self.measure_entries[cache_slot] = Some(CacheEntry { key, content: layout_output.size });
self.measure_entries[cache_slot] =
Some(CacheEntry { key, content: MeasureOutput::new(input, layout_output) });
}
RunMode::PerformHiddenLayout => {}
}