Skip to content

Commit bfc2a88

Browse files
ickshonpealice-i-cecileUkoeHB
authored
Toggleable UI layout rounding (#16841)
# Objective Allow users to enable or disable layout rounding for specific UI nodes and their descendants. Fixes #16731 ## Solution New component `LayoutConfig` that can be added to any UiNode entity. Setting the `use_rounding` field of `LayoutConfig` determines if the Node and its descendants should be given rounded or unrounded coordinates. ## Testing Not tested this extensively but it seems to work and it's not very complicated. This really basic test app returns fractional coords: ```rust use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .add_systems(Update, report) .run(); } fn setup(mut commands: Commands) { commands.spawn(Camera2d); commands.spawn(( Node { left: Val::Px(0.1), width: Val::Px(100.1), height: Val::Px(100.1), ..Default::default() }, LayoutConfig { use_rounding: false }, )); } fn report(node: Query<(Ref<ComputedNode>, &GlobalTransform)>) { for (c, g) in node.iter() { if c.is_changed() { println!("{:#?}", c); println!("position = {:?}", g.to_scale_rotation_translation().2); } } } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com>
1 parent ddf4d9e commit bfc2a88

File tree

3 files changed

+62
-19
lines changed

3 files changed

+62
-19
lines changed

crates/bevy_ui/src/layout/mod.rs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{
22
experimental::{UiChildren, UiRootNodes},
3-
BorderRadius, ComputedNode, ContentSize, DefaultUiCamera, Display, Node, Outline, OverflowAxis,
4-
ScrollPosition, TargetCamera, UiScale, Val,
3+
BorderRadius, ComputedNode, ContentSize, DefaultUiCamera, Display, LayoutConfig, Node, Outline,
4+
OverflowAxis, ScrollPosition, TargetCamera, UiScale, Val,
55
};
66
use bevy_ecs::{
77
change_detection::{DetectChanges, DetectChangesMut},
@@ -120,10 +120,12 @@ pub fn ui_layout_system(
120120
&mut ComputedNode,
121121
&mut Transform,
122122
&Node,
123+
Option<&LayoutConfig>,
123124
Option<&BorderRadius>,
124125
Option<&Outline>,
125126
Option<&ScrollPosition>,
126127
)>,
128+
127129
mut buffer_query: Query<&mut ComputedTextBlock>,
128130
mut font_system: ResMut<CosmicFontSystem>,
129131
) {
@@ -294,6 +296,7 @@ with UI components as a child of an entity without UI components, your UI layout
294296
&mut commands,
295297
*root,
296298
&mut ui_surface,
299+
true,
297300
None,
298301
&mut node_transform_query,
299302
&ui_children,
@@ -312,11 +315,13 @@ with UI components as a child of an entity without UI components, your UI layout
312315
commands: &mut Commands,
313316
entity: Entity,
314317
ui_surface: &mut UiSurface,
318+
inherited_use_rounding: bool,
315319
root_size: Option<Vec2>,
316320
node_transform_query: &mut Query<(
317321
&mut ComputedNode,
318322
&mut Transform,
319323
&Node,
324+
Option<&LayoutConfig>,
320325
Option<&BorderRadius>,
321326
Option<&Outline>,
322327
Option<&ScrollPosition>,
@@ -330,12 +335,17 @@ with UI components as a child of an entity without UI components, your UI layout
330335
mut node,
331336
mut transform,
332337
style,
338+
maybe_layout_config,
333339
maybe_border_radius,
334340
maybe_outline,
335341
maybe_scroll_position,
336342
)) = node_transform_query.get_mut(entity)
337343
{
338-
let Ok((layout, unrounded_size)) = ui_surface.get_layout(entity) else {
344+
let use_rounding = maybe_layout_config
345+
.map(|layout_config| layout_config.use_rounding)
346+
.unwrap_or(inherited_use_rounding);
347+
348+
let Ok((layout, unrounded_size)) = ui_surface.get_layout(entity, use_rounding) else {
339349
return;
340350
};
341351

@@ -446,6 +456,7 @@ with UI components as a child of an entity without UI components, your UI layout
446456
commands,
447457
child_uinode,
448458
ui_surface,
459+
use_rounding,
449460
Some(viewport_size),
450461
node_transform_query,
451462
ui_children,
@@ -573,7 +584,7 @@ mod tests {
573584
let mut ui_surface = world.resource_mut::<UiSurface>();
574585

575586
for ui_entity in [ui_root, ui_child] {
576-
let layout = ui_surface.get_layout(ui_entity).unwrap().0;
587+
let layout = ui_surface.get_layout(ui_entity, true).unwrap().0;
577588
assert_eq!(layout.size.width, WINDOW_WIDTH);
578589
assert_eq!(layout.size.height, WINDOW_HEIGHT);
579590
}
@@ -962,7 +973,7 @@ mod tests {
962973
let mut ui_surface = world.resource_mut::<UiSurface>();
963974

964975
let layout = ui_surface
965-
.get_layout(ui_node_entity)
976+
.get_layout(ui_node_entity, true)
966977
.expect("failed to get layout")
967978
.0;
968979

@@ -1049,7 +1060,7 @@ mod tests {
10491060
ui_schedule.run(&mut world);
10501061

10511062
let mut ui_surface = world.resource_mut::<UiSurface>();
1052-
let layout = ui_surface.get_layout(ui_entity).unwrap().0;
1063+
let layout = ui_surface.get_layout(ui_entity, true).unwrap().0;
10531064

10541065
// the node should takes its size from the fixed size measure func
10551066
assert_eq!(layout.size.width, content_size.x);
@@ -1078,7 +1089,7 @@ mod tests {
10781089

10791090
// a node with a content size should have taffy context
10801091
assert!(ui_surface.taffy.get_node_context(ui_node).is_some());
1081-
let layout = ui_surface.get_layout(ui_entity).unwrap().0;
1092+
let layout = ui_surface.get_layout(ui_entity, true).unwrap().0;
10821093
assert_eq!(layout.size.width, content_size.x);
10831094
assert_eq!(layout.size.height, content_size.y);
10841095

@@ -1091,7 +1102,7 @@ mod tests {
10911102
assert!(ui_surface.taffy.get_node_context(ui_node).is_none());
10921103

10931104
// Without a content size, the node has no width or height constraints so the length of both dimensions is 0.
1094-
let layout = ui_surface.get_layout(ui_entity).unwrap().0;
1105+
let layout = ui_surface.get_layout(ui_entity, true).unwrap().0;
10951106
assert_eq!(layout.size.width, 0.);
10961107
assert_eq!(layout.size.height, 0.);
10971108
}

crates/bevy_ui/src/layout/ui_surface.rs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -277,23 +277,33 @@ impl UiSurface {
277277
/// Does not compute the layout geometry, `compute_window_layouts` should be run before using this function.
278278
/// On success returns a pair consisting of the final resolved layout values after rounding
279279
/// and the size of the node after layout resolution but before rounding.
280-
pub fn get_layout(&mut self, entity: Entity) -> Result<(taffy::Layout, Vec2), LayoutError> {
280+
pub fn get_layout(
281+
&mut self,
282+
entity: Entity,
283+
use_rounding: bool,
284+
) -> Result<(taffy::Layout, Vec2), LayoutError> {
281285
let Some(taffy_node) = self.entity_to_taffy.get(&entity) else {
282286
return Err(LayoutError::InvalidHierarchy);
283287
};
284288

285-
let layout = self
286-
.taffy
287-
.layout(*taffy_node)
288-
.cloned()
289-
.map_err(LayoutError::TaffyError)?;
289+
if use_rounding {
290+
self.taffy.enable_rounding();
291+
} else {
292+
self.taffy.disable_rounding();
293+
}
290294

291-
self.taffy.disable_rounding();
292-
let taffy_size = self.taffy.layout(*taffy_node).unwrap().size;
293-
let unrounded_size = Vec2::new(taffy_size.width, taffy_size.height);
294-
self.taffy.enable_rounding();
295+
let out = match self.taffy.layout(*taffy_node).cloned() {
296+
Ok(layout) => {
297+
self.taffy.disable_rounding();
298+
let taffy_size = self.taffy.layout(*taffy_node).unwrap().size;
299+
let unrounded_size = Vec2::new(taffy_size.width, taffy_size.height);
300+
Ok((layout, unrounded_size))
301+
}
302+
Err(taffy_error) => Err(LayoutError::TaffyError(taffy_error)),
303+
};
295304

296-
Ok((layout, unrounded_size))
305+
self.taffy.enable_rounding();
306+
out
297307
}
298308
}
299309

crates/bevy_ui/src/ui_node.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2550,6 +2550,28 @@ impl Default for ShadowStyle {
25502550
}
25512551
}
25522552

2553+
#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]
2554+
#[reflect(Component, Debug, PartialEq, Default)]
2555+
#[cfg_attr(
2556+
feature = "serialize",
2557+
derive(serde::Serialize, serde::Deserialize),
2558+
reflect(Serialize, Deserialize)
2559+
)]
2560+
/// This component can be added to any UI node to modify its layout behavior.
2561+
pub struct LayoutConfig {
2562+
/// If set to true the coordinates for this node and its descendents will be rounded to the nearest physical pixel.
2563+
/// This can help prevent visual artifacts like blurry images or semi-transparent edges that can occur with sub-pixel positioning.
2564+
///
2565+
/// Defaults to true.
2566+
pub use_rounding: bool,
2567+
}
2568+
2569+
impl Default for LayoutConfig {
2570+
fn default() -> Self {
2571+
Self { use_rounding: true }
2572+
}
2573+
}
2574+
25532575
#[cfg(test)]
25542576
mod tests {
25552577
use crate::GridPlacement;

0 commit comments

Comments
 (0)