Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions crates/bevy_ui/src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2934,4 +2934,42 @@ mod tests {
.abs_diff_eq(Vec2::new(50., 30.), 1e-5));
}
}

#[test]
fn rounding_test() {
// Regression test to ensure that updating layout properties (like width)
// and computing layouts with/without rounding works correctly
// without corrupting Taffy state or the calculated size.
let mut app = setup_ui_test_app();

let root = app
.world_mut()
.spawn(Node {
width: px(100.),
height: px(100.),
..default()
})
.with_child((
Node::default(),
LayoutConfig {
use_rounding: false,
},
))
.id();

app.update();

assert_eq!(
app.world().get::<ComputedNode>(root).unwrap().size(),
Vec2::splat(100.)
);

app.world_mut().get_mut::<Node>(root).unwrap().width = px(200.);
app.update();

assert_eq!(
app.world().get::<ComputedNode>(root).unwrap().size(),
Vec2::new(200., 100.)
);
}
}
28 changes: 17 additions & 11 deletions crates/bevy_ui/src/layout/ui_surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ impl UiSurface {
) {
let implicit_viewport_node = self.get_or_insert_taffy_viewport_node(ui_root_entity);

// Ensure rounding is enabled globally before computing the layout tree,
// as enable/disable_rounding controls whether rounded node geometry is created during layout.
self.taffy.enable_rounding();

let available_space = taffy::geometry::Size {
width: taffy::style::AvailableSpace::Definite(render_target_resolution.x as f32),
height: taffy::style::AvailableSpace::Definite(render_target_resolution.y as f32),
Expand Down Expand Up @@ -315,24 +319,26 @@ impl UiSurface {
return Err(UiSurfaceError::NoAssociatedTaffyNode);
};

// Note: Taffy's enable/disable_rounding has a dual purpose:
// 1. It controls whether rounded geometry is generated during compute_layout.
// 2. It controls whether the per-node layout() getter returns rounded or unrounded geometry.
// Here we temporarily toggle it to fetch the unrounded size regardless of the global state.
if use_rounding {
self.taffy.enable_rounding();
} else {
self.taffy.disable_rounding();
}

let out = match self.taffy.layout(taffy_node.id).cloned() {
Ok(layout) => {
self.taffy.disable_rounding();
let taffy_size = self.taffy.layout(taffy_node.id).unwrap().size;
let unrounded_size = Vec2::new(taffy_size.width, taffy_size.height);
Ok((layout, unrounded_size))
}
Err(taffy_error) => Err(UiSurfaceError::TaffyError(taffy_error)),
};
let layout = self.taffy.layout(taffy_node.id).cloned();

self.taffy.enable_rounding();
out
self.taffy.disable_rounding();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a comment; why are we temporarily disabling rounding?

@ickshonpe ickshonpe Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taffy's API is a bit confusing here, which is why this bug crept in. enable/disable_rounding does two things. It enables or disables whether the rounded final_layout is updated during layout. But also after the layout is generated it controls whether the per node layout getter function returns rounded or unrounded geometry:

pub fn layout(&self, node: NodeId) -> TaffyResult<&Layout> {
        if self.config.use_rounding {
            Ok(&self.nodes[node.into()].final_layout)
        } else {
            Ok(&self.nodes[node.into()].unrounded_layout)
        }
    }

@jorgeandrecastro jorgeandrecastro Oct 4, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a comment; why are we temporarily disabling rounding?

Hello alice-i-cecille 😅 thanks for the suggestion I've added a comment to document this behavior as requested, well why we need to temporarily disable rounding? It's because Taffy's enable/disable_rounding setting controls both how geometry is generated and what the per-node layout getter returns. We temporarily disable it to fetch the unrounded size, and then restore the caller's original use_rounding preference.

I found this "issue " , cause a was working on another one before so i saw this and i pull request, Hope its helps bevy engine 😀

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taffy's API is a bit confusing here, which is why this bug crept in. enable/disable_rounding does two things. It enables or disables whether rounded node geometry is created during layout. But also after the layout is generated it controls whether the per node layout getter function returns rounded or unrounded geometry:

pub fn layout(&self, node: NodeId) -> TaffyResult<&Layout> {
        if self.config.use_rounding {
            Ok(&self.nodes[node.into()].final_layout)
        } else {
            Ok(&self.nodes[node.into()].unrounded_layout)
        }
    }

Hello ickshonpe 😀Thank you for the detailed breakdown! That explains why the API behaves that way, and it makes complete sense why we need to explicitly restore the rounding state after getting the unrounded size.

let taffy_size = self.taffy.layout(taffy_node.id).unwrap().size;
let unrounded_size = Vec2::new(taffy_size.width, taffy_size.height);

match layout {
Ok(l) => Ok((l, unrounded_size)),
Err(taffy_error) => Err(UiSurfaceError::TaffyError(taffy_error)),
}
}

/// Returns the number of children belonging to the entity's associated taffy node.
Expand Down
Loading