bevy/crates/bevy_ui/src/ui_node.rs

911 lines
29 KiB
Rust
Raw Normal View History

use crate::{Size, UiRect};
use bevy_asset::Handle;
use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
Move `sprite::Rect` into `bevy_math` (#5686) # Objective Promote the `Rect` utility of `sprite::Rect`, which defines a rectangle by its minimum and maximum corners, to the `bevy_math` crate to make it available as a general math type to all crates without the need to depend on the `bevy_sprite` crate. Fixes #5575 ## Solution Move `sprite::Rect` into `bevy_math` and fix all uses. Implement `Reflect` for `Rect` directly into the `bevy_reflect` crate by having `bevy_reflect` depend on `bevy_math`. This looks like a new dependency, but the `bevy_reflect` was "cheating" for other math types by directly depending on `glam` to reflect other math types, thereby giving the illusion that there was no dependency on `bevy_math`. In practice conceptually Bevy's math types are reflected into the `bevy_reflect` crate to avoid a dependency of that crate to a "lower level" utility crate like `bevy_math` (which in turn would make `bevy_reflect` be a dependency of most other crates, and increase the risk of circular dependencies). So this change simply formalizes that dependency in `Cargo.toml`. The `Rect` struct is also augmented in this change with a collection of utility methods to improve its usability. A few uses cases are updated to use those new methods, resulting is more clear and concise syntax. --- ## Changelog ### Changed - Moved the `sprite::Rect` type into `bevy_math`. ### Added - Added several utility methods to the `math::Rect` type. ## Migration Guide The `bevy::sprite::Rect` type moved to the math utility crate as `bevy::math::Rect`. You should change your imports from `use bevy::sprite::Rect` to `use bevy::math::Rect`.
2022-09-02 12:35:23 +00:00
use bevy_math::{Rect, Vec2};
add `#[reflect(Default)]` to create default value for reflected types (#3733) ### Problem It currently isn't possible to construct the default value of a reflected type. Because of that, it isn't possible to use `add_component` of `ReflectComponent` to add a new component to an entity because you can't know what the initial value should be. ### Solution 1. add `ReflectDefault` type ```rust #[derive(Clone)] pub struct ReflectDefault { default: fn() -> Box<dyn Reflect>, } impl ReflectDefault { pub fn default(&self) -> Box<dyn Reflect> { (self.default)() } } impl<T: Reflect + Default> FromType<T> for ReflectDefault { fn from_type() -> Self { ReflectDefault { default: || Box::new(T::default()), } } } ``` 2. add `#[reflect(Default)]` to all component types that implement `Default` and are user facing (so not `ComputedSize`, `CubemapVisibleEntities` etc.) This makes it possible to add the default value of a component to an entity without any compile-time information: ```rust fn main() { let mut app = App::new(); app.register_type::<Camera>(); let type_registry = app.world.get_resource::<TypeRegistry>().unwrap(); let type_registry = type_registry.read(); let camera_registration = type_registry.get(std::any::TypeId::of::<Camera>()).unwrap(); let reflect_default = camera_registration.data::<ReflectDefault>().unwrap(); let reflect_component = camera_registration .data::<ReflectComponent>() .unwrap() .clone(); let default = reflect_default.default(); drop(type_registry); let entity = app.world.spawn().id(); reflect_component.add_component(&mut app.world, entity, &*default); let camera = app.world.entity(entity).get::<Camera>().unwrap(); dbg!(&camera); } ``` ### Open questions - should we have `ReflectDefault` or `ReflectFromWorld` or both?
2022-05-03 19:20:13 +00:00
use bevy_reflect::prelude::*;
use bevy_render::{
color::Color,
texture::{Image, DEFAULT_IMAGE_HANDLE},
};
2020-11-28 00:39:59 +00:00
use serde::{Deserialize, Serialize};
use std::ops::{Div, DivAssign, Mul, MulAssign};
use thiserror::Error;
2020-01-13 00:51:21 +00:00
/// Describes the size of a UI node
#[derive(Component, Debug, Clone, Reflect)]
add `#[reflect(Default)]` to create default value for reflected types (#3733) ### Problem It currently isn't possible to construct the default value of a reflected type. Because of that, it isn't possible to use `add_component` of `ReflectComponent` to add a new component to an entity because you can't know what the initial value should be. ### Solution 1. add `ReflectDefault` type ```rust #[derive(Clone)] pub struct ReflectDefault { default: fn() -> Box<dyn Reflect>, } impl ReflectDefault { pub fn default(&self) -> Box<dyn Reflect> { (self.default)() } } impl<T: Reflect + Default> FromType<T> for ReflectDefault { fn from_type() -> Self { ReflectDefault { default: || Box::new(T::default()), } } } ``` 2. add `#[reflect(Default)]` to all component types that implement `Default` and are user facing (so not `ComputedSize`, `CubemapVisibleEntities` etc.) This makes it possible to add the default value of a component to an entity without any compile-time information: ```rust fn main() { let mut app = App::new(); app.register_type::<Camera>(); let type_registry = app.world.get_resource::<TypeRegistry>().unwrap(); let type_registry = type_registry.read(); let camera_registration = type_registry.get(std::any::TypeId::of::<Camera>()).unwrap(); let reflect_default = camera_registration.data::<ReflectDefault>().unwrap(); let reflect_component = camera_registration .data::<ReflectComponent>() .unwrap() .clone(); let default = reflect_default.default(); drop(type_registry); let entity = app.world.spawn().id(); reflect_component.add_component(&mut app.world, entity, &*default); let camera = app.world.entity(entity).get::<Camera>().unwrap(); dbg!(&camera); } ``` ### Open questions - should we have `ReflectDefault` or `ReflectFromWorld` or both?
2022-05-03 19:20:13 +00:00
#[reflect(Component, Default)]
2020-01-13 00:51:21 +00:00
pub struct Node {
/// The size of the node as width and height in logical pixels
/// automatically calculated by [`super::flex::flex_node_system`]
pub(crate) calculated_size: Vec2,
}
impl Node {
/// The calculated node size as width and height in logical pixels
/// automatically calculated by [`super::flex::flex_node_system`]
pub fn size(&self) -> Vec2 {
self.calculated_size
}
2020-01-13 00:51:21 +00:00
}
2020-07-26 19:27:09 +00:00
impl Node {
pub const DEFAULT: Self = Self {
calculated_size: Vec2::ZERO,
};
}
impl Default for Node {
fn default() -> Self {
Self::DEFAULT
}
}
/// An enum that describes possible types of value in flexbox layout options
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)]
2022-08-02 22:40:29 +00:00
#[reflect(PartialEq, Serialize, Deserialize)]
2020-07-26 19:27:09 +00:00
pub enum Val {
/// No value defined
2020-07-26 19:27:09 +00:00
Undefined,
/// Automatically determine this value
2020-07-26 19:27:09 +00:00
Auto,
/// Set this value in pixels
2020-07-26 19:27:09 +00:00
Px(f32),
/// Set this value in percent
2020-07-26 19:27:09 +00:00
Percent(f32),
}
impl Val {
pub const DEFAULT: Self = Self::Undefined;
}
impl Default for Val {
fn default() -> Self {
Self::DEFAULT
}
}
impl Mul<f32> for Val {
2020-07-26 19:27:09 +00:00
type Output = Val;
2020-07-28 21:24:03 +00:00
fn mul(self, rhs: f32) -> Self::Output {
2020-07-26 19:27:09 +00:00
match self {
Val::Undefined => Val::Undefined,
Val::Auto => Val::Auto,
Val::Px(value) => Val::Px(value * rhs),
Val::Percent(value) => Val::Percent(value * rhs),
2020-07-26 19:27:09 +00:00
}
}
}
impl MulAssign<f32> for Val {
fn mul_assign(&mut self, rhs: f32) {
2020-07-26 19:27:09 +00:00
match self {
2020-07-28 21:24:03 +00:00
Val::Undefined | Val::Auto => {}
Val::Px(value) | Val::Percent(value) => *value *= rhs,
2020-07-26 19:27:09 +00:00
}
}
}
impl Div<f32> for Val {
type Output = Val;
fn div(self, rhs: f32) -> Self::Output {
match self {
Val::Undefined => Val::Undefined,
Val::Auto => Val::Auto,
Val::Px(value) => Val::Px(value / rhs),
Val::Percent(value) => Val::Percent(value / rhs),
}
}
}
impl DivAssign<f32> for Val {
fn div_assign(&mut self, rhs: f32) {
match self {
Val::Undefined | Val::Auto => {}
Val::Px(value) | Val::Percent(value) => *value /= rhs,
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, Copy, Error)]
pub enum ValArithmeticError {
#[error("the variants of the Vals don't match")]
NonIdenticalVariants,
#[error("the given variant of Val is not evaluateable (non-numeric)")]
NonEvaluateable,
}
impl Val {
/// Tries to add the values of two [`Val`]s.
/// Returns [`ValArithmeticError::NonIdenticalVariants`] if two [`Val`]s are of different variants.
/// When adding non-numeric [`Val`]s, it returns the value unchanged.
pub fn try_add(&self, rhs: Val) -> Result<Val, ValArithmeticError> {
match (self, rhs) {
(Val::Undefined, Val::Undefined) | (Val::Auto, Val::Auto) => Ok(*self),
(Val::Px(value), Val::Px(rhs_value)) => Ok(Val::Px(value + rhs_value)),
(Val::Percent(value), Val::Percent(rhs_value)) => Ok(Val::Percent(value + rhs_value)),
_ => Err(ValArithmeticError::NonIdenticalVariants),
}
}
/// Adds `rhs` to `self` and assigns the result to `self` (see [`Val::try_add`])
pub fn try_add_assign(&mut self, rhs: Val) -> Result<(), ValArithmeticError> {
*self = self.try_add(rhs)?;
Ok(())
}
/// Tries to subtract the values of two [`Val`]s.
/// Returns [`ValArithmeticError::NonIdenticalVariants`] if two [`Val`]s are of different variants.
/// When adding non-numeric [`Val`]s, it returns the value unchanged.
pub fn try_sub(&self, rhs: Val) -> Result<Val, ValArithmeticError> {
match (self, rhs) {
(Val::Undefined, Val::Undefined) | (Val::Auto, Val::Auto) => Ok(*self),
(Val::Px(value), Val::Px(rhs_value)) => Ok(Val::Px(value - rhs_value)),
(Val::Percent(value), Val::Percent(rhs_value)) => Ok(Val::Percent(value - rhs_value)),
_ => Err(ValArithmeticError::NonIdenticalVariants),
}
}
/// Subtracts `rhs` from `self` and assigns the result to `self` (see [`Val::try_sub`])
pub fn try_sub_assign(&mut self, rhs: Val) -> Result<(), ValArithmeticError> {
*self = self.try_sub(rhs)?;
Ok(())
}
/// A convenience function for simple evaluation of [`Val::Percent`] variant into a concrete [`Val::Px`] value.
/// Returns a [`ValArithmeticError::NonEvaluateable`] if the [`Val`] is impossible to evaluate into [`Val::Px`].
/// Otherwise it returns an [`f32`] containing the evaluated value in pixels.
///
/// **Note:** If a [`Val::Px`] is evaluated, it's inner value returned unchanged.
pub fn evaluate(&self, size: f32) -> Result<f32, ValArithmeticError> {
match self {
Val::Percent(value) => Ok(size * value / 100.0),
Val::Px(value) => Ok(*value),
_ => Err(ValArithmeticError::NonEvaluateable),
}
}
/// Similar to [`Val::try_add`], but performs [`Val::evaluate`] on both values before adding.
/// Returns an [`f32`] value in pixels.
pub fn try_add_with_size(&self, rhs: Val, size: f32) -> Result<f32, ValArithmeticError> {
let lhs = self.evaluate(size)?;
let rhs = rhs.evaluate(size)?;
Ok(lhs + rhs)
}
/// Similar to [`Val::try_add_assign`], but performs [`Val::evaluate`] on both values before adding.
/// The value gets converted to [`Val::Px`].
pub fn try_add_assign_with_size(
&mut self,
rhs: Val,
size: f32,
) -> Result<(), ValArithmeticError> {
*self = Val::Px(self.evaluate(size)? + rhs.evaluate(size)?);
Ok(())
}
/// Similar to [`Val::try_sub`], but performs [`Val::evaluate`] on both values before subtracting.
/// Returns an [`f32`] value in pixels.
pub fn try_sub_with_size(&self, rhs: Val, size: f32) -> Result<f32, ValArithmeticError> {
let lhs = self.evaluate(size)?;
let rhs = rhs.evaluate(size)?;
Ok(lhs - rhs)
}
/// Similar to [`Val::try_sub_assign`], but performs [`Val::evaluate`] on both values before adding.
/// The value gets converted to [`Val::Px`].
pub fn try_sub_assign_with_size(
&mut self,
rhs: Val,
size: f32,
) -> Result<(), ValArithmeticError> {
*self = Val::Px(self.try_add_with_size(rhs, size)?);
Ok(())
}
}
/// Describes the style of a UI node
///
/// It uses the [Flexbox](https://cssreference.io/flexbox/) system.
#[derive(Component, Clone, PartialEq, Debug, Reflect)]
add `#[reflect(Default)]` to create default value for reflected types (#3733) ### Problem It currently isn't possible to construct the default value of a reflected type. Because of that, it isn't possible to use `add_component` of `ReflectComponent` to add a new component to an entity because you can't know what the initial value should be. ### Solution 1. add `ReflectDefault` type ```rust #[derive(Clone)] pub struct ReflectDefault { default: fn() -> Box<dyn Reflect>, } impl ReflectDefault { pub fn default(&self) -> Box<dyn Reflect> { (self.default)() } } impl<T: Reflect + Default> FromType<T> for ReflectDefault { fn from_type() -> Self { ReflectDefault { default: || Box::new(T::default()), } } } ``` 2. add `#[reflect(Default)]` to all component types that implement `Default` and are user facing (so not `ComputedSize`, `CubemapVisibleEntities` etc.) This makes it possible to add the default value of a component to an entity without any compile-time information: ```rust fn main() { let mut app = App::new(); app.register_type::<Camera>(); let type_registry = app.world.get_resource::<TypeRegistry>().unwrap(); let type_registry = type_registry.read(); let camera_registration = type_registry.get(std::any::TypeId::of::<Camera>()).unwrap(); let reflect_default = camera_registration.data::<ReflectDefault>().unwrap(); let reflect_component = camera_registration .data::<ReflectComponent>() .unwrap() .clone(); let default = reflect_default.default(); drop(type_registry); let entity = app.world.spawn().id(); reflect_component.add_component(&mut app.world, entity, &*default); let camera = app.world.entity(entity).get::<Camera>().unwrap(); dbg!(&camera); } ``` ### Open questions - should we have `ReflectDefault` or `ReflectFromWorld` or both?
2022-05-03 19:20:13 +00:00
#[reflect(Component, Default, PartialEq)]
2020-07-26 19:27:09 +00:00
pub struct Style {
/// Whether to arrange this node and its children with flexbox layout
Disable UI node `Interaction` when `ComputedVisibility` is false (#5361) # Objective UI nodes can be hidden by setting their `Visibility` property. Since #5310 was merged, this is now ergonomic to use, as visibility is now inherited. However, UI nodes still receive (and store) interactions when hidden, resulting in surprising hidden state (and an inability to otherwise disable UI nodes. ## Solution Fixes #5360. I've updated the `ui_focus_system` to accomplish this in a minimally intrusive way, and updated the docs to match. **NOTE:** I have not added automated tests to verify this behavior, as we do not currently have a good testing paradigm for `bevy_ui`. I'm not thrilled with that by any means, but I'm not sure fixing it is within scope. ## Paths not taken ### Separate `Disabled` component This is a much larger and more controversial change, and not well-scoped to UI. Furthermore, it is extremely rare that you want hidden UI elements to function: the most common cases are for things like changing tabs, collapsing elements or so on. Splitting this behavior would be more complex, and substantially violate user expectations. ### A separate limbo world Mentioned in the linked issue. Super cool, but all of the problems of the `Disabled` component solution with a whole new RFC-worth of complexity. ### Using change detection to reduce the amount of redundant work Adds a lot of complexity for questionable performance gains. Likely involves a complete refactor of the entire system. We simply don't have the tests or benchmarks here to justify this. ## Changelog - UI nodes are now always in an `Interaction::None` state while they are hidden (via the `ComputedVisibility` component).
2022-07-20 21:26:47 +00:00
///
/// If this is set to [`Display::None`], this node will be collapsed.
2020-07-26 19:27:09 +00:00
pub display: Display,
/// Whether to arrange this node relative to other nodes, or positioned absolutely
2020-07-26 19:27:09 +00:00
pub position_type: PositionType,
/// Which direction the content of this node should go
2020-07-26 19:27:09 +00:00
pub direction: Direction,
/// Whether to use column or row layout
2020-07-26 19:27:09 +00:00
pub flex_direction: FlexDirection,
/// How to wrap nodes
2020-07-26 19:27:09 +00:00
pub flex_wrap: FlexWrap,
/// How items are aligned according to the cross axis
2020-07-26 19:27:09 +00:00
pub align_items: AlignItems,
Fix the `AlignSelf` documentation (#7577) # Objective The current `AlignSelf` doc comments: ```rust pub enum AlignSelf { /// Use the value of [`AlignItems`] Auto, /// If the parent has [`AlignItems::Center`] only this item will be at the start FlexStart, /// If the parent has [`AlignItems::Center`] only this item will be at the end FlexEnd, /// If the parent has [`AlignItems::FlexStart`] only this item will be at the center Center, /// If the parent has [`AlignItems::Center`] only this item will be at the baseline Baseline, /// If the parent has [`AlignItems::Center`] only this item will stretch along the whole cross axis Stretch, } ``` Actual behaviour of `AlignSelf` in Bevy main: <img width="642" alt="align_self" src="https://user-images.githubusercontent.com/27962798/217795178-7a82638f-118d-4474-b7f9-ca27f204731d.PNG"> The white label at the top of each column is the parent node's `AlignItems` setting. `AlignSelf` is always applied, not (as the documentation states) only when the parent has `AlignItems::Center` or `AlignItems::FlexStart` set. ```rust use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) .add_startup_system(setup) .run(); } fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { commands.spawn(Camera2dBundle::default()); commands.spawn(NodeBundle { style: Style { justify_content: JustifyContent::SpaceAround, align_items: AlignItems::Center, size: Size::new(Val::Percent(100.), Val::Percent(100.)), ..Default::default() }, background_color: BackgroundColor(Color::NAVY), ..Default::default() }).with_children(|builder| { for align_items in [ AlignItems::Baseline, AlignItems::FlexStart, AlignItems::Center, AlignItems::FlexEnd, AlignItems::Stretch, ] { builder.spawn(NodeBundle { style: Style { align_items, flex_direction: FlexDirection::Column, justify_content: JustifyContent::SpaceBetween, size: Size::new(Val::Px(150.), Val::Px(500.)), ..Default::default() }, background_color: BackgroundColor(Color::DARK_GRAY), ..Default::default() }).with_children(|builder| { builder.spawn(( TextBundle { text: Text::from_section( format!("AlignItems::{align_items:?}"), TextStyle { font: asset_server.load("fonts/FiraSans-Regular.ttf"), font_size: 16.0, color: Color::BLACK, }, ), style: Style { align_self: AlignSelf::Stretch, ..Default::default() }, ..Default::default() }, BackgroundColor(Color::WHITE), )); for align_self in [ AlignSelf::Auto, AlignSelf::FlexStart, AlignSelf::Center, AlignSelf::FlexEnd, AlignSelf::Baseline, AlignSelf::Stretch, ] { builder.spawn(( TextBundle { text: Text::from_section( format!("AlignSelf::{align_self:?}"), TextStyle { font: asset_server.load("fonts/FiraSans-Regular.ttf"), font_size: 16.0, color: Color::WHITE, }, ), style: Style { align_self, ..Default::default() }, ..Default::default() }, BackgroundColor(Color::BLACK), )); } }); } }); } ```
2023-02-09 20:00:11 +00:00
/// How this item is aligned according to the cross axis.
/// Overrides [`AlignItems`].
2020-07-26 19:27:09 +00:00
pub align_self: AlignSelf,
/// How to align each line, only applies if flex_wrap is set to
/// [`FlexWrap::Wrap`] and there are multiple lines of items
2020-07-26 19:27:09 +00:00
pub align_content: AlignContent,
/// How items align according to the main axis
2020-07-26 19:27:09 +00:00
pub justify_content: JustifyContent,
/// The position of the node as described by its Rect
pub position: UiRect,
/// The amount of space around a node outside its border.
///
/// If a percentage value is used, the percentage is calculated based on the width of the parent node.
///
/// # Example
/// ```
/// # use bevy_ui::{Style, UiRect, Val};
/// let style = Style {
/// margin: UiRect {
/// left: Val::Percent(10.),
/// right: Val::Percent(10.),
/// top: Val::Percent(15.),
/// bottom: Val::Percent(15.)
/// },
/// ..Default::default()
/// };
/// ```
/// A node with this style and a parent with dimensions of 100px by 300px, will have calculated margins of 10px on both left and right edges, and 15px on both top and bottom egdes.
pub margin: UiRect,
/// The amount of space between the edges of a node and its contents.
///
/// If a percentage value is used, the percentage is calculated based on the width of the parent node.
///
/// # Example
/// ```
/// # use bevy_ui::{Style, UiRect, Val};
/// let style = Style {
/// padding: UiRect {
/// left: Val::Percent(1.),
/// right: Val::Percent(2.),
/// top: Val::Percent(3.),
/// bottom: Val::Percent(4.)
/// },
/// ..Default::default()
/// };
/// ```
/// A node with this style and a parent with dimensions of 300px by 100px, will have calculated padding of 3px on the left, 6px on the right, 9px on the top and 12px on the bottom.
pub padding: UiRect,
/// The amount of space between the margins of a node and its padding.
///
/// If a percentage value is used, the percentage is calculated based on the width of the parent node.
///
/// The size of the node will be expanded if there are constraints that prevent the layout algorithm from placing the border within the existing node boundary.
///
/// Rendering for borders is not yet implemented.
pub border: UiRect,
/// Defines how much a flexbox item should grow if there's space available
2020-07-26 19:27:09 +00:00
pub flex_grow: f32,
/// How to shrink if there's not enough space available
2020-07-26 19:27:09 +00:00
pub flex_shrink: f32,
/// The initial length of the main axis, before other properties are applied.
///
/// If both are set, `flex_basis` overrides `size` on the main axis but it obeys the bounds defined by `min_size` and `max_size`.
2020-07-26 19:27:09 +00:00
pub flex_basis: Val,
/// The ideal size of the flexbox
///
/// `size.width` is used when it is within the bounds defined by `min_size.width` and `max_size.width`.
/// `size.height` is used when it is within the bounds defined by `min_size.height` and `max_size.height`.
pub size: Size,
/// The minimum size of the flexbox
///
/// `min_size.width` is used if it is greater than either `size.width` or `max_size.width`, or both.
/// `min_size.height` is used if it is greater than either `size.height` or `max_size.height`, or both.
pub min_size: Size,
/// The maximum size of the flexbox
///
/// `max_size.width` is used if it is within the bounds defined by `min_size.width` and `size.width`.
/// `max_size.height` is used if it is within the bounds defined by `min_size.height` and `size.height.
pub max_size: Size,
/// The aspect ratio of the flexbox
2020-07-26 19:27:09 +00:00
pub aspect_ratio: Option<f32>,
/// How to handle overflow
pub overflow: Overflow,
/// The size of the gutters between the rows and columns of the flexbox layout
///
/// Values of `Size::UNDEFINED` and `Size::AUTO` are treated as zero.
pub gap: Size,
2020-07-26 19:27:09 +00:00
}
impl Style {
pub const DEFAULT: Self = Self {
display: Display::DEFAULT,
position_type: PositionType::DEFAULT,
direction: Direction::DEFAULT,
flex_direction: FlexDirection::DEFAULT,
flex_wrap: FlexWrap::DEFAULT,
align_items: AlignItems::DEFAULT,
align_self: AlignSelf::DEFAULT,
align_content: AlignContent::DEFAULT,
justify_content: JustifyContent::DEFAULT,
position: UiRect::DEFAULT,
margin: UiRect::DEFAULT,
padding: UiRect::DEFAULT,
border: UiRect::DEFAULT,
flex_grow: 0.0,
flex_shrink: 1.0,
flex_basis: Val::Auto,
size: Size::AUTO,
min_size: Size::AUTO,
max_size: Size::AUTO,
aspect_ratio: None,
overflow: Overflow::DEFAULT,
gap: Size::UNDEFINED,
};
}
2020-07-26 19:27:09 +00:00
impl Default for Style {
fn default() -> Self {
Self::DEFAULT
2020-07-26 19:27:09 +00:00
}
}
/// How items are aligned according to the cross axis
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
2022-08-02 22:40:29 +00:00
#[reflect(PartialEq, Serialize, Deserialize)]
2020-07-26 19:27:09 +00:00
pub enum AlignItems {
/// Items are packed towards the start of the axis.
Start,
/// Items are packed towards the end of the axis.
End,
/// Items are packed towards the start of the axis, unless the flex direction is reversed;
/// then they are packed towards the end of the axis.
2020-07-26 19:27:09 +00:00
FlexStart,
/// Items are packed towards the end of the axis, unless the flex direction is reversed;
/// then they are packed towards the end of the axis.
2020-07-26 19:27:09 +00:00
FlexEnd,
/// Items are aligned at the center.
2020-07-26 19:27:09 +00:00
Center,
/// Items are aligned at the baseline.
2020-07-26 19:27:09 +00:00
Baseline,
/// Items are stretched across the whole cross axis.
2020-07-26 19:27:09 +00:00
Stretch,
}
impl AlignItems {
pub const DEFAULT: Self = Self::Stretch;
}
impl Default for AlignItems {
fn default() -> Self {
Self::DEFAULT
}
}
Fix the `AlignSelf` documentation (#7577) # Objective The current `AlignSelf` doc comments: ```rust pub enum AlignSelf { /// Use the value of [`AlignItems`] Auto, /// If the parent has [`AlignItems::Center`] only this item will be at the start FlexStart, /// If the parent has [`AlignItems::Center`] only this item will be at the end FlexEnd, /// If the parent has [`AlignItems::FlexStart`] only this item will be at the center Center, /// If the parent has [`AlignItems::Center`] only this item will be at the baseline Baseline, /// If the parent has [`AlignItems::Center`] only this item will stretch along the whole cross axis Stretch, } ``` Actual behaviour of `AlignSelf` in Bevy main: <img width="642" alt="align_self" src="https://user-images.githubusercontent.com/27962798/217795178-7a82638f-118d-4474-b7f9-ca27f204731d.PNG"> The white label at the top of each column is the parent node's `AlignItems` setting. `AlignSelf` is always applied, not (as the documentation states) only when the parent has `AlignItems::Center` or `AlignItems::FlexStart` set. ```rust use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) .add_startup_system(setup) .run(); } fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { commands.spawn(Camera2dBundle::default()); commands.spawn(NodeBundle { style: Style { justify_content: JustifyContent::SpaceAround, align_items: AlignItems::Center, size: Size::new(Val::Percent(100.), Val::Percent(100.)), ..Default::default() }, background_color: BackgroundColor(Color::NAVY), ..Default::default() }).with_children(|builder| { for align_items in [ AlignItems::Baseline, AlignItems::FlexStart, AlignItems::Center, AlignItems::FlexEnd, AlignItems::Stretch, ] { builder.spawn(NodeBundle { style: Style { align_items, flex_direction: FlexDirection::Column, justify_content: JustifyContent::SpaceBetween, size: Size::new(Val::Px(150.), Val::Px(500.)), ..Default::default() }, background_color: BackgroundColor(Color::DARK_GRAY), ..Default::default() }).with_children(|builder| { builder.spawn(( TextBundle { text: Text::from_section( format!("AlignItems::{align_items:?}"), TextStyle { font: asset_server.load("fonts/FiraSans-Regular.ttf"), font_size: 16.0, color: Color::BLACK, }, ), style: Style { align_self: AlignSelf::Stretch, ..Default::default() }, ..Default::default() }, BackgroundColor(Color::WHITE), )); for align_self in [ AlignSelf::Auto, AlignSelf::FlexStart, AlignSelf::Center, AlignSelf::FlexEnd, AlignSelf::Baseline, AlignSelf::Stretch, ] { builder.spawn(( TextBundle { text: Text::from_section( format!("AlignSelf::{align_self:?}"), TextStyle { font: asset_server.load("fonts/FiraSans-Regular.ttf"), font_size: 16.0, color: Color::WHITE, }, ), style: Style { align_self, ..Default::default() }, ..Default::default() }, BackgroundColor(Color::BLACK), )); } }); } }); } ```
2023-02-09 20:00:11 +00:00
/// How this item is aligned according to the cross axis.
/// Overrides [`AlignItems`].
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
2022-08-02 22:40:29 +00:00
#[reflect(PartialEq, Serialize, Deserialize)]
2020-07-26 19:27:09 +00:00
pub enum AlignSelf {
/// Use the parent node's [`AlignItems`] value to determine how this item should be aligned.
2020-07-26 19:27:09 +00:00
Auto,
/// This item will be aligned with the start of the axis.
Start,
/// This item will be aligned with the end of the axis.
End,
/// This item will be aligned with the start of the axis, unless the flex direction is reversed;
/// then it will be aligned with the end of the axis.
2020-07-26 19:27:09 +00:00
FlexStart,
/// This item will be aligned with the start of the axis, unless the flex direction is reversed;
/// then it will be aligned with the end of the axis.
2020-07-26 19:27:09 +00:00
FlexEnd,
/// This item will be aligned at the center.
2020-07-26 19:27:09 +00:00
Center,
/// This item will be aligned at the baseline.
2020-07-26 19:27:09 +00:00
Baseline,
/// This item will be stretched across the whole cross axis.
2020-07-26 19:27:09 +00:00
Stretch,
}
impl AlignSelf {
pub const DEFAULT: Self = Self::Auto;
}
impl Default for AlignSelf {
fn default() -> Self {
Self::DEFAULT
}
}
/// Defines how each line is aligned within the flexbox.
///
/// It only applies if [`FlexWrap::Wrap`] is present and if there are multiple lines of items.
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
2022-08-02 22:40:29 +00:00
#[reflect(PartialEq, Serialize, Deserialize)]
2020-07-26 19:27:09 +00:00
pub enum AlignContent {
/// Each line moves towards the start of the cross axis.
Start,
/// Each line moves towards the end of the cross axis.
End,
/// Each line moves towards the start of the cross axis, unless the flex direction is reversed; then the line moves towards the end of the cross axis.
2020-07-26 19:27:09 +00:00
FlexStart,
/// Each line moves towards the end of the cross axis, unless the flex direction is reversed; then the line moves towards the start of the cross axis.
2020-07-26 19:27:09 +00:00
FlexEnd,
/// Each line moves towards the center of the cross axis.
2020-07-26 19:27:09 +00:00
Center,
/// Each line will stretch to fill the remaining space.
2020-07-26 19:27:09 +00:00
Stretch,
/// Each line fills the space it needs, putting the remaining space, if any
/// inbetween the lines.
2020-07-26 19:27:09 +00:00
SpaceBetween,
/// The gap between the first and last items is exactly THE SAME as the gap between items.
/// The gaps are distributed evenly.
SpaceEvenly,
/// Each line fills the space it needs, putting the remaining space, if any
/// around the lines.
2020-07-26 19:27:09 +00:00
SpaceAround,
}
impl AlignContent {
pub const DEFAULT: Self = Self::Stretch;
}
impl Default for AlignContent {
fn default() -> Self {
Self::DEFAULT
}
}
/// Defines the text direction
///
/// For example English is written LTR (left-to-right) while Arabic is written RTL (right-to-left).
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
2022-08-02 22:40:29 +00:00
#[reflect(PartialEq, Serialize, Deserialize)]
2020-07-26 19:27:09 +00:00
pub enum Direction {
/// Inherit from parent node.
2020-07-26 19:27:09 +00:00
Inherit,
/// Text is written left to right.
LeftToRight,
/// Text is written right to left.
RightToLeft,
2020-07-26 19:27:09 +00:00
}
impl Direction {
pub const DEFAULT: Self = Self::Inherit;
}
impl Default for Direction {
fn default() -> Self {
Self::DEFAULT
}
}
Disable UI node `Interaction` when `ComputedVisibility` is false (#5361) # Objective UI nodes can be hidden by setting their `Visibility` property. Since #5310 was merged, this is now ergonomic to use, as visibility is now inherited. However, UI nodes still receive (and store) interactions when hidden, resulting in surprising hidden state (and an inability to otherwise disable UI nodes. ## Solution Fixes #5360. I've updated the `ui_focus_system` to accomplish this in a minimally intrusive way, and updated the docs to match. **NOTE:** I have not added automated tests to verify this behavior, as we do not currently have a good testing paradigm for `bevy_ui`. I'm not thrilled with that by any means, but I'm not sure fixing it is within scope. ## Paths not taken ### Separate `Disabled` component This is a much larger and more controversial change, and not well-scoped to UI. Furthermore, it is extremely rare that you want hidden UI elements to function: the most common cases are for things like changing tabs, collapsing elements or so on. Splitting this behavior would be more complex, and substantially violate user expectations. ### A separate limbo world Mentioned in the linked issue. Super cool, but all of the problems of the `Disabled` component solution with a whole new RFC-worth of complexity. ### Using change detection to reduce the amount of redundant work Adds a lot of complexity for questionable performance gains. Likely involves a complete refactor of the entire system. We simply don't have the tests or benchmarks here to justify this. ## Changelog - UI nodes are now always in an `Interaction::None` state while they are hidden (via the `ComputedVisibility` component).
2022-07-20 21:26:47 +00:00
/// Whether to use a Flexbox layout model.
///
/// Part of the [`Style`] component.
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
2022-08-02 22:40:29 +00:00
#[reflect(PartialEq, Serialize, Deserialize)]
2020-07-26 19:27:09 +00:00
pub enum Display {
Disable UI node `Interaction` when `ComputedVisibility` is false (#5361) # Objective UI nodes can be hidden by setting their `Visibility` property. Since #5310 was merged, this is now ergonomic to use, as visibility is now inherited. However, UI nodes still receive (and store) interactions when hidden, resulting in surprising hidden state (and an inability to otherwise disable UI nodes. ## Solution Fixes #5360. I've updated the `ui_focus_system` to accomplish this in a minimally intrusive way, and updated the docs to match. **NOTE:** I have not added automated tests to verify this behavior, as we do not currently have a good testing paradigm for `bevy_ui`. I'm not thrilled with that by any means, but I'm not sure fixing it is within scope. ## Paths not taken ### Separate `Disabled` component This is a much larger and more controversial change, and not well-scoped to UI. Furthermore, it is extremely rare that you want hidden UI elements to function: the most common cases are for things like changing tabs, collapsing elements or so on. Splitting this behavior would be more complex, and substantially violate user expectations. ### A separate limbo world Mentioned in the linked issue. Super cool, but all of the problems of the `Disabled` component solution with a whole new RFC-worth of complexity. ### Using change detection to reduce the amount of redundant work Adds a lot of complexity for questionable performance gains. Likely involves a complete refactor of the entire system. We simply don't have the tests or benchmarks here to justify this. ## Changelog - UI nodes are now always in an `Interaction::None` state while they are hidden (via the `ComputedVisibility` component).
2022-07-20 21:26:47 +00:00
/// Use Flexbox layout model to determine the position of this [`Node`].
2020-07-26 19:27:09 +00:00
Flex,
Disable UI node `Interaction` when `ComputedVisibility` is false (#5361) # Objective UI nodes can be hidden by setting their `Visibility` property. Since #5310 was merged, this is now ergonomic to use, as visibility is now inherited. However, UI nodes still receive (and store) interactions when hidden, resulting in surprising hidden state (and an inability to otherwise disable UI nodes. ## Solution Fixes #5360. I've updated the `ui_focus_system` to accomplish this in a minimally intrusive way, and updated the docs to match. **NOTE:** I have not added automated tests to verify this behavior, as we do not currently have a good testing paradigm for `bevy_ui`. I'm not thrilled with that by any means, but I'm not sure fixing it is within scope. ## Paths not taken ### Separate `Disabled` component This is a much larger and more controversial change, and not well-scoped to UI. Furthermore, it is extremely rare that you want hidden UI elements to function: the most common cases are for things like changing tabs, collapsing elements or so on. Splitting this behavior would be more complex, and substantially violate user expectations. ### A separate limbo world Mentioned in the linked issue. Super cool, but all of the problems of the `Disabled` component solution with a whole new RFC-worth of complexity. ### Using change detection to reduce the amount of redundant work Adds a lot of complexity for questionable performance gains. Likely involves a complete refactor of the entire system. We simply don't have the tests or benchmarks here to justify this. ## Changelog - UI nodes are now always in an `Interaction::None` state while they are hidden (via the `ComputedVisibility` component).
2022-07-20 21:26:47 +00:00
/// Use no layout, don't render this node and its children.
///
/// If you want to hide a node and its children,
/// but keep its layout in place, set its [`Visibility`](bevy_render::view::Visibility) component instead.
2020-07-26 19:27:09 +00:00
None,
}
impl Display {
pub const DEFAULT: Self = Self::Flex;
}
impl Default for Display {
fn default() -> Self {
Self::DEFAULT
}
}
/// Defines how flexbox items are ordered within a flexbox
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
2022-08-02 22:40:29 +00:00
#[reflect(PartialEq, Serialize, Deserialize)]
2020-07-26 19:27:09 +00:00
pub enum FlexDirection {
/// Same way as text direction along the main axis.
2020-07-26 19:27:09 +00:00
Row,
/// Flex from top to bottom.
2020-07-26 19:27:09 +00:00
Column,
/// Opposite way as text direction along the main axis.
2020-07-26 19:27:09 +00:00
RowReverse,
/// Flex from bottom to top.
2020-07-26 19:27:09 +00:00
ColumnReverse,
}
impl FlexDirection {
pub const DEFAULT: Self = Self::Row;
}
impl Default for FlexDirection {
fn default() -> Self {
Self::DEFAULT
}
}
/// Defines how items are aligned according to the main axis
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
2022-08-02 22:40:29 +00:00
#[reflect(PartialEq, Serialize, Deserialize)]
2020-07-26 19:27:09 +00:00
pub enum JustifyContent {
/// Items are packed toward the start of the axis.
Start,
/// Items are packed toward the end of the axis.
End,
/// Pushed towards the start, unless the flex direction is reversed; then pushed towards the end.
2020-07-26 19:27:09 +00:00
FlexStart,
/// Pushed towards the end, unless the flex direction is reversed; then pushed towards the start.
2020-07-26 19:27:09 +00:00
FlexEnd,
/// Centered along the main axis.
2020-07-26 19:27:09 +00:00
Center,
/// Remaining space is distributed between the items.
2020-07-26 19:27:09 +00:00
SpaceBetween,
/// Remaining space is distributed around the items.
2020-07-26 19:27:09 +00:00
SpaceAround,
/// Like [`JustifyContent::SpaceAround`] but with even spacing between items.
2020-07-26 19:27:09 +00:00
SpaceEvenly,
}
impl JustifyContent {
pub const DEFAULT: Self = Self::FlexStart;
}
impl Default for JustifyContent {
fn default() -> Self {
Self::DEFAULT
}
}
/// Whether to show or hide overflowing items
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect, Serialize, Deserialize)]
2022-08-02 22:40:29 +00:00
#[reflect(PartialEq, Serialize, Deserialize)]
pub enum Overflow {
/// Show overflowing items.
Visible,
/// Hide overflowing items.
Hidden,
}
2020-07-26 19:27:09 +00:00
impl Overflow {
pub const DEFAULT: Self = Self::Visible;
}
impl Default for Overflow {
fn default() -> Self {
Self::DEFAULT
}
}
/// The strategy used to position this node
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
2022-08-02 22:40:29 +00:00
#[reflect(PartialEq, Serialize, Deserialize)]
2020-07-26 19:27:09 +00:00
pub enum PositionType {
/// Relative to all other nodes with the [`PositionType::Relative`] value.
2020-07-26 19:27:09 +00:00
Relative,
/// Independent of all other nodes.
///
/// As usual, the `Style.position` field of this node is specified relative to its parent node.
2020-07-26 19:27:09 +00:00
Absolute,
}
impl PositionType {
const DEFAULT: Self = Self::Relative;
}
impl Default for PositionType {
fn default() -> Self {
Self::DEFAULT
}
}
/// Defines if flexbox items appear on a single line or on multiple lines
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
2022-08-02 22:40:29 +00:00
#[reflect(PartialEq, Serialize, Deserialize)]
2020-07-26 19:27:09 +00:00
pub enum FlexWrap {
/// Single line, will overflow if needed.
2020-07-26 19:27:09 +00:00
NoWrap,
/// Multiple lines, if needed.
2020-07-26 19:27:09 +00:00
Wrap,
/// Same as [`FlexWrap::Wrap`] but new lines will appear before the previous one.
2020-07-26 19:27:09 +00:00
WrapReverse,
}
impl FlexWrap {
const DEFAULT: Self = Self::NoWrap;
}
impl Default for FlexWrap {
fn default() -> Self {
Self::DEFAULT
}
}
/// The calculated size of the node
#[derive(Component, Copy, Clone, Debug, Reflect)]
#[reflect(Component)]
pub struct CalculatedSize {
/// The size of the node in logical pixels
pub size: Vec2,
/// Whether to attempt to preserve the aspect ratio when determining the layout for this item
pub preserve_aspect_ratio: bool,
}
impl CalculatedSize {
const DEFAULT: Self = Self {
size: Vec2::ZERO,
preserve_aspect_ratio: false,
};
}
impl Default for CalculatedSize {
fn default() -> Self {
Self::DEFAULT
}
}
/// The background color of the node
///
/// This serves as the "fill" color.
/// When combined with [`UiImage`], tints the provided texture.
#[derive(Component, Copy, Clone, Debug, Reflect)]
add `#[reflect(Default)]` to create default value for reflected types (#3733) ### Problem It currently isn't possible to construct the default value of a reflected type. Because of that, it isn't possible to use `add_component` of `ReflectComponent` to add a new component to an entity because you can't know what the initial value should be. ### Solution 1. add `ReflectDefault` type ```rust #[derive(Clone)] pub struct ReflectDefault { default: fn() -> Box<dyn Reflect>, } impl ReflectDefault { pub fn default(&self) -> Box<dyn Reflect> { (self.default)() } } impl<T: Reflect + Default> FromType<T> for ReflectDefault { fn from_type() -> Self { ReflectDefault { default: || Box::new(T::default()), } } } ``` 2. add `#[reflect(Default)]` to all component types that implement `Default` and are user facing (so not `ComputedSize`, `CubemapVisibleEntities` etc.) This makes it possible to add the default value of a component to an entity without any compile-time information: ```rust fn main() { let mut app = App::new(); app.register_type::<Camera>(); let type_registry = app.world.get_resource::<TypeRegistry>().unwrap(); let type_registry = type_registry.read(); let camera_registration = type_registry.get(std::any::TypeId::of::<Camera>()).unwrap(); let reflect_default = camera_registration.data::<ReflectDefault>().unwrap(); let reflect_component = camera_registration .data::<ReflectComponent>() .unwrap() .clone(); let default = reflect_default.default(); drop(type_registry); let entity = app.world.spawn().id(); reflect_component.add_component(&mut app.world, entity, &*default); let camera = app.world.entity(entity).get::<Camera>().unwrap(); dbg!(&camera); } ``` ### Open questions - should we have `ReflectDefault` or `ReflectFromWorld` or both?
2022-05-03 19:20:13 +00:00
#[reflect(Component, Default)]
pub struct BackgroundColor(pub Color);
impl BackgroundColor {
pub const DEFAULT: Self = Self(Color::WHITE);
}
impl Default for BackgroundColor {
fn default() -> Self {
Self::DEFAULT
}
}
impl From<Color> for BackgroundColor {
fn from(color: Color) -> Self {
Self(color)
}
}
/// The 2D texture displayed for this UI node
#[derive(Component, Clone, Debug, Reflect)]
add `#[reflect(Default)]` to create default value for reflected types (#3733) ### Problem It currently isn't possible to construct the default value of a reflected type. Because of that, it isn't possible to use `add_component` of `ReflectComponent` to add a new component to an entity because you can't know what the initial value should be. ### Solution 1. add `ReflectDefault` type ```rust #[derive(Clone)] pub struct ReflectDefault { default: fn() -> Box<dyn Reflect>, } impl ReflectDefault { pub fn default(&self) -> Box<dyn Reflect> { (self.default)() } } impl<T: Reflect + Default> FromType<T> for ReflectDefault { fn from_type() -> Self { ReflectDefault { default: || Box::new(T::default()), } } } ``` 2. add `#[reflect(Default)]` to all component types that implement `Default` and are user facing (so not `ComputedSize`, `CubemapVisibleEntities` etc.) This makes it possible to add the default value of a component to an entity without any compile-time information: ```rust fn main() { let mut app = App::new(); app.register_type::<Camera>(); let type_registry = app.world.get_resource::<TypeRegistry>().unwrap(); let type_registry = type_registry.read(); let camera_registration = type_registry.get(std::any::TypeId::of::<Camera>()).unwrap(); let reflect_default = camera_registration.data::<ReflectDefault>().unwrap(); let reflect_component = camera_registration .data::<ReflectComponent>() .unwrap() .clone(); let default = reflect_default.default(); drop(type_registry); let entity = app.world.spawn().id(); reflect_component.add_component(&mut app.world, entity, &*default); let camera = app.world.entity(entity).get::<Camera>().unwrap(); dbg!(&camera); } ``` ### Open questions - should we have `ReflectDefault` or `ReflectFromWorld` or both?
2022-05-03 19:20:13 +00:00
#[reflect(Component, Default)]
pub struct UiImage {
/// Handle to the texture
pub texture: Handle<Image>,
/// Whether the image should be flipped along its x-axis
pub flip_x: bool,
/// Whether the image should be flipped along its y-axis
pub flip_y: bool,
}
impl Default for UiImage {
fn default() -> UiImage {
UiImage {
texture: DEFAULT_IMAGE_HANDLE.typed(),
flip_x: false,
flip_y: false,
}
}
}
impl UiImage {
pub fn new(texture: Handle<Image>) -> Self {
Self {
texture,
..Default::default()
}
}
}
impl From<Handle<Image>> for UiImage {
fn from(texture: Handle<Image>) -> Self {
Self::new(texture)
}
}
/// The calculated clip of the node
#[derive(Component, Default, Copy, Clone, Debug, Reflect)]
#[reflect(Component)]
pub struct CalculatedClip {
/// The rect of the clip
Move `sprite::Rect` into `bevy_math` (#5686) # Objective Promote the `Rect` utility of `sprite::Rect`, which defines a rectangle by its minimum and maximum corners, to the `bevy_math` crate to make it available as a general math type to all crates without the need to depend on the `bevy_sprite` crate. Fixes #5575 ## Solution Move `sprite::Rect` into `bevy_math` and fix all uses. Implement `Reflect` for `Rect` directly into the `bevy_reflect` crate by having `bevy_reflect` depend on `bevy_math`. This looks like a new dependency, but the `bevy_reflect` was "cheating" for other math types by directly depending on `glam` to reflect other math types, thereby giving the illusion that there was no dependency on `bevy_math`. In practice conceptually Bevy's math types are reflected into the `bevy_reflect` crate to avoid a dependency of that crate to a "lower level" utility crate like `bevy_math` (which in turn would make `bevy_reflect` be a dependency of most other crates, and increase the risk of circular dependencies). So this change simply formalizes that dependency in `Cargo.toml`. The `Rect` struct is also augmented in this change with a collection of utility methods to improve its usability. A few uses cases are updated to use those new methods, resulting is more clear and concise syntax. --- ## Changelog ### Changed - Moved the `sprite::Rect` type into `bevy_math`. ### Added - Added several utility methods to the `math::Rect` type. ## Migration Guide The `bevy::sprite::Rect` type moved to the math utility crate as `bevy::math::Rect`. You should change your imports from `use bevy::sprite::Rect` to `use bevy::math::Rect`.
2022-09-02 12:35:23 +00:00
pub clip: Rect,
}
Add z-index support with a predictable UI stack (#5877) # Objective Add consistent UI rendering and interaction where deep nodes inside two different hierarchies will never render on top of one-another by default and offer an escape hatch (z-index) for nodes to change their depth. ## The problem with current implementation The current implementation of UI rendering is broken in that regard, mainly because [it sets the Z value of the `Transform` component based on a "global Z" space](https://github.com/bevyengine/bevy/blob/main/crates/bevy_ui/src/update.rs#L43) shared by all nodes in the UI. This doesn't account for the fact that each node's final `GlobalTransform` value will be relative to its parent. This effectively makes the depth unpredictable when two deep trees are rendered on top of one-another. At the moment, it's also up to each part of the UI code to sort all of the UI nodes. The solution that's offered here does the full sorting of UI node entities once and offers the result through a resource so that all systems can use it. ## Solution ### New ZIndex component This adds a new optional `ZIndex` enum component for nodes which offers two mechanism: - `ZIndex::Local(i32)`: Overrides the depth of the node relative to its siblings. - `ZIndex::Global(i32)`: Overrides the depth of the node relative to the UI root. This basically allows any node in the tree to "escape" the parent and be ordered relative to the entire UI. Note that in the current implementation, omitting `ZIndex` on a node has the same result as adding `ZIndex::Local(0)`. Additionally, the "global" stacking context is essentially a way to add your node to the root stacking context, so using `ZIndex::Local(n)` on a root node (one without parent) will share that space with all nodes using `Index::Global(n)`. ### New UiStack resource This adds a new `UiStack` resource which is calculated from both hierarchy and `ZIndex` during UI update and contains a vector of all node entities in the UI, ordered by depth (from farthest from camera to closest). This is exposed publicly by the bevy_ui crate with the hope that it can be used for consistent ordering and to reduce the amount of sorting that needs to be done by UI systems (i.e. instead of sorting everything by `global_transform.z` in every system, this array can be iterated over). ### New z_index example This also adds a new z_index example that showcases the new `ZIndex` component. It's also a good general demo of the new UI stack system, because making this kind of UI was very broken with the old system (e.g. nodes would render on top of each other, not respecting hierarchy or insert order at all). ![image](https://user-images.githubusercontent.com/1060971/189015985-8ea8f989-0e9d-4601-a7e0-4a27a43a53f9.png) --- ## Changelog - Added the `ZIndex` component to bevy_ui. - Added the `UiStack` resource to bevy_ui, and added implementation in a new `stack.rs` module. - Removed the previous Z updating system from bevy_ui, because it was replaced with the above. - Changed bevy_ui rendering to use UiStack instead of z ordering. - Changed bevy_ui focus/interaction system to use UiStack instead of z ordering. - Added a new z_index example. ## ZIndex demo Here's a demo I wrote to test these features https://user-images.githubusercontent.com/1060971/188329295-d7beebd6-9aee-43ab-821e-d437df5dbe8a.mp4 Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-11-02 22:06:04 +00:00
/// Indicates that this [`Node`] entity's front-to-back ordering is not controlled solely
/// by its location in the UI hierarchy. A node with a higher z-index will appear on top
/// of other nodes with a lower z-index.
///
/// UI nodes that have the same z-index will appear according to the order in which they
/// appear in the UI hierarchy. In such a case, the last node to be added to its parent
/// will appear in front of this parent's other children.
///
/// Internally, nodes with a global z-index share the stacking context of root UI nodes
/// (nodes that have no parent). Because of this, there is no difference between using
/// [`ZIndex::Local(n)`] and [`ZIndex::Global(n)`] for root nodes.
///
/// Nodes without this component will be treated as if they had a value of [`ZIndex::Local(0)`].
#[derive(Component, Copy, Clone, Debug, Reflect)]
pub enum ZIndex {
/// Indicates the order in which this node should be rendered relative to its siblings.
Local(i32),
/// Indicates the order in which this node should be rendered relative to root nodes and
/// all other nodes that have a global z-index.
Global(i32),
}
impl Default for ZIndex {
fn default() -> Self {
Self::Local(0)
}
}
#[cfg(test)]
mod tests {
use crate::ValArithmeticError;
use super::Val;
#[test]
fn val_try_add() {
let undefined_sum = Val::Undefined.try_add(Val::Undefined).unwrap();
let auto_sum = Val::Auto.try_add(Val::Auto).unwrap();
let px_sum = Val::Px(20.).try_add(Val::Px(22.)).unwrap();
let percent_sum = Val::Percent(50.).try_add(Val::Percent(50.)).unwrap();
assert_eq!(undefined_sum, Val::Undefined);
assert_eq!(auto_sum, Val::Auto);
assert_eq!(px_sum, Val::Px(42.));
assert_eq!(percent_sum, Val::Percent(100.));
}
#[test]
fn val_try_add_to_self() {
let mut val = Val::Px(5.);
val.try_add_assign(Val::Px(3.)).unwrap();
assert_eq!(val, Val::Px(8.));
}
#[test]
fn val_try_sub() {
let undefined_sum = Val::Undefined.try_sub(Val::Undefined).unwrap();
let auto_sum = Val::Auto.try_sub(Val::Auto).unwrap();
let px_sum = Val::Px(72.).try_sub(Val::Px(30.)).unwrap();
let percent_sum = Val::Percent(100.).try_sub(Val::Percent(50.)).unwrap();
assert_eq!(undefined_sum, Val::Undefined);
assert_eq!(auto_sum, Val::Auto);
assert_eq!(px_sum, Val::Px(42.));
assert_eq!(percent_sum, Val::Percent(50.));
}
#[test]
fn different_variant_val_try_add() {
let different_variant_sum_1 = Val::Undefined.try_add(Val::Auto);
let different_variant_sum_2 = Val::Px(50.).try_add(Val::Percent(50.));
let different_variant_sum_3 = Val::Percent(50.).try_add(Val::Undefined);
assert_eq!(
different_variant_sum_1,
Err(ValArithmeticError::NonIdenticalVariants)
);
assert_eq!(
different_variant_sum_2,
Err(ValArithmeticError::NonIdenticalVariants)
);
assert_eq!(
different_variant_sum_3,
Err(ValArithmeticError::NonIdenticalVariants)
);
}
#[test]
fn different_variant_val_try_sub() {
let different_variant_diff_1 = Val::Undefined.try_sub(Val::Auto);
let different_variant_diff_2 = Val::Px(50.).try_sub(Val::Percent(50.));
let different_variant_diff_3 = Val::Percent(50.).try_sub(Val::Undefined);
assert_eq!(
different_variant_diff_1,
Err(ValArithmeticError::NonIdenticalVariants)
);
assert_eq!(
different_variant_diff_2,
Err(ValArithmeticError::NonIdenticalVariants)
);
assert_eq!(
different_variant_diff_3,
Err(ValArithmeticError::NonIdenticalVariants)
);
}
#[test]
fn val_evaluate() {
let size = 250.;
let result = Val::Percent(80.).evaluate(size).unwrap();
assert_eq!(result, size * 0.8);
}
#[test]
fn val_evaluate_px() {
let size = 250.;
let result = Val::Px(10.).evaluate(size).unwrap();
assert_eq!(result, 10.);
}
#[test]
fn val_invalid_evaluation() {
let size = 250.;
let evaluate_undefined = Val::Undefined.evaluate(size);
let evaluate_auto = Val::Auto.evaluate(size);
assert_eq!(evaluate_undefined, Err(ValArithmeticError::NonEvaluateable));
assert_eq!(evaluate_auto, Err(ValArithmeticError::NonEvaluateable));
}
#[test]
fn val_try_add_with_size() {
let size = 250.;
let px_sum = Val::Px(21.).try_add_with_size(Val::Px(21.), size).unwrap();
let percent_sum = Val::Percent(20.)
.try_add_with_size(Val::Percent(30.), size)
.unwrap();
let mixed_sum = Val::Px(20.)
.try_add_with_size(Val::Percent(30.), size)
.unwrap();
assert_eq!(px_sum, 42.);
assert_eq!(percent_sum, 0.5 * size);
assert_eq!(mixed_sum, 20. + 0.3 * size);
}
#[test]
fn val_try_sub_with_size() {
let size = 250.;
let px_sum = Val::Px(60.).try_sub_with_size(Val::Px(18.), size).unwrap();
let percent_sum = Val::Percent(80.)
.try_sub_with_size(Val::Percent(30.), size)
.unwrap();
let mixed_sum = Val::Percent(50.)
.try_sub_with_size(Val::Px(30.), size)
.unwrap();
assert_eq!(px_sum, 42.);
assert_eq!(percent_sum, 0.5 * size);
assert_eq!(mixed_sum, 0.5 * size - 30.);
}
#[test]
fn val_try_add_non_numeric_with_size() {
let size = 250.;
let undefined_sum = Val::Undefined.try_add_with_size(Val::Undefined, size);
let percent_sum = Val::Auto.try_add_with_size(Val::Auto, size);
assert_eq!(undefined_sum, Err(ValArithmeticError::NonEvaluateable));
assert_eq!(percent_sum, Err(ValArithmeticError::NonEvaluateable));
}
#[test]
fn val_arithmetic_error_messages() {
assert_eq!(
format!("{}", ValArithmeticError::NonIdenticalVariants),
"the variants of the Vals don't match"
);
assert_eq!(
format!("{}", ValArithmeticError::NonEvaluateable),
"the given variant of Val is not evaluateable (non-numeric)"
);
}
}