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>
This commit is contained in:
parent
ddf4d9ea93
commit
bfc2a88f94
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
experimental::{UiChildren, UiRootNodes},
|
||||
BorderRadius, ComputedNode, ContentSize, DefaultUiCamera, Display, Node, Outline, OverflowAxis,
|
||||
ScrollPosition, TargetCamera, UiScale, Val,
|
||||
BorderRadius, ComputedNode, ContentSize, DefaultUiCamera, Display, LayoutConfig, Node, Outline,
|
||||
OverflowAxis, ScrollPosition, TargetCamera, UiScale, Val,
|
||||
};
|
||||
use bevy_ecs::{
|
||||
change_detection::{DetectChanges, DetectChangesMut},
|
||||
@ -120,10 +120,12 @@ pub fn ui_layout_system(
|
||||
&mut ComputedNode,
|
||||
&mut Transform,
|
||||
&Node,
|
||||
Option<&LayoutConfig>,
|
||||
Option<&BorderRadius>,
|
||||
Option<&Outline>,
|
||||
Option<&ScrollPosition>,
|
||||
)>,
|
||||
|
||||
mut buffer_query: Query<&mut ComputedTextBlock>,
|
||||
mut font_system: ResMut<CosmicFontSystem>,
|
||||
) {
|
||||
@ -294,6 +296,7 @@ with UI components as a child of an entity without UI components, your UI layout
|
||||
&mut commands,
|
||||
*root,
|
||||
&mut ui_surface,
|
||||
true,
|
||||
None,
|
||||
&mut node_transform_query,
|
||||
&ui_children,
|
||||
@ -312,11 +315,13 @@ with UI components as a child of an entity without UI components, your UI layout
|
||||
commands: &mut Commands,
|
||||
entity: Entity,
|
||||
ui_surface: &mut UiSurface,
|
||||
inherited_use_rounding: bool,
|
||||
root_size: Option<Vec2>,
|
||||
node_transform_query: &mut Query<(
|
||||
&mut ComputedNode,
|
||||
&mut Transform,
|
||||
&Node,
|
||||
Option<&LayoutConfig>,
|
||||
Option<&BorderRadius>,
|
||||
Option<&Outline>,
|
||||
Option<&ScrollPosition>,
|
||||
@ -330,12 +335,17 @@ with UI components as a child of an entity without UI components, your UI layout
|
||||
mut node,
|
||||
mut transform,
|
||||
style,
|
||||
maybe_layout_config,
|
||||
maybe_border_radius,
|
||||
maybe_outline,
|
||||
maybe_scroll_position,
|
||||
)) = node_transform_query.get_mut(entity)
|
||||
{
|
||||
let Ok((layout, unrounded_size)) = ui_surface.get_layout(entity) else {
|
||||
let use_rounding = maybe_layout_config
|
||||
.map(|layout_config| layout_config.use_rounding)
|
||||
.unwrap_or(inherited_use_rounding);
|
||||
|
||||
let Ok((layout, unrounded_size)) = ui_surface.get_layout(entity, use_rounding) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@ -446,6 +456,7 @@ with UI components as a child of an entity without UI components, your UI layout
|
||||
commands,
|
||||
child_uinode,
|
||||
ui_surface,
|
||||
use_rounding,
|
||||
Some(viewport_size),
|
||||
node_transform_query,
|
||||
ui_children,
|
||||
@ -573,7 +584,7 @@ mod tests {
|
||||
let mut ui_surface = world.resource_mut::<UiSurface>();
|
||||
|
||||
for ui_entity in [ui_root, ui_child] {
|
||||
let layout = ui_surface.get_layout(ui_entity).unwrap().0;
|
||||
let layout = ui_surface.get_layout(ui_entity, true).unwrap().0;
|
||||
assert_eq!(layout.size.width, WINDOW_WIDTH);
|
||||
assert_eq!(layout.size.height, WINDOW_HEIGHT);
|
||||
}
|
||||
@ -962,7 +973,7 @@ mod tests {
|
||||
let mut ui_surface = world.resource_mut::<UiSurface>();
|
||||
|
||||
let layout = ui_surface
|
||||
.get_layout(ui_node_entity)
|
||||
.get_layout(ui_node_entity, true)
|
||||
.expect("failed to get layout")
|
||||
.0;
|
||||
|
||||
@ -1049,7 +1060,7 @@ mod tests {
|
||||
ui_schedule.run(&mut world);
|
||||
|
||||
let mut ui_surface = world.resource_mut::<UiSurface>();
|
||||
let layout = ui_surface.get_layout(ui_entity).unwrap().0;
|
||||
let layout = ui_surface.get_layout(ui_entity, true).unwrap().0;
|
||||
|
||||
// the node should takes its size from the fixed size measure func
|
||||
assert_eq!(layout.size.width, content_size.x);
|
||||
@ -1078,7 +1089,7 @@ mod tests {
|
||||
|
||||
// a node with a content size should have taffy context
|
||||
assert!(ui_surface.taffy.get_node_context(ui_node).is_some());
|
||||
let layout = ui_surface.get_layout(ui_entity).unwrap().0;
|
||||
let layout = ui_surface.get_layout(ui_entity, true).unwrap().0;
|
||||
assert_eq!(layout.size.width, content_size.x);
|
||||
assert_eq!(layout.size.height, content_size.y);
|
||||
|
||||
@ -1091,7 +1102,7 @@ mod tests {
|
||||
assert!(ui_surface.taffy.get_node_context(ui_node).is_none());
|
||||
|
||||
// Without a content size, the node has no width or height constraints so the length of both dimensions is 0.
|
||||
let layout = ui_surface.get_layout(ui_entity).unwrap().0;
|
||||
let layout = ui_surface.get_layout(ui_entity, true).unwrap().0;
|
||||
assert_eq!(layout.size.width, 0.);
|
||||
assert_eq!(layout.size.height, 0.);
|
||||
}
|
||||
|
@ -277,23 +277,33 @@ impl UiSurface {
|
||||
/// Does not compute the layout geometry, `compute_window_layouts` should be run before using this function.
|
||||
/// On success returns a pair consisting of the final resolved layout values after rounding
|
||||
/// and the size of the node after layout resolution but before rounding.
|
||||
pub fn get_layout(&mut self, entity: Entity) -> Result<(taffy::Layout, Vec2), LayoutError> {
|
||||
pub fn get_layout(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
use_rounding: bool,
|
||||
) -> Result<(taffy::Layout, Vec2), LayoutError> {
|
||||
let Some(taffy_node) = self.entity_to_taffy.get(&entity) else {
|
||||
return Err(LayoutError::InvalidHierarchy);
|
||||
};
|
||||
|
||||
let layout = self
|
||||
.taffy
|
||||
.layout(*taffy_node)
|
||||
.cloned()
|
||||
.map_err(LayoutError::TaffyError)?;
|
||||
if use_rounding {
|
||||
self.taffy.enable_rounding();
|
||||
} else {
|
||||
self.taffy.disable_rounding();
|
||||
}
|
||||
|
||||
let out = match self.taffy.layout(*taffy_node).cloned() {
|
||||
Ok(layout) => {
|
||||
self.taffy.disable_rounding();
|
||||
let taffy_size = self.taffy.layout(*taffy_node).unwrap().size;
|
||||
let unrounded_size = Vec2::new(taffy_size.width, taffy_size.height);
|
||||
Ok((layout, unrounded_size))
|
||||
}
|
||||
Err(taffy_error) => Err(LayoutError::TaffyError(taffy_error)),
|
||||
};
|
||||
|
||||
self.taffy.disable_rounding();
|
||||
let taffy_size = self.taffy.layout(*taffy_node).unwrap().size;
|
||||
let unrounded_size = Vec2::new(taffy_size.width, taffy_size.height);
|
||||
self.taffy.enable_rounding();
|
||||
|
||||
Ok((layout, unrounded_size))
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2550,6 +2550,28 @@ impl Default for ShadowStyle {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]
|
||||
#[reflect(Component, Debug, PartialEq, Default)]
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
derive(serde::Serialize, serde::Deserialize),
|
||||
reflect(Serialize, Deserialize)
|
||||
)]
|
||||
/// This component can be added to any UI node to modify its layout behavior.
|
||||
pub struct LayoutConfig {
|
||||
/// If set to true the coordinates for this node and its descendents will be rounded to the nearest physical pixel.
|
||||
/// This can help prevent visual artifacts like blurry images or semi-transparent edges that can occur with sub-pixel positioning.
|
||||
///
|
||||
/// Defaults to true.
|
||||
pub use_rounding: bool,
|
||||
}
|
||||
|
||||
impl Default for LayoutConfig {
|
||||
fn default() -> Self {
|
||||
Self { use_rounding: true }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::GridPlacement;
|
||||
|
Loading…
Reference in New Issue
Block a user