mirror of
https://github.com/bevyengine/bevy
synced 2024-12-24 03:53:06 +00:00
a304fd9a99
# Objective - Hierarchy tools are not just used for `Transform`: they are also used for scenes. - In the future there's interest in using them for other features, such as visiibility inheritance. - The fact that these tools are found in `bevy_transform` causes a great deal of user and developer confusion - Fixes #2758. ## Solution - Split `bevy_transform` into two! - Make everything work again. Note that this is a very tightly scoped PR: I *know* there are code quality and docs issues that existed in bevy_transform that I've just moved around. We should fix those in a seperate PR and try to merge this ASAP to reduce the bitrot involved in splitting an entire crate. ## Frustrations The API around `GlobalTransform` is a mess: we have massive code and docs duplication, no link between the two types and no clear way to extend this to other forms of inheritance. In the medium-term, I feel pretty strongly that `GlobalTransform` should be replaced by something like `Inherited<Transform>`, which lives in `bevy_hierarchy`: - avoids code duplication - makes the inheritance pattern extensible - links the types at the type-level - allows us to remove all references to inheritance from `bevy_transform`, making it more useful as a standalone crate and cleaning up its docs ## Additional context - double-blessed by @cart in https://github.com/bevyengine/bevy/issues/4141#issuecomment-1063592414 and https://github.com/bevyengine/bevy/issues/2758#issuecomment-913810963 - preparation for more advanced / cleaner hierarchy tools: go read https://github.com/bevyengine/rfcs/pull/53 ! - originally attempted by @finegeometer in #2789. It was a great idea, just needed more discussion! Co-authored-by: Carter Anderson <mcanders1@gmail.com>
242 lines
7.9 KiB
Rust
242 lines
7.9 KiB
Rust
//! This module contains systems that update the UI when something changes
|
|
|
|
use crate::{CalculatedClip, Overflow, Style};
|
|
|
|
use super::Node;
|
|
use bevy_ecs::{
|
|
entity::Entity,
|
|
query::{With, Without},
|
|
system::{Commands, Query},
|
|
};
|
|
use bevy_hierarchy::{Children, Parent};
|
|
use bevy_math::Vec2;
|
|
use bevy_sprite::Rect;
|
|
use bevy_transform::components::{GlobalTransform, Transform};
|
|
|
|
/// The resolution of Z values for UI
|
|
pub const UI_Z_STEP: f32 = 0.001;
|
|
|
|
/// Updates transforms of nodes to fit with the z system
|
|
pub fn ui_z_system(
|
|
root_node_query: Query<Entity, (With<Node>, Without<Parent>)>,
|
|
mut node_query: Query<&mut Transform, With<Node>>,
|
|
children_query: Query<&Children>,
|
|
) {
|
|
let mut current_global_z = 0.0;
|
|
for entity in root_node_query.iter() {
|
|
current_global_z = update_hierarchy(
|
|
&children_query,
|
|
&mut node_query,
|
|
entity,
|
|
current_global_z,
|
|
current_global_z,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn update_hierarchy(
|
|
children_query: &Query<&Children>,
|
|
node_query: &mut Query<&mut Transform, With<Node>>,
|
|
entity: Entity,
|
|
parent_global_z: f32,
|
|
mut current_global_z: f32,
|
|
) -> f32 {
|
|
current_global_z += UI_Z_STEP;
|
|
if let Ok(mut transform) = node_query.get_mut(entity) {
|
|
let new_z = current_global_z - parent_global_z;
|
|
// only trigger change detection when the new value is different
|
|
if transform.translation.z != new_z {
|
|
transform.translation.z = new_z;
|
|
}
|
|
}
|
|
if let Ok(children) = children_query.get(entity) {
|
|
let current_parent_global_z = current_global_z;
|
|
for child in children.iter().cloned() {
|
|
current_global_z = update_hierarchy(
|
|
children_query,
|
|
node_query,
|
|
child,
|
|
current_parent_global_z,
|
|
current_global_z,
|
|
);
|
|
}
|
|
}
|
|
current_global_z
|
|
}
|
|
|
|
/// Updates clipping for all nodes
|
|
pub fn update_clipping_system(
|
|
mut commands: Commands,
|
|
root_node_query: Query<Entity, (With<Node>, Without<Parent>)>,
|
|
mut node_query: Query<(&Node, &GlobalTransform, &Style, Option<&mut CalculatedClip>)>,
|
|
children_query: Query<&Children>,
|
|
) {
|
|
for root_node in root_node_query.iter() {
|
|
update_clipping(
|
|
&mut commands,
|
|
&children_query,
|
|
&mut node_query,
|
|
root_node,
|
|
None,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn update_clipping(
|
|
commands: &mut Commands,
|
|
children_query: &Query<&Children>,
|
|
node_query: &mut Query<(&Node, &GlobalTransform, &Style, Option<&mut CalculatedClip>)>,
|
|
entity: Entity,
|
|
clip: Option<Rect>,
|
|
) {
|
|
let (node, global_transform, style, calculated_clip) = node_query.get_mut(entity).unwrap();
|
|
// Update this node's CalculatedClip component
|
|
match (clip, calculated_clip) {
|
|
(None, None) => {}
|
|
(None, Some(_)) => {
|
|
commands.entity(entity).remove::<CalculatedClip>();
|
|
}
|
|
(Some(clip), None) => {
|
|
commands.entity(entity).insert(CalculatedClip { clip });
|
|
}
|
|
(Some(clip), Some(mut old_clip)) => {
|
|
*old_clip = CalculatedClip { clip };
|
|
}
|
|
}
|
|
|
|
// Calculate new clip for its children
|
|
let children_clip = match style.overflow {
|
|
Overflow::Visible => clip,
|
|
Overflow::Hidden => {
|
|
let node_center = global_transform.translation.truncate();
|
|
let node_rect = Rect {
|
|
min: node_center - node.size / 2.,
|
|
max: node_center + node.size / 2.,
|
|
};
|
|
if let Some(clip) = clip {
|
|
Some(Rect {
|
|
min: Vec2::max(clip.min, node_rect.min),
|
|
max: Vec2::min(clip.max, node_rect.max),
|
|
})
|
|
} else {
|
|
Some(node_rect)
|
|
}
|
|
}
|
|
};
|
|
|
|
if let Ok(children) = children_query.get(entity) {
|
|
for child in children.iter().cloned() {
|
|
update_clipping(commands, children_query, node_query, child, children_clip);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use bevy_ecs::{
|
|
component::Component,
|
|
schedule::{Schedule, Stage, SystemStage},
|
|
system::{CommandQueue, Commands},
|
|
world::World,
|
|
};
|
|
use bevy_hierarchy::BuildChildren;
|
|
use bevy_transform::components::Transform;
|
|
|
|
use crate::Node;
|
|
|
|
use super::{ui_z_system, UI_Z_STEP};
|
|
|
|
#[derive(Component, PartialEq, Debug, Clone)]
|
|
struct Label(&'static str);
|
|
|
|
fn node_with_transform(name: &'static str) -> (Label, Node, Transform) {
|
|
(Label(name), Node::default(), Transform::identity())
|
|
}
|
|
|
|
fn node_without_transform(name: &'static str) -> (Label, Node) {
|
|
(Label(name), Node::default())
|
|
}
|
|
|
|
fn get_steps(transform: &Transform) -> u32 {
|
|
(transform.translation.z / UI_Z_STEP).round() as u32
|
|
}
|
|
|
|
#[test]
|
|
fn test_ui_z_system() {
|
|
let mut world = World::default();
|
|
let mut queue = CommandQueue::default();
|
|
let mut commands = Commands::new(&mut queue, &world);
|
|
commands.spawn_bundle(node_with_transform("0"));
|
|
|
|
commands
|
|
.spawn_bundle(node_with_transform("1"))
|
|
.with_children(|parent| {
|
|
parent
|
|
.spawn_bundle(node_with_transform("1-0"))
|
|
.with_children(|parent| {
|
|
parent.spawn_bundle(node_with_transform("1-0-0"));
|
|
parent.spawn_bundle(node_without_transform("1-0-1"));
|
|
parent.spawn_bundle(node_with_transform("1-0-2"));
|
|
});
|
|
parent.spawn_bundle(node_with_transform("1-1"));
|
|
parent
|
|
.spawn_bundle(node_without_transform("1-2"))
|
|
.with_children(|parent| {
|
|
parent.spawn_bundle(node_with_transform("1-2-0"));
|
|
parent.spawn_bundle(node_with_transform("1-2-1"));
|
|
parent
|
|
.spawn_bundle(node_with_transform("1-2-2"))
|
|
.with_children(|_| ());
|
|
parent.spawn_bundle(node_with_transform("1-2-3"));
|
|
});
|
|
parent.spawn_bundle(node_with_transform("1-3"));
|
|
});
|
|
|
|
commands
|
|
.spawn_bundle(node_without_transform("2"))
|
|
.with_children(|parent| {
|
|
parent
|
|
.spawn_bundle(node_with_transform("2-0"))
|
|
.with_children(|_parent| ());
|
|
parent
|
|
.spawn_bundle(node_with_transform("2-1"))
|
|
.with_children(|parent| {
|
|
parent.spawn_bundle(node_with_transform("2-1-0"));
|
|
});
|
|
});
|
|
queue.apply(&mut world);
|
|
|
|
let mut schedule = Schedule::default();
|
|
let mut update_stage = SystemStage::parallel();
|
|
update_stage.add_system(ui_z_system);
|
|
schedule.add_stage("update", update_stage);
|
|
schedule.run(&mut world);
|
|
|
|
let mut actual_result = world
|
|
.query::<(&Label, &Transform)>()
|
|
.iter(&world)
|
|
.map(|(name, transform)| (name.clone(), get_steps(transform)))
|
|
.collect::<Vec<(Label, u32)>>();
|
|
actual_result.sort_unstable_by_key(|(name, _)| name.0);
|
|
let expected_result = vec![
|
|
(Label("0"), 1),
|
|
(Label("1"), 1),
|
|
(Label("1-0"), 1),
|
|
(Label("1-0-0"), 1),
|
|
// 1-0-1 has no transform
|
|
(Label("1-0-2"), 3),
|
|
(Label("1-1"), 5),
|
|
// 1-2 has no transform
|
|
(Label("1-2-0"), 1),
|
|
(Label("1-2-1"), 2),
|
|
(Label("1-2-2"), 3),
|
|
(Label("1-2-3"), 4),
|
|
(Label("1-3"), 11),
|
|
// 2 has no transform
|
|
(Label("2-0"), 1),
|
|
(Label("2-1"), 2),
|
|
(Label("2-1-0"), 1),
|
|
];
|
|
assert_eq!(actual_result, expected_result);
|
|
}
|
|
}
|