bevy/crates/bevy_ui/src/update.rs
ickshonpe 09df19bcad
Split UI Overflow by axis (#8095)
# Objective

Split the UI overflow enum so that overflow can be set for each axis
separately.

## Solution

Change `Overflow` from an enum to a struct with `x` and `y`
`OverflowAxis` fields, where `OverflowAxis` is an enum with `Clip` and
`Visible` variants. Modify `update_clipping` to calculate clipping for
each axis separately. If only one axis is clipped, the other axis is
given infinite bounds.

<img width="642" alt="overflow"
src="https://user-images.githubusercontent.com/27962798/227592983-568cf76f-7e40-48c4-a511-43c886f5e431.PNG">

---

## Changelog
* Split the UI overflow implementation so overflow can be set for each
axis separately.
* Added the enum `OverflowAxis` with `Clip` and `Visible` variants.
* Changed `Overflow` to a struct with `x` and `y` fields of type
`OverflowAxis`.
* `Overflow` has new methods `visible()` and `hidden()` that replace its
previous `Clip` and `Visible` variants.
* Added `Overflow` helper methods `clip_x()` and `clip_y()` that return
a new `Overflow` value with the given axis clipped.
* Modified `update_clipping` so it calculates clipping for each axis
separately. If a node is only clipped on a single axis, the other axis
is given `-f32::INFINITY` to `f32::INFINITY` clipping bounds.


## Migration Guide

The `Style` property `Overflow` is now a struct with `x` and `y` fields,
that allow for per-axis overflow control.

Use these helper functions to replace the variants of `Overflow`:
* Replace `Overflow::Visible` with  `Overflow::visible()`
* Replace `Overflow::Hidden` with `Overflow::clip()`
2023-04-17 22:23:52 +00:00

93 lines
3.4 KiB
Rust

//! This module contains systems that update the UI when something changes
use crate::{CalculatedClip, OverflowAxis, Style};
use super::Node;
use bevy_ecs::{
entity::Entity,
query::{With, Without},
system::{Commands, Query},
};
use bevy_hierarchy::{Children, Parent};
use bevy_math::Rect;
use bevy_transform::components::GlobalTransform;
/// 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 {
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,
maybe_inherited_clip: Option<Rect>,
) {
let (node, global_transform, style, maybe_calculated_clip) =
node_query.get_mut(entity).unwrap();
// Update this node's CalculatedClip component
if let Some(mut calculated_clip) = maybe_calculated_clip {
if let Some(inherited_clip) = maybe_inherited_clip {
// Replace the previous calculated clip with the inherited clipping rect
if calculated_clip.clip != inherited_clip {
*calculated_clip = CalculatedClip {
clip: inherited_clip,
};
}
} else {
// No inherited clipping rect, remove the component
commands.entity(entity).remove::<CalculatedClip>();
}
} else if let Some(inherited_clip) = maybe_inherited_clip {
// No previous calculated clip, add a new CalculatedClip component with the inherited clipping rect
commands.entity(entity).insert(CalculatedClip {
clip: inherited_clip,
});
}
// Calculate new clip rectangle for children nodes
let children_clip = if style.overflow.is_visible() {
// When `Visible`, children might be visible even when they are outside
// the current node's boundaries. In this case they inherit the current
// node's parent clip. If an ancestor is set as `Hidden`, that clip will
// be used; otherwise this will be `None`.
maybe_inherited_clip
} else {
// If `maybe_inherited_clip` is `Some`, use the intersection between
// current node's clip and the inherited clip. This handles the case
// of nested `Overflow::Hidden` nodes. If parent `clip` is not
// defined, use the current node's clip.
let mut node_rect = node.logical_rect(global_transform);
if style.overflow.x == OverflowAxis::Visible {
node_rect.min.x = -f32::INFINITY;
node_rect.max.x = f32::INFINITY;
}
if style.overflow.y == OverflowAxis::Visible {
node_rect.min.y = -f32::INFINITY;
node_rect.max.y = f32::INFINITY;
}
Some(maybe_inherited_clip.map_or(node_rect, |c| c.intersect(node_rect)))
};
if let Ok(children) = children_query.get(entity) {
for &child in children {
update_clipping(commands, children_query, node_query, child, children_clip);
}
}
}