mirror of
https://github.com/bevyengine/bevy
synced 2025-01-06 10:18:59 +00:00
ffc62c1a81
# Objective `text_system` runs before the UI layout is calculated and the size of the text node is determined, so it cannot correctly shape the text to fit the layout, and has no way of determining if the text needs to be wrapped. The function `text_constraint` attempts to determine the size of the node from the local size constraints in the `Style` component. It can't be made to work, you have to compute the whole layout to get the correct size. A simple example of where this fails completely is a text node set to stretch to fill the empty space adjacent to a node with size constraints set to `Val::Percent(50.)`. The text node will take up half the space, even though its size constraints are `Val::Auto` Also because the `text_system` queries for changes to the `Style` component, when a style value is changed that doesn't affect the node's geometry the text is recomputed unnecessarily. Querying on changes to `Node` is not much better. The UI layout is changed to fit the `CalculatedSize` of the text, so the size of the node is changed and so the text and UI layout get recalculated multiple times from a single change to a `Text`. Also, the `MeasureFunc` doesn't work at all, it doesn't have enough information to fit the text correctly and makes no attempt. Fixes #7663, #6717, #5834, #1490, ## Solution Split the `text_system` into two functions: * `measure_text_system` which calculates the size constraints for the text node and runs before `UiSystem::Flex` * `text_system` which runs after `UiSystem::Flex` and generates the actual text. * Fix the `MeasureFunc` calculations. --- Text wrapping in main: <img width="961" alt="Capturemain" src="https://user-images.githubusercontent.com/27962798/220425740-4fe4bf46-24fb-4685-a1cf-bc01e139e72d.PNG"> With this PR: <img width="961" alt="captured_wrap" src="https://user-images.githubusercontent.com/27962798/220425807-949996b0-f127-4637-9f33-56a6da944fb0.PNG"> ## Changelog * Removed the previous fields from `CalculatedSize`. `CalculatedSize` now contains a boxed `Measure`. * Added `measurement` module to `bevy_ui`. * Added the method `create_text_measure` to `TextPipeline`. * Added a new system `measure_text_system` that runs before `UiSystem::Flex` that creates a `MeasureFunc` for the text. * Rescheduled `text_system` to run after `UiSystem::Flex`. * Added a trait `Measure`. A `Measure` is used to compute the size of a UI node when the size of that node is based on its content. * Added `ImageMeasure` and `TextMeasure` which implement `Measure`. * Added a new component `UiImageSize` which is used by `update_image_calculated_size_system` to track image size changes. * Added a `UiImageSize` component to `ImageBundle`. ## Migration Guide `ImageBundle` has a new component `UiImageSize` which contains the size of the image bundle's texture and is updated automatically by `update_image_calculated_size_system` --------- Co-authored-by: François <mockersf@gmail.com>
255 lines
9.5 KiB
Rust
255 lines
9.5 KiB
Rust
//! This module contains basic node bundles used to build UIs
|
|
|
|
use crate::{
|
|
widget::{Button, UiImageSize},
|
|
BackgroundColor, CalculatedSize, FocusPolicy, Interaction, Node, Style, UiImage, ZIndex,
|
|
};
|
|
use bevy_ecs::bundle::Bundle;
|
|
use bevy_render::{
|
|
prelude::{Color, ComputedVisibility},
|
|
view::Visibility,
|
|
};
|
|
#[cfg(feature = "bevy_text")]
|
|
use bevy_text::{Text, TextAlignment, TextLayoutInfo, TextSection, TextStyle};
|
|
use bevy_transform::prelude::{GlobalTransform, Transform};
|
|
|
|
/// The basic UI node
|
|
///
|
|
/// Useful as a container for a variety of child nodes.
|
|
#[derive(Bundle, Clone, Debug)]
|
|
pub struct NodeBundle {
|
|
/// Describes the logical size of the node
|
|
pub node: Node,
|
|
/// Describes the style including flexbox settings
|
|
pub style: Style,
|
|
/// The background color, which serves as a "fill" for this node
|
|
pub background_color: BackgroundColor,
|
|
/// Whether this node should block interaction with lower nodes
|
|
pub focus_policy: FocusPolicy,
|
|
/// The transform of the node
|
|
///
|
|
/// This field is automatically managed by the UI layout system.
|
|
/// To alter the position of the `nodebundle`, use the properties of the [`Style`] component.
|
|
pub transform: Transform,
|
|
/// The global transform of the node
|
|
///
|
|
/// This field is automatically managed by the UI layout system.
|
|
/// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.
|
|
pub global_transform: GlobalTransform,
|
|
/// Describes the visibility properties of the node
|
|
pub visibility: Visibility,
|
|
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
|
|
pub computed_visibility: ComputedVisibility,
|
|
/// Indicates the depth at which the node should appear in the UI
|
|
pub z_index: ZIndex,
|
|
}
|
|
|
|
impl Default for NodeBundle {
|
|
fn default() -> Self {
|
|
NodeBundle {
|
|
// Transparent background
|
|
background_color: Color::NONE.into(),
|
|
node: Default::default(),
|
|
style: Default::default(),
|
|
focus_policy: Default::default(),
|
|
transform: Default::default(),
|
|
global_transform: Default::default(),
|
|
visibility: Default::default(),
|
|
computed_visibility: Default::default(),
|
|
z_index: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A UI node that is an image
|
|
#[derive(Bundle, Clone, Debug, Default)]
|
|
pub struct ImageBundle {
|
|
/// Describes the logical size of the node
|
|
pub node: Node,
|
|
/// Describes the style including flexbox settings
|
|
pub style: Style,
|
|
/// The calculated size based on the given image
|
|
pub calculated_size: CalculatedSize,
|
|
/// The background color, which serves as a "fill" for this node
|
|
///
|
|
/// Combines with `UiImage` to tint the provided image.
|
|
pub background_color: BackgroundColor,
|
|
/// The image of the node
|
|
pub image: UiImage,
|
|
/// The size of the image in pixels
|
|
///
|
|
/// This field is set automatically
|
|
pub image_size: UiImageSize,
|
|
/// Whether this node should block interaction with lower nodes
|
|
pub focus_policy: FocusPolicy,
|
|
/// The transform of the node
|
|
///
|
|
/// This field is automatically managed by the UI layout system.
|
|
/// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.
|
|
pub transform: Transform,
|
|
/// The global transform of the node
|
|
///
|
|
/// This field is automatically managed by the UI layout system.
|
|
/// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.
|
|
pub global_transform: GlobalTransform,
|
|
/// Describes the visibility properties of the node
|
|
pub visibility: Visibility,
|
|
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
|
|
pub computed_visibility: ComputedVisibility,
|
|
/// Indicates the depth at which the node should appear in the UI
|
|
pub z_index: ZIndex,
|
|
}
|
|
|
|
#[cfg(feature = "bevy_text")]
|
|
/// A UI node that is text
|
|
#[derive(Bundle, Clone, Debug)]
|
|
pub struct TextBundle {
|
|
/// Describes the logical size of the node
|
|
pub node: Node,
|
|
/// Describes the style including flexbox settings
|
|
pub style: Style,
|
|
/// Contains the text of the node
|
|
pub text: Text,
|
|
/// Text layout information
|
|
pub text_layout_info: TextLayoutInfo,
|
|
/// The calculated size based on the given image
|
|
pub calculated_size: CalculatedSize,
|
|
/// Whether this node should block interaction with lower nodes
|
|
pub focus_policy: FocusPolicy,
|
|
/// The transform of the node
|
|
///
|
|
/// This field is automatically managed by the UI layout system.
|
|
/// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.
|
|
pub transform: Transform,
|
|
/// The global transform of the node
|
|
///
|
|
/// This field is automatically managed by the UI layout system.
|
|
/// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.
|
|
pub global_transform: GlobalTransform,
|
|
/// Describes the visibility properties of the node
|
|
pub visibility: Visibility,
|
|
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
|
|
pub computed_visibility: ComputedVisibility,
|
|
/// Indicates the depth at which the node should appear in the UI
|
|
pub z_index: ZIndex,
|
|
/// The background color that will fill the containing node
|
|
pub background_color: BackgroundColor,
|
|
}
|
|
|
|
#[cfg(feature = "bevy_text")]
|
|
impl Default for TextBundle {
|
|
fn default() -> Self {
|
|
Self {
|
|
text: Default::default(),
|
|
text_layout_info: Default::default(),
|
|
calculated_size: Default::default(),
|
|
// Transparent background
|
|
background_color: BackgroundColor(Color::NONE),
|
|
node: Default::default(),
|
|
style: Default::default(),
|
|
focus_policy: Default::default(),
|
|
transform: Default::default(),
|
|
global_transform: Default::default(),
|
|
visibility: Default::default(),
|
|
computed_visibility: Default::default(),
|
|
z_index: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "bevy_text")]
|
|
impl TextBundle {
|
|
/// Create a [`TextBundle`] from a single section.
|
|
///
|
|
/// See [`Text::from_section`] for usage.
|
|
pub fn from_section(value: impl Into<String>, style: TextStyle) -> Self {
|
|
Self {
|
|
text: Text::from_section(value, style),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Create a [`TextBundle`] from a list of sections.
|
|
///
|
|
/// See [`Text::from_sections`] for usage.
|
|
pub fn from_sections(sections: impl IntoIterator<Item = TextSection>) -> Self {
|
|
Self {
|
|
text: Text::from_sections(sections),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Returns this [`TextBundle`] with a new [`TextAlignment`] on [`Text`].
|
|
pub const fn with_text_alignment(mut self, alignment: TextAlignment) -> Self {
|
|
self.text.alignment = alignment;
|
|
self
|
|
}
|
|
|
|
/// Returns this [`TextBundle`] with a new [`Style`].
|
|
pub const fn with_style(mut self, style: Style) -> Self {
|
|
self.style = style;
|
|
self
|
|
}
|
|
|
|
/// Returns this [`TextBundle`] with a new [`BackgroundColor`].
|
|
pub const fn with_background_color(mut self, color: Color) -> Self {
|
|
self.background_color = BackgroundColor(color);
|
|
self
|
|
}
|
|
}
|
|
|
|
/// A UI node that is a button
|
|
#[derive(Bundle, Clone, Debug)]
|
|
pub struct ButtonBundle {
|
|
/// Describes the logical size of the node
|
|
pub node: Node,
|
|
/// Marker component that signals this node is a button
|
|
pub button: Button,
|
|
/// Describes the style including flexbox settings
|
|
pub style: Style,
|
|
/// Describes whether and how the button has been interacted with by the input
|
|
pub interaction: Interaction,
|
|
/// Whether this node should block interaction with lower nodes
|
|
pub focus_policy: FocusPolicy,
|
|
/// The background color, which serves as a "fill" for this node
|
|
///
|
|
/// When combined with `UiImage`, tints the provided image.
|
|
pub background_color: BackgroundColor,
|
|
/// The image of the node
|
|
pub image: UiImage,
|
|
/// The transform of the node
|
|
///
|
|
/// This field is automatically managed by the UI layout system.
|
|
/// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.
|
|
pub transform: Transform,
|
|
/// The global transform of the node
|
|
///
|
|
/// This field is automatically managed by the UI layout system.
|
|
/// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.
|
|
pub global_transform: GlobalTransform,
|
|
/// Describes the visibility properties of the node
|
|
pub visibility: Visibility,
|
|
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
|
|
pub computed_visibility: ComputedVisibility,
|
|
/// Indicates the depth at which the node should appear in the UI
|
|
pub z_index: ZIndex,
|
|
}
|
|
|
|
impl Default for ButtonBundle {
|
|
fn default() -> Self {
|
|
Self {
|
|
focus_policy: FocusPolicy::Block,
|
|
node: Default::default(),
|
|
button: Default::default(),
|
|
style: Default::default(),
|
|
interaction: Default::default(),
|
|
background_color: Default::default(),
|
|
image: Default::default(),
|
|
transform: Default::default(),
|
|
global_transform: Default::default(),
|
|
visibility: Default::default(),
|
|
computed_visibility: Default::default(),
|
|
z_index: Default::default(),
|
|
}
|
|
}
|
|
}
|