🐛 Fix layout render-wasm issues

This commit is contained in:
alonso.torres 2026-05-29 11:53:24 +02:00 committed by Aitor Moreno
parent d3148e1a10
commit dddb4cf0b6
3 changed files with 62 additions and 18 deletions

View File

@ -29,6 +29,8 @@ fn propagate_children(
) -> Result<VecDeque<Modifier>> {
let mut result = VecDeque::new();
// We use the identity transform as a mark that a reflow is needed.
// It's needed to be propagated to its children.
if identitish(&transform) {
for child_id in shape.children_ids_iter(true) {
result.push_back(Modifier::transform_propagate(*child_id, transform));
@ -180,6 +182,7 @@ fn propagate_transform(
modifiers: &mut HashMap<Uuid, Matrix>,
reflown: &mut HashSet<Uuid>,
reflowed_shapes: &mut HashSet<Uuid>,
pending_reflows: &mut HashSet<Uuid>,
) -> Result<()> {
let Some(shape) = state.shapes.get(&entry.id) else {
return Ok(());
@ -237,13 +240,29 @@ fn propagate_transform(
transform.post_concat(&resize_transform);
}
GrowType::AutoWidth => {
let width_before = text_content.width();
let height_before = text_content.size.height;
let (new_width, new_height) = if height_changed {
let mut clone = text_content.clone();
clone.update_layout(resized_selrect);
(clone.width(), clone.size.height)
} else {
(text_content.width(), text_content.size.height)
(width_before, height_before)
};
if (!is_close_to(width_before, new_width)
|| !is_close_to(height_before, new_height))
&& reflowed_shapes.insert(shape.id)
{
entries.push_back(Modifier::reflow(shape.id, false));
if let Some(parent_id) = shape.parent_id {
for pid in
shapes::all_with_ancestors(&[parent_id], shapes, false).iter()
{
reflown.remove(pid);
}
}
}
let resize_transform = math::resize_matrix(
&shape_bounds_after,
&shape_bounds_after,
@ -284,7 +303,7 @@ fn propagate_transform(
let is_propagate = entry.source == TransformEntrySource::Propagate;
// If this is a layout and we're only moving don't need to reflow
if shape.has_layout() && is_resize {
if shape.has_layout() && is_resize && pending_reflows.insert(shape.id) {
entries.push_back(Modifier::reflow(shape.id, false));
}
@ -292,13 +311,17 @@ fn propagate_transform(
// When the parent is either a group or a layout we only mark for reflow
// if the current transformation is not a move propagation.
// If it's a move propagation we don't need to reflow, the parent is already changed.
if (parent.has_layout() || parent.is_group_like()) && (is_resize || !is_propagate) {
if (parent.has_layout() || parent.is_group_like())
&& (is_resize || !is_propagate)
&& pending_reflows.insert(parent.id)
{
entries.push_back(Modifier::reflow(parent.id, false));
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn propagate_reflow(
id: &Uuid,
state: &State,
@ -307,6 +330,7 @@ fn propagate_reflow(
layout_reflows: &mut HashSet<Uuid>,
reflown: &mut HashSet<Uuid>,
modifiers: &HashMap<Uuid, Matrix>,
pending_reflows: &mut HashSet<Uuid>,
) {
let Some(shape) = state.shapes.get(id) else {
return;
@ -326,7 +350,7 @@ fn propagate_reflow(
}
Type::Group(Group { masked: true }) => {
let children_ids = shape.children_ids(true);
if let Some(child) = shapes.get(&children_ids[0]) {
if let Some(child) = children_ids.first().and_then(|id| shapes.get(id)) {
let child_bounds = bounds.find(child);
bounds.insert(shape.id, child_bounds);
}
@ -348,7 +372,7 @@ fn propagate_reflow(
}
if let Some(parent) = shape.parent_id.and_then(|id| shapes.get(&id)) {
if parent.has_layout() || parent.is_group_like() {
if (parent.has_layout() || parent.is_group_like()) && pending_reflows.insert(parent.id) {
entries.push_back(Modifier::reflow(parent.id, false));
}
}
@ -406,6 +430,14 @@ pub fn propagate_modifiers(
let mut bounds = HashMap::<Uuid, Bounds>::new();
let mut reflown = HashSet::<Uuid>::new();
let mut layout_reflows = HashSet::<Uuid>::new();
// Tracks text shapes that have already triggered a reflow across all outer
// iterations, preventing oscillation when a parent layout re-emits a
// transform for the same text shape in a later pass.
let mut reflowed_shapes = HashSet::<Uuid>::new();
// Tracks reflow ids already queued to avoid flooding entries with
// duplicate Reflow entries when many children of the same parent
// are transformed in the same pass.
let mut pending_reflows = HashSet::<Uuid>::new();
// We first propagate the transforms to the children and then after
// recalculate the layouts. The layout can create further transforms that
@ -413,7 +445,6 @@ pub fn propagate_modifiers(
// In order for loop to eventualy finish, we limit the flex reflow to just
// one (the reflown set).
while !entries.is_empty() {
let mut reflowed_shapes = HashSet::<Uuid>::new();
while let Some(modifier) = entries.pop_front() {
match modifier {
Modifier::Transform(entry, pixel) => propagate_transform(
@ -425,8 +456,10 @@ pub fn propagate_modifiers(
&mut modifiers,
&mut reflown,
&mut reflowed_shapes,
&mut pending_reflows,
)?,
Modifier::Reflow(id, force_reflow) => {
pending_reflows.remove(&id);
if force_reflow {
reflown.remove(&id);
}
@ -439,6 +472,7 @@ pub fn propagate_modifiers(
&mut layout_reflows,
&mut reflown,
&modifiers,
&mut pending_reflows,
)
}
}
@ -465,7 +499,6 @@ pub fn propagate_modifiers(
}
reflow_shape(id, state, &mut reflown, &mut entries, &mut bounds_temp)?;
}
layout_reflows = HashSet::new();
}
// #[allow(dead_code)]

View File

@ -54,6 +54,7 @@ struct LayoutAxis {
gap_across: f32,
is_auto_main: bool,
is_auto_across: bool,
is_wrap: bool,
}
impl LayoutAxis {
@ -78,6 +79,7 @@ impl LayoutAxis {
gap_across: layout_data.row_gap,
is_auto_main: num_child > 0 && shape.is_layout_horizontal_auto(),
is_auto_across: num_child > 0 && shape.is_layout_vertical_auto(),
is_wrap: flex_data.is_wrap(),
}
} else {
Self {
@ -93,6 +95,7 @@ impl LayoutAxis {
gap_across: layout_data.column_gap,
is_auto_main: num_child > 0 && shape.is_layout_vertical_auto(),
is_auto_across: num_child > 0 && shape.is_layout_horizontal_auto(),
is_wrap: flex_data.is_wrap(),
}
}
}
@ -399,7 +402,7 @@ fn calculate_track_positions(
) {
let mut align_content = &layout_data.align_content;
if layout_axis.is_auto_across {
if layout_axis.is_auto_across || !layout_axis.is_wrap {
align_content = &AlignContent::Start;
}
@ -427,7 +430,10 @@ fn calculate_track_positions(
AlignContent::SpaceAround => {
let effective_gap = (layout_axis.across_space() - total_across_size) / tlen as f32;
(effective_gap / 2.0, effective_gap)
(
layout_axis.padding_across_start + effective_gap / 2.0,
effective_gap,
)
}
AlignContent::SpaceEvenly => {
@ -473,7 +479,9 @@ fn calculate_track_data(
let total_across_size = tracks.iter().map(|t| t.across_size).sum::<f32>();
if !layout_axis.is_auto_across && layout_data.align_content == AlignContent::Stretch {
let stretch_tracks = !layout_axis.is_wrap || layout_data.align_content == AlignContent::Stretch;
if !layout_axis.is_auto_across && stretch_tracks {
stretch_tracks_sizes(&layout_axis, &mut tracks, total_across_size);
}
@ -506,12 +514,12 @@ fn first_anchor(
}
JustifyContent::SpaceAround => {
let effective_gap = (layout_axis.main_space() - total_shapes_size) / slen as f32;
layout_axis.padding_main_end + f32::max(layout_axis.gap_main, effective_gap / 2.0)
layout_axis.padding_main_start + f32::max(layout_axis.gap_main, effective_gap / 2.0)
}
JustifyContent::SpaceEvenly => {
let effective_gap =
(layout_axis.main_space() - total_shapes_size) / (track.shapes.len() + 1) as f32;
layout_axis.padding_main_end + f32::max(layout_axis.gap_main, effective_gap)
layout_axis.padding_main_start + f32::max(layout_axis.gap_main, effective_gap)
}
_ => layout_axis.padding_main_start,
};
@ -538,8 +546,11 @@ fn next_anchor(
+ child_axis.margin_main_end
+ match layout_data.justify_content {
JustifyContent::SpaceBetween => {
let effective_gap = (layout_axis.main_space() - total_shapes_size)
/ (track.shapes.len() - 1) as f32;
let effective_gap = if track.shapes.len() > 1 {
(layout_axis.main_space() - total_shapes_size) / (track.shapes.len() - 1) as f32
} else {
0.0
};
child_axis.main_size + f32::max(layout_axis.gap_main, effective_gap)
}
JustifyContent::SpaceAround => {

View File

@ -193,7 +193,7 @@ fn set_auto_multi_span(
// Sort descendant order of prop-span
selected_cells.sort_by(|a, b| {
if column {
b.column_span.cmp(&a.row_span)
b.column_span.cmp(&a.column_span)
} else {
b.row_span.cmp(&a.row_span)
}
@ -268,7 +268,7 @@ fn set_flex_multi_span(
// Sort descendant order of prop-span
selected_cells.sort_by(|a, b| {
if column {
b.column_span.cmp(&a.row_span)
b.column_span.cmp(&a.column_span)
} else {
b.row_span.cmp(&a.row_span)
}
@ -901,7 +901,7 @@ pub fn reflow_grid_layout(
let auto_width = column_tracks.iter().map(|t| t.size).sum::<f32>()
+ layout_data.padding_left
+ layout_data.padding_right
+ (column_tracks.len() - 1) as f32 * layout_data.column_gap;
+ column_tracks.len().saturating_sub(1) as f32 * layout_data.column_gap;
scale_width = auto_width / width;
}
@ -909,7 +909,7 @@ pub fn reflow_grid_layout(
let auto_height = row_tracks.iter().map(|t| t.size).sum::<f32>()
+ layout_data.padding_top
+ layout_data.padding_bottom
+ (row_tracks.len() - 1) as f32 * layout_data.row_gap;
+ row_tracks.len().saturating_sub(1) as f32 * layout_data.row_gap;
scale_height = auto_height / height;
}