Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove custom rounding #16097

Merged
merged 19 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e2b1cb7
Enable taffy's builtin layout coords rounding
ickshonpe Oct 23, 2024
275a06b
Removed the coordinate rounding from `ui_layout_system`.
ickshonpe Oct 23, 2024
4567842
Merge branch 'main' into remove-custom-rounding
ickshonpe Oct 25, 2024
5c7ca68
Changed the initial scaling in the test before the while loop from `1…
ickshonpe Oct 25, 2024
536b784
Update crates/bevy_ui/src/layout/mod.rs
ickshonpe Oct 25, 2024
22fb21f
Merge branch 'main' into remove-custom-rounding
ickshonpe Oct 25, 2024
e48b4d8
Modified `ui_rounding_text` so it passes, accuracy not so important w…
ickshonpe Oct 27, 2024
2d1c9e5
Merge branch 'main' into remove-custom-rounding
ickshonpe Oct 27, 2024
e0c651a
Merge branch 'main' into remove-custom-rounding
ickshonpe Oct 27, 2024
b9985fb
Merge branch 'main' into remove-custom-rounding
ickshonpe Oct 27, 2024
5766f43
Clean up, removed `unrounded_size` field from `Node`
ickshonpe Oct 27, 2024
7b96e5c
Removed too many brackets
ickshonpe Oct 27, 2024
9d80d38
Brought back unrounded sizes
ickshonpe Oct 27, 2024
321f3a6
Merge branch 'main' into remove-custom-rounding
ickshonpe Oct 27, 2024
faf5965
Removed the no longer necessary `absolute_location` parameter from `u…
ickshonpe Oct 27, 2024
b635211
Merge branch 'remove-custom-rounding' of https://github.com/ickshonpe…
ickshonpe Oct 27, 2024
d58f037
Comments and a few renamings of local variables for clarity.
ickshonpe Oct 28, 2024
f2d11a8
Merge branch 'main' into remove-custom-rounding
ickshonpe Oct 28, 2024
cab2a2a
Merge branch 'main' into remove-custom-rounding
ickshonpe Oct 28, 2024
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
103 changes: 32 additions & 71 deletions crates/bevy_ui/src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,14 +296,13 @@ with UI components as a child of an entity without UI components, your UI layout
update_uinode_geometry_recursive(
&mut commands,
*root,
&ui_surface,
&mut ui_surface,
None,
&mut node_transform_query,
&ui_children,
inverse_target_scale_factor,
Vec2::ZERO,
Vec2::ZERO,
Vec2::ZERO,
);
}

Expand All @@ -315,7 +314,7 @@ with UI components as a child of an entity without UI components, your UI layout
fn update_uinode_geometry_recursive(
commands: &mut Commands,
entity: Entity,
ui_surface: &UiSurface,
ui_surface: &mut UiSurface,
root_size: Option<Vec2>,
node_transform_query: &mut Query<(
&mut ComputedNode,
Expand All @@ -329,7 +328,6 @@ with UI components as a child of an entity without UI components, your UI layout
inverse_target_scale_factor: f32,
parent_size: Vec2,
parent_scroll_position: Vec2,
mut absolute_location: Vec2,
) {
if let Ok((
mut node,
Expand All @@ -340,28 +338,24 @@ with UI components as a child of an entity without UI components, your UI layout
maybe_scroll_position,
)) = node_transform_query.get_mut(entity)
{
let Ok(layout) = ui_surface.get_layout(entity) else {
let Ok((layout, unrounded_unscaled_size)) = ui_surface.get_layout(entity) else {
return;
};

let layout_size =
inverse_target_scale_factor * Vec2::new(layout.size.width, layout.size.height);
let unrounded_size = inverse_target_scale_factor * unrounded_unscaled_size;
let layout_location =
inverse_target_scale_factor * Vec2::new(layout.location.x, layout.location.y);

absolute_location += layout_location;

let rounded_size = approx_round_layout_coords(absolute_location + layout_size)
- approx_round_layout_coords(absolute_location);

let rounded_location =
approx_round_layout_coords(layout_location - parent_scroll_position)
+ 0.5 * (rounded_size - parent_size);
// The position of the center of the node, stored in the node's transform
let node_center =
layout_location - parent_scroll_position + 0.5 * (layout_size - parent_size);

// only trigger change detection when the new values are different
if node.size != rounded_size || node.unrounded_size != layout_size {
node.size = rounded_size;
node.unrounded_size = layout_size;
if node.size != layout_size || node.unrounded_size != unrounded_size {
node.size = layout_size;
node.unrounded_size = unrounded_size;
}

let taffy_rect_to_border_rect = |rect: taffy::Rect<f32>| BorderRect {
Expand Down Expand Up @@ -402,8 +396,8 @@ with UI components as a child of an entity without UI components, your UI layout
.max(0.);
}

if transform.translation.truncate() != rounded_location {
transform.translation = rounded_location.extend(0.);
if transform.translation.truncate() != node_center {
transform.translation = node_center.extend(0.);
}

let scroll_position: Vec2 = maybe_scroll_position
Expand All @@ -423,11 +417,9 @@ with UI components as a child of an entity without UI components, your UI layout
})
.unwrap_or_default();

let round_content_size = approx_round_layout_coords(
Vec2::new(layout.content_size.width, layout.content_size.height)
* inverse_target_scale_factor,
);
let max_possible_offset = (round_content_size - rounded_size).max(Vec2::ZERO);
let content_size = Vec2::new(layout.content_size.width, layout.content_size.height)
* inverse_target_scale_factor;
let max_possible_offset = (content_size - layout_size).max(Vec2::ZERO);
let clamped_scroll_position = scroll_position.clamp(Vec2::ZERO, max_possible_offset);

if clamped_scroll_position != scroll_position {
Expand All @@ -445,36 +437,14 @@ with UI components as a child of an entity without UI components, your UI layout
node_transform_query,
ui_children,
inverse_target_scale_factor,
rounded_size,
layout_size,
clamped_scroll_position,
absolute_location,
);
}
}
}
}

#[inline]
/// Round `value` to the nearest whole integer, with ties (values with a fractional part equal to 0.5) rounded towards positive infinity.
fn approx_round_ties_up(value: f32) -> f32 {
(value + 0.5).floor()
}

#[inline]
/// Rounds layout coordinates by rounding ties upwards.
///
/// Rounding ties up avoids gaining a pixel when rounding bounds that span from negative to positive.
///
/// Example: The width between bounds of -50.5 and 49.5 before rounding is 100, using:
/// - `f32::round`: width becomes 101 (rounds to -51 and 50).
/// - `round_ties_up`: width is 100 (rounds to -50 and 50).
fn approx_round_layout_coords(value: Vec2) -> Vec2 {
Vec2 {
x: approx_round_ties_up(value.x),
y: approx_round_ties_up(value.y),
}
}

#[cfg(test)]
mod tests {
use taffy::TraversePartialTree;
Expand All @@ -493,7 +463,7 @@ mod tests {
use bevy_hierarchy::{
despawn_with_children_recursive, BuildChildren, ChildBuild, Children, Parent,
};
use bevy_math::{vec2, Rect, UVec2, Vec2};
use bevy_math::{Rect, UVec2, Vec2};
use bevy_render::{
camera::{ManualTextureViews, OrthographicProjection},
prelude::Camera,
Expand All @@ -510,21 +480,10 @@ mod tests {
};

use crate::{
layout::{approx_round_layout_coords, ui_surface::UiSurface},
prelude::*,
ui_layout_system,
update::update_target_camera_system,
ContentSize, LayoutContext,
layout::ui_surface::UiSurface, prelude::*, ui_layout_system,
update::update_target_camera_system, ContentSize, LayoutContext,
};

#[test]
fn round_layout_coords_must_round_ties_up() {
assert_eq!(
approx_round_layout_coords(vec2(-50.5, 49.5)),
vec2(-50., 50.)
);
}

// these window dimensions are easy to convert to and from percentage values
const WINDOW_WIDTH: f32 = 1000.;
const WINDOW_HEIGHT: f32 = 100.;
Expand Down Expand Up @@ -598,10 +557,10 @@ mod tests {
world.entity_mut(ui_root).add_child(ui_child);

ui_schedule.run(&mut world);
let ui_surface = world.resource::<UiSurface>();
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();
let layout = ui_surface.get_layout(ui_entity).unwrap().0;
assert_eq!(layout.size.width, WINDOW_WIDTH);
assert_eq!(layout.size.height, WINDOW_HEIGHT);
}
Expand Down Expand Up @@ -955,11 +914,12 @@ mod tests {
.get_single(world)
.expect("missing MovingUiNode");
assert_eq!(expected_camera_entity, target_camera_entity);
let ui_surface = world.resource::<UiSurface>();
let mut ui_surface = world.resource_mut::<UiSurface>();

let layout = ui_surface
.get_layout(ui_node_entity)
.expect("failed to get layout");
.expect("failed to get layout")
Copy link
Contributor

Choose a reason for hiding this comment

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

expect() here

.0;

// negative test for #12255
assert_eq!(Vec2::new(layout.location.x, layout.location.y), new_pos);
Expand Down Expand Up @@ -1043,8 +1003,8 @@ mod tests {

ui_schedule.run(&mut world);

let ui_surface = world.resource::<UiSurface>();
let layout = ui_surface.get_layout(ui_entity).unwrap();
let mut ui_surface = world.resource_mut::<UiSurface>();
let layout = ui_surface.get_layout(ui_entity).unwrap().0;
Copy link
Contributor

Choose a reason for hiding this comment

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

unwrap() here


// the node should takes its size from the fixed size measure func
assert_eq!(layout.size.width, content_size.x);
Expand All @@ -1068,25 +1028,25 @@ mod tests {

ui_schedule.run(&mut world);

let ui_surface = world.resource::<UiSurface>();
let mut ui_surface = world.resource_mut::<UiSurface>();
let ui_node = ui_surface.entity_to_taffy[&ui_entity];

// 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();
let layout = ui_surface.get_layout(ui_entity).unwrap().0;
assert_eq!(layout.size.width, content_size.x);
assert_eq!(layout.size.height, content_size.y);

world.entity_mut(ui_entity).remove::<ContentSize>();

ui_schedule.run(&mut world);

let ui_surface = world.resource::<UiSurface>();
let mut ui_surface = world.resource_mut::<UiSurface>();
// a node without a content size should not have taffy context
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();
let layout = ui_surface.get_layout(ui_entity).unwrap().0;
assert_eq!(layout.size.width, 0.);
assert_eq!(layout.size.height, 0.);
}
Expand Down Expand Up @@ -1123,7 +1083,8 @@ mod tests {
.collect::<Vec<Entity>>();

for r in [2, 3, 5, 7, 11, 13, 17, 19, 21, 23, 29, 31].map(|n| (n as f32).recip()) {
let mut s = r;
// This fails with very small / unrealistic scale values
let mut s = 1. - r;
while s <= 5. {
world.resource_mut::<UiScale>().0 = s;
ui_schedule.run(&mut world);
Expand Down
32 changes: 21 additions & 11 deletions crates/bevy_ui/src/layout/ui_surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use bevy_ecs::{
entity::{Entity, EntityHashMap},
prelude::Resource,
};
use bevy_math::UVec2;
use bevy_math::{UVec2, Vec2};
use bevy_utils::default;

use crate::{layout::convert, LayoutContext, LayoutError, Measure, MeasureArgs, Node, NodeMeasure};
Expand Down Expand Up @@ -51,8 +51,7 @@ impl fmt::Debug for UiSurface {

impl Default for UiSurface {
fn default() -> Self {
let mut taffy: TaffyTree<NodeMeasure> = TaffyTree::new();
taffy.disable_rounding();
let taffy: TaffyTree<NodeMeasure> = TaffyTree::new();
Self {
entity_to_taffy: Default::default(),
camera_entity_to_taffy: Default::default(),
Expand Down Expand Up @@ -276,14 +275,25 @@ impl UiSurface {

/// Get the layout geometry for the taffy node corresponding to the ui node [`Entity`].
/// Does not compute the layout geometry, `compute_window_layouts` should be run before using this function.
pub fn get_layout(&self, entity: Entity) -> Result<&taffy::Layout, LayoutError> {
if let Some(taffy_node) = self.entity_to_taffy.get(&entity) {
self.taffy
.layout(*taffy_node)
.map_err(LayoutError::TaffyError)
} else {
Err(LayoutError::InvalidHierarchy)
}
/// On success returns a pair consisiting 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> {
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)?;

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))
}
}

Expand Down