2023-03-18 01:45:34 +00:00
|
|
|
use crate::{
|
Mark ghost nodes as experimental and partially feature flag them (#15961)
# Objective
As discussed in #15341, ghost nodes are a contentious and experimental
feature. In the interest of enabling ecosystem experimentation, we've
decided to keep them in Bevy 0.15.
That said, we don't use them internally, and don't expect third-party
crates to support them. If the experimentation returns a negative result
(they aren't very useful, an alternative design is preferred etc) they
will be removed.
We should clearly communicate this status to users, and make sure that
users don't use ghost nodes in their projects without a very clear
understanding of what they're getting themselves into.
## Solution
To make life easy for users (and Bevy), `GhostNode` and all associated
helpers remain public and are always available.
However, actually constructing these requires enabling a feature flag
that's clearly marked as experimental. To do so, I've added a
meaningless private field.
When the feature flag is enabled, our constructs (`new` and `default`)
can be used. I've added a `new` constructor, which should be preferred
over `Default::default` as that can be readily deprecated, allowing us
to prompt users to swap over to the much nicer `GhostNode` syntax once
this is a unit struct again.
Full credit: this was mostly @cart's design: I'm just implementing it!
## Testing
I've run the ghost_nodes example and it fails to compile without the
feature flag. With the feature flag, it works fine :)
---------
Co-authored-by: Zachary Harrold <zac@harrold.com.au>
2024-10-16 22:20:48 +00:00
|
|
|
experimental::UiChildren,
|
2023-03-18 01:45:34 +00:00
|
|
|
prelude::{Button, Label},
|
2024-11-07 21:52:58 +00:00
|
|
|
widget::{ImageNode, TextUiReader},
|
2024-10-27 19:14:46 +00:00
|
|
|
ComputedNode,
|
2023-03-18 01:45:34 +00:00
|
|
|
};
|
2023-03-01 22:45:04 +00:00
|
|
|
use bevy_a11y::{
|
2024-11-04 20:07:38 +00:00
|
|
|
accesskit::{Node, Rect, Role},
|
2023-03-01 22:45:04 +00:00
|
|
|
AccessibilityNode,
|
|
|
|
};
|
2023-05-23 23:50:48 +00:00
|
|
|
use bevy_app::{App, Plugin, PostUpdate};
|
2023-03-01 22:45:04 +00:00
|
|
|
use bevy_ecs::{
|
2023-05-08 20:49:55 +00:00
|
|
|
prelude::{DetectChanges, Entity},
|
|
|
|
query::{Changed, Without},
|
2023-05-23 23:50:48 +00:00
|
|
|
schedule::IntoSystemConfigs,
|
2023-03-01 22:45:04 +00:00
|
|
|
system::{Commands, Query},
|
2023-05-08 20:49:55 +00:00
|
|
|
world::Ref,
|
2023-03-01 22:45:04 +00:00
|
|
|
};
|
2024-01-09 19:08:15 +00:00
|
|
|
use bevy_render::{camera::CameraUpdateSystem, prelude::Camera};
|
2023-03-01 22:45:04 +00:00
|
|
|
use bevy_transform::prelude::GlobalTransform;
|
|
|
|
|
2024-11-04 20:07:38 +00:00
|
|
|
fn calc_label(
|
2024-10-15 02:32:34 +00:00
|
|
|
text_reader: &mut TextUiReader,
|
Text rework (#15591)
**Ready for review. Examples migration progress: 100%.**
# Objective
- Implement https://github.com/bevyengine/bevy/discussions/15014
## Solution
This implements [cart's
proposal](https://github.com/bevyengine/bevy/discussions/15014#discussioncomment-10574459)
faithfully except for one change. I separated `TextSpan` from
`TextSpan2d` because `TextSpan` needs to require the `GhostNode`
component, which is a `bevy_ui` component only usable by UI.
Extra changes:
- Added `EntityCommands::commands_mut` that returns a mutable reference.
This is a blocker for extension methods that return something other than
`self`. Note that `sickle_ui`'s `UiBuilder::commands` returns a mutable
reference for this reason.
## Testing
- [x] Text examples all work.
---
## Showcase
TODO: showcase-worthy
## Migration Guide
TODO: very breaking
### Accessing text spans by index
Text sections are now text sections on different entities in a
hierarchy, Use the new `TextReader` and `TextWriter` system parameters
to access spans by index.
Before:
```rust
fn refresh_text(mut query: Query<&mut Text, With<TimeText>>, time: Res<Time>) {
let text = query.single_mut();
text.sections[1].value = format_time(time.elapsed());
}
```
After:
```rust
fn refresh_text(
query: Query<Entity, With<TimeText>>,
mut writer: UiTextWriter,
time: Res<Time>
) {
let entity = query.single();
*writer.text(entity, 1) = format_time(time.elapsed());
}
```
### Iterating text spans
Text spans are now entities in a hierarchy, so the new `UiTextReader`
and `UiTextWriter` system parameters provide ways to iterate that
hierarchy. The `UiTextReader::iter` method will give you a normal
iterator over spans, and `UiTextWriter::for_each` lets you visit each of
the spans.
---------
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-10-09 18:35:36 +00:00
|
|
|
children: impl Iterator<Item = Entity>,
|
|
|
|
) -> Option<Box<str>> {
|
2023-03-01 22:45:04 +00:00
|
|
|
let mut name = None;
|
2023-09-19 03:35:22 +00:00
|
|
|
for child in children {
|
Text rework (#15591)
**Ready for review. Examples migration progress: 100%.**
# Objective
- Implement https://github.com/bevyengine/bevy/discussions/15014
## Solution
This implements [cart's
proposal](https://github.com/bevyengine/bevy/discussions/15014#discussioncomment-10574459)
faithfully except for one change. I separated `TextSpan` from
`TextSpan2d` because `TextSpan` needs to require the `GhostNode`
component, which is a `bevy_ui` component only usable by UI.
Extra changes:
- Added `EntityCommands::commands_mut` that returns a mutable reference.
This is a blocker for extension methods that return something other than
`self`. Note that `sickle_ui`'s `UiBuilder::commands` returns a mutable
reference for this reason.
## Testing
- [x] Text examples all work.
---
## Showcase
TODO: showcase-worthy
## Migration Guide
TODO: very breaking
### Accessing text spans by index
Text sections are now text sections on different entities in a
hierarchy, Use the new `TextReader` and `TextWriter` system parameters
to access spans by index.
Before:
```rust
fn refresh_text(mut query: Query<&mut Text, With<TimeText>>, time: Res<Time>) {
let text = query.single_mut();
text.sections[1].value = format_time(time.elapsed());
}
```
After:
```rust
fn refresh_text(
query: Query<Entity, With<TimeText>>,
mut writer: UiTextWriter,
time: Res<Time>
) {
let entity = query.single();
*writer.text(entity, 1) = format_time(time.elapsed());
}
```
### Iterating text spans
Text spans are now entities in a hierarchy, so the new `UiTextReader`
and `UiTextWriter` system parameters provide ways to iterate that
hierarchy. The `UiTextReader::iter` method will give you a normal
iterator over spans, and `UiTextWriter::for_each` lets you visit each of
the spans.
---------
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-10-09 18:35:36 +00:00
|
|
|
let values = text_reader
|
|
|
|
.iter(child)
|
2024-10-13 17:06:22 +00:00
|
|
|
.map(|(_, _, text, _, _)| text.into())
|
Text rework (#15591)
**Ready for review. Examples migration progress: 100%.**
# Objective
- Implement https://github.com/bevyengine/bevy/discussions/15014
## Solution
This implements [cart's
proposal](https://github.com/bevyengine/bevy/discussions/15014#discussioncomment-10574459)
faithfully except for one change. I separated `TextSpan` from
`TextSpan2d` because `TextSpan` needs to require the `GhostNode`
component, which is a `bevy_ui` component only usable by UI.
Extra changes:
- Added `EntityCommands::commands_mut` that returns a mutable reference.
This is a blocker for extension methods that return something other than
`self`. Note that `sickle_ui`'s `UiBuilder::commands` returns a mutable
reference for this reason.
## Testing
- [x] Text examples all work.
---
## Showcase
TODO: showcase-worthy
## Migration Guide
TODO: very breaking
### Accessing text spans by index
Text sections are now text sections on different entities in a
hierarchy, Use the new `TextReader` and `TextWriter` system parameters
to access spans by index.
Before:
```rust
fn refresh_text(mut query: Query<&mut Text, With<TimeText>>, time: Res<Time>) {
let text = query.single_mut();
text.sections[1].value = format_time(time.elapsed());
}
```
After:
```rust
fn refresh_text(
query: Query<Entity, With<TimeText>>,
mut writer: UiTextWriter,
time: Res<Time>
) {
let entity = query.single();
*writer.text(entity, 1) = format_time(time.elapsed());
}
```
### Iterating text spans
Text spans are now entities in a hierarchy, so the new `UiTextReader`
and `UiTextWriter` system parameters provide ways to iterate that
hierarchy. The `UiTextReader::iter` method will give you a normal
iterator over spans, and `UiTextWriter::for_each` lets you visit each of
the spans.
---------
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-10-09 18:35:36 +00:00
|
|
|
.collect::<Vec<String>>();
|
|
|
|
if !values.is_empty() {
|
2023-03-01 22:45:04 +00:00
|
|
|
name = Some(values.join(" "));
|
|
|
|
}
|
|
|
|
}
|
2024-07-01 15:54:40 +00:00
|
|
|
name.map(String::into_boxed_str)
|
2023-03-01 22:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn calc_bounds(
|
|
|
|
camera: Query<(&Camera, &GlobalTransform)>,
|
Merge Style properties into Node. Use ComputedNode for computed properties. (#15975)
# Objective
Continue improving the user experience of our UI Node API in the
direction specified by [Bevy's Next Generation Scene / UI
System](https://github.com/bevyengine/bevy/discussions/14437)
## Solution
As specified in the document above, merge `Style` fields into `Node`,
and move "computed Node fields" into `ComputedNode` (I chose this name
over something like `ComputedNodeLayout` because it currently contains
more than just layout info. If we want to break this up / rename these
concepts, lets do that in a separate PR). `Style` has been removed.
This accomplishes a number of goals:
## Ergonomics wins
Specifying both `Node` and `Style` is now no longer required for
non-default styles
Before:
```rust
commands.spawn((
Node::default(),
Style {
width: Val::Px(100.),
..default()
},
));
```
After:
```rust
commands.spawn(Node {
width: Val::Px(100.),
..default()
});
```
## Conceptual clarity
`Style` was never a comprehensive "style sheet". It only defined "core"
style properties that all `Nodes` shared. Any "styled property" that
couldn't fit that mold had to be in a separate component. A "real" style
system would style properties _across_ components (`Node`, `Button`,
etc). We have plans to build a true style system (see the doc linked
above).
By moving the `Style` fields to `Node`, we fully embrace `Node` as the
driving concept and remove the "style system" confusion.
## Next Steps
* Consider identifying and splitting out "style properties that aren't
core to Node". This should not happen for Bevy 0.15.
---
## Migration Guide
Move any fields set on `Style` into `Node` and replace all `Style`
component usage with `Node`.
Before:
```rust
commands.spawn((
Node::default(),
Style {
width: Val::Px(100.),
..default()
},
));
```
After:
```rust
commands.spawn(Node {
width: Val::Px(100.),
..default()
});
```
For any usage of the "computed node properties" that used to live on
`Node`, use `ComputedNode` instead:
Before:
```rust
fn system(nodes: Query<&Node>) {
for node in &nodes {
let computed_size = node.size();
}
}
```
After:
```rust
fn system(computed_nodes: Query<&ComputedNode>) {
for computed_node in &computed_nodes {
let computed_size = computed_node.size();
}
}
```
2024-10-18 22:25:33 +00:00
|
|
|
mut nodes: Query<(
|
|
|
|
&mut AccessibilityNode,
|
|
|
|
Ref<ComputedNode>,
|
|
|
|
Ref<GlobalTransform>,
|
|
|
|
)>,
|
2023-03-01 22:45:04 +00:00
|
|
|
) {
|
|
|
|
if let Ok((camera, camera_transform)) = camera.get_single() {
|
|
|
|
for (mut accessible, node, transform) in &mut nodes {
|
2023-05-08 20:49:55 +00:00
|
|
|
if node.is_changed() || transform.is_changed() {
|
2024-09-03 19:45:15 +00:00
|
|
|
if let Ok(translation) =
|
2023-05-08 20:49:55 +00:00
|
|
|
camera.world_to_viewport(camera_transform, transform.translation())
|
|
|
|
{
|
|
|
|
let bounds = Rect::new(
|
|
|
|
translation.x.into(),
|
|
|
|
translation.y.into(),
|
2024-10-28 21:05:25 +00:00
|
|
|
(translation.x + node.size.x).into(),
|
|
|
|
(translation.y + node.size.y).into(),
|
2023-05-08 20:49:55 +00:00
|
|
|
);
|
|
|
|
accessible.set_bounds(bounds);
|
|
|
|
}
|
2023-03-01 22:45:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn button_changed(
|
|
|
|
mut commands: Commands,
|
2024-10-02 00:24:28 +00:00
|
|
|
mut query: Query<(Entity, Option<&mut AccessibilityNode>), Changed<Button>>,
|
|
|
|
ui_children: UiChildren,
|
2024-10-15 02:32:34 +00:00
|
|
|
mut text_reader: TextUiReader,
|
2023-03-01 22:45:04 +00:00
|
|
|
) {
|
2024-10-02 00:24:28 +00:00
|
|
|
for (entity, accessible) in &mut query {
|
2024-11-04 20:07:38 +00:00
|
|
|
let label = calc_label(&mut text_reader, ui_children.iter_ui_children(entity));
|
2023-03-01 22:45:04 +00:00
|
|
|
if let Some(mut accessible) = accessible {
|
|
|
|
accessible.set_role(Role::Button);
|
2024-11-04 20:07:38 +00:00
|
|
|
if let Some(name) = label {
|
|
|
|
accessible.set_label(name);
|
2023-03-01 22:45:04 +00:00
|
|
|
} else {
|
2024-11-04 20:07:38 +00:00
|
|
|
accessible.clear_label();
|
2023-03-01 22:45:04 +00:00
|
|
|
}
|
|
|
|
} else {
|
2024-11-04 20:07:38 +00:00
|
|
|
let mut node = Node::new(Role::Button);
|
|
|
|
if let Some(label) = label {
|
|
|
|
node.set_label(label);
|
2023-03-01 22:45:04 +00:00
|
|
|
}
|
|
|
|
commands
|
|
|
|
.entity(entity)
|
fix: use try_insert instead of insert in bevy_ui to prevent panics when despawning ui nodes (#13000)
# Objective
Sometimes when despawning a ui node in the PostUpdate schedule it
panics. This is because both a despawn command and insert command are
being run on the same entity.
See this example code:
```rs
use bevy::{prelude::*, ui::UiSystem};
#[derive(Resource)]
struct SliceSquare(Handle<Image>);
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, create_ui)
.add_systems(PostUpdate, despawn_nine_slice.after(UiSystem::Layout))
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2dBundle::default());
commands.insert_resource(SliceSquare(asset_server.load("textures/slice_square.png")));
}
fn create_ui(mut commands: Commands, slice_square: Res<SliceSquare>) {
commands.spawn((
NodeBundle {
style: Style {
width: Val::Px(200.),
height: Val::Px(200.),
..default()
},
background_color: Color::WHITE.into(),
..default()
},
UiImage::new(slice_square.0.clone()),
ImageScaleMode::Sliced(TextureSlicer {
border: BorderRect::square(220.),
center_scale_mode: SliceScaleMode::Stretch,
sides_scale_mode: SliceScaleMode::Stretch,
max_corner_scale: 1.,
}),
));
}
fn despawn_nine_slice(mut commands: Commands, mut slices: Query<Entity, With<ImageScaleMode>>) {
for entity in slices.iter_mut() {
commands.entity(entity).despawn_recursive();
}
}
```
This code spawns a UiNode with a sliced image scale mode, and despawns
it in the same frame. The
bevy_ui::texture_slice::compute_slices_on_image_change system tries to
insert the ComputedTextureSlices component on that node, but that entity
is already despawned causing this error:
```md
error[B0003]: Could not insert a bundle (of type `bevy_ui::texture_slice::ComputedTextureSlices`) for entity Entity { index: 2, generation: 3 } because it doesn't
exist in this World. See: https://bevyengine.org/learn/errors/#b0003
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic when applying buffers for system `bevy_ui::texture_slice::compute_slices_on_image_change`!
Encountered a panic in system `bevy_ecs::schedule::executor::apply_deferred`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!
```
Note that you might have to run the code a few times before this error
appears.
## Solution
Use try_insert instead of insert for non critical inserts in the bevy_ui
crate.
## Some notes
In a lot of cases it does not makes much sense to despawn ui nodes after
the layout system has finished. Except maybe if you delete the root ui
node of a tree. I personally encountered this issue in bevy `0.13.2`
with a system that was running before the layout system. And in `0.13.2`
the `compute_slices_on_image_change` system was also running before the
layout system. But now it runs after the layout system. So the only way
that this bug appears now is if you despawn ui nodes after the layout
system. So I am not 100% sure if using try_insert in this system is the
best option. But personally I still think it is better then the program
panicking.
However the `update_children_target_camera` system does still run before
the layout system. So I do think it might still be able to panic when ui
nodes are despawned before the layout system. Though I haven't been able
to verify that.
2024-04-19 18:12:08 +00:00
|
|
|
.try_insert(AccessibilityNode::from(node));
|
2023-03-01 22:45:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn image_changed(
|
|
|
|
mut commands: Commands,
|
2024-11-07 21:52:58 +00:00
|
|
|
mut query: Query<
|
|
|
|
(Entity, Option<&mut AccessibilityNode>),
|
|
|
|
(Changed<ImageNode>, Without<Button>),
|
|
|
|
>,
|
2024-10-02 00:24:28 +00:00
|
|
|
ui_children: UiChildren,
|
2024-10-15 02:32:34 +00:00
|
|
|
mut text_reader: TextUiReader,
|
2023-03-01 22:45:04 +00:00
|
|
|
) {
|
2024-10-02 00:24:28 +00:00
|
|
|
for (entity, accessible) in &mut query {
|
2024-11-04 20:07:38 +00:00
|
|
|
let label = calc_label(&mut text_reader, ui_children.iter_ui_children(entity));
|
2023-03-01 22:45:04 +00:00
|
|
|
if let Some(mut accessible) = accessible {
|
|
|
|
accessible.set_role(Role::Image);
|
2024-11-04 20:07:38 +00:00
|
|
|
if let Some(label) = label {
|
|
|
|
accessible.set_label(label);
|
2023-03-01 22:45:04 +00:00
|
|
|
} else {
|
2024-11-04 20:07:38 +00:00
|
|
|
accessible.clear_label();
|
2023-03-01 22:45:04 +00:00
|
|
|
}
|
|
|
|
} else {
|
2024-11-04 20:07:38 +00:00
|
|
|
let mut node = Node::new(Role::Image);
|
|
|
|
if let Some(label) = label {
|
|
|
|
node.set_label(label);
|
2023-03-01 22:45:04 +00:00
|
|
|
}
|
|
|
|
commands
|
|
|
|
.entity(entity)
|
fix: use try_insert instead of insert in bevy_ui to prevent panics when despawning ui nodes (#13000)
# Objective
Sometimes when despawning a ui node in the PostUpdate schedule it
panics. This is because both a despawn command and insert command are
being run on the same entity.
See this example code:
```rs
use bevy::{prelude::*, ui::UiSystem};
#[derive(Resource)]
struct SliceSquare(Handle<Image>);
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, create_ui)
.add_systems(PostUpdate, despawn_nine_slice.after(UiSystem::Layout))
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2dBundle::default());
commands.insert_resource(SliceSquare(asset_server.load("textures/slice_square.png")));
}
fn create_ui(mut commands: Commands, slice_square: Res<SliceSquare>) {
commands.spawn((
NodeBundle {
style: Style {
width: Val::Px(200.),
height: Val::Px(200.),
..default()
},
background_color: Color::WHITE.into(),
..default()
},
UiImage::new(slice_square.0.clone()),
ImageScaleMode::Sliced(TextureSlicer {
border: BorderRect::square(220.),
center_scale_mode: SliceScaleMode::Stretch,
sides_scale_mode: SliceScaleMode::Stretch,
max_corner_scale: 1.,
}),
));
}
fn despawn_nine_slice(mut commands: Commands, mut slices: Query<Entity, With<ImageScaleMode>>) {
for entity in slices.iter_mut() {
commands.entity(entity).despawn_recursive();
}
}
```
This code spawns a UiNode with a sliced image scale mode, and despawns
it in the same frame. The
bevy_ui::texture_slice::compute_slices_on_image_change system tries to
insert the ComputedTextureSlices component on that node, but that entity
is already despawned causing this error:
```md
error[B0003]: Could not insert a bundle (of type `bevy_ui::texture_slice::ComputedTextureSlices`) for entity Entity { index: 2, generation: 3 } because it doesn't
exist in this World. See: https://bevyengine.org/learn/errors/#b0003
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic when applying buffers for system `bevy_ui::texture_slice::compute_slices_on_image_change`!
Encountered a panic in system `bevy_ecs::schedule::executor::apply_deferred`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!
```
Note that you might have to run the code a few times before this error
appears.
## Solution
Use try_insert instead of insert for non critical inserts in the bevy_ui
crate.
## Some notes
In a lot of cases it does not makes much sense to despawn ui nodes after
the layout system has finished. Except maybe if you delete the root ui
node of a tree. I personally encountered this issue in bevy `0.13.2`
with a system that was running before the layout system. And in `0.13.2`
the `compute_slices_on_image_change` system was also running before the
layout system. But now it runs after the layout system. So the only way
that this bug appears now is if you despawn ui nodes after the layout
system. So I am not 100% sure if using try_insert in this system is the
best option. But personally I still think it is better then the program
panicking.
However the `update_children_target_camera` system does still run before
the layout system. So I do think it might still be able to panic when ui
nodes are despawned before the layout system. Though I haven't been able
to verify that.
2024-04-19 18:12:08 +00:00
|
|
|
.try_insert(AccessibilityNode::from(node));
|
2023-03-01 22:45:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn label_changed(
|
|
|
|
mut commands: Commands,
|
Text rework (#15591)
**Ready for review. Examples migration progress: 100%.**
# Objective
- Implement https://github.com/bevyengine/bevy/discussions/15014
## Solution
This implements [cart's
proposal](https://github.com/bevyengine/bevy/discussions/15014#discussioncomment-10574459)
faithfully except for one change. I separated `TextSpan` from
`TextSpan2d` because `TextSpan` needs to require the `GhostNode`
component, which is a `bevy_ui` component only usable by UI.
Extra changes:
- Added `EntityCommands::commands_mut` that returns a mutable reference.
This is a blocker for extension methods that return something other than
`self`. Note that `sickle_ui`'s `UiBuilder::commands` returns a mutable
reference for this reason.
## Testing
- [x] Text examples all work.
---
## Showcase
TODO: showcase-worthy
## Migration Guide
TODO: very breaking
### Accessing text spans by index
Text sections are now text sections on different entities in a
hierarchy, Use the new `TextReader` and `TextWriter` system parameters
to access spans by index.
Before:
```rust
fn refresh_text(mut query: Query<&mut Text, With<TimeText>>, time: Res<Time>) {
let text = query.single_mut();
text.sections[1].value = format_time(time.elapsed());
}
```
After:
```rust
fn refresh_text(
query: Query<Entity, With<TimeText>>,
mut writer: UiTextWriter,
time: Res<Time>
) {
let entity = query.single();
*writer.text(entity, 1) = format_time(time.elapsed());
}
```
### Iterating text spans
Text spans are now entities in a hierarchy, so the new `UiTextReader`
and `UiTextWriter` system parameters provide ways to iterate that
hierarchy. The `UiTextReader::iter` method will give you a normal
iterator over spans, and `UiTextWriter::for_each` lets you visit each of
the spans.
---------
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-10-09 18:35:36 +00:00
|
|
|
mut query: Query<(Entity, Option<&mut AccessibilityNode>), Changed<Label>>,
|
2024-10-15 02:32:34 +00:00
|
|
|
mut text_reader: TextUiReader,
|
2023-03-01 22:45:04 +00:00
|
|
|
) {
|
Text rework (#15591)
**Ready for review. Examples migration progress: 100%.**
# Objective
- Implement https://github.com/bevyengine/bevy/discussions/15014
## Solution
This implements [cart's
proposal](https://github.com/bevyengine/bevy/discussions/15014#discussioncomment-10574459)
faithfully except for one change. I separated `TextSpan` from
`TextSpan2d` because `TextSpan` needs to require the `GhostNode`
component, which is a `bevy_ui` component only usable by UI.
Extra changes:
- Added `EntityCommands::commands_mut` that returns a mutable reference.
This is a blocker for extension methods that return something other than
`self`. Note that `sickle_ui`'s `UiBuilder::commands` returns a mutable
reference for this reason.
## Testing
- [x] Text examples all work.
---
## Showcase
TODO: showcase-worthy
## Migration Guide
TODO: very breaking
### Accessing text spans by index
Text sections are now text sections on different entities in a
hierarchy, Use the new `TextReader` and `TextWriter` system parameters
to access spans by index.
Before:
```rust
fn refresh_text(mut query: Query<&mut Text, With<TimeText>>, time: Res<Time>) {
let text = query.single_mut();
text.sections[1].value = format_time(time.elapsed());
}
```
After:
```rust
fn refresh_text(
query: Query<Entity, With<TimeText>>,
mut writer: UiTextWriter,
time: Res<Time>
) {
let entity = query.single();
*writer.text(entity, 1) = format_time(time.elapsed());
}
```
### Iterating text spans
Text spans are now entities in a hierarchy, so the new `UiTextReader`
and `UiTextWriter` system parameters provide ways to iterate that
hierarchy. The `UiTextReader::iter` method will give you a normal
iterator over spans, and `UiTextWriter::for_each` lets you visit each of
the spans.
---------
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-10-09 18:35:36 +00:00
|
|
|
for (entity, accessible) in &mut query {
|
|
|
|
let values = text_reader
|
|
|
|
.iter(entity)
|
2024-10-13 17:06:22 +00:00
|
|
|
.map(|(_, _, text, _, _)| text.into())
|
2023-03-01 22:45:04 +00:00
|
|
|
.collect::<Vec<String>>();
|
2024-11-04 20:07:38 +00:00
|
|
|
let label = Some(values.join(" ").into_boxed_str());
|
2023-03-01 22:45:04 +00:00
|
|
|
if let Some(mut accessible) = accessible {
|
2024-07-01 14:42:40 +00:00
|
|
|
accessible.set_role(Role::Label);
|
2024-11-04 20:07:38 +00:00
|
|
|
if let Some(label) = label {
|
|
|
|
accessible.set_label(label);
|
2023-03-01 22:45:04 +00:00
|
|
|
} else {
|
2024-11-04 20:07:38 +00:00
|
|
|
accessible.clear_label();
|
2023-03-01 22:45:04 +00:00
|
|
|
}
|
|
|
|
} else {
|
2024-11-04 20:07:38 +00:00
|
|
|
let mut node = Node::new(Role::Label);
|
|
|
|
if let Some(label) = label {
|
|
|
|
node.set_label(label);
|
2023-03-01 22:45:04 +00:00
|
|
|
}
|
|
|
|
commands
|
|
|
|
.entity(entity)
|
fix: use try_insert instead of insert in bevy_ui to prevent panics when despawning ui nodes (#13000)
# Objective
Sometimes when despawning a ui node in the PostUpdate schedule it
panics. This is because both a despawn command and insert command are
being run on the same entity.
See this example code:
```rs
use bevy::{prelude::*, ui::UiSystem};
#[derive(Resource)]
struct SliceSquare(Handle<Image>);
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, create_ui)
.add_systems(PostUpdate, despawn_nine_slice.after(UiSystem::Layout))
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2dBundle::default());
commands.insert_resource(SliceSquare(asset_server.load("textures/slice_square.png")));
}
fn create_ui(mut commands: Commands, slice_square: Res<SliceSquare>) {
commands.spawn((
NodeBundle {
style: Style {
width: Val::Px(200.),
height: Val::Px(200.),
..default()
},
background_color: Color::WHITE.into(),
..default()
},
UiImage::new(slice_square.0.clone()),
ImageScaleMode::Sliced(TextureSlicer {
border: BorderRect::square(220.),
center_scale_mode: SliceScaleMode::Stretch,
sides_scale_mode: SliceScaleMode::Stretch,
max_corner_scale: 1.,
}),
));
}
fn despawn_nine_slice(mut commands: Commands, mut slices: Query<Entity, With<ImageScaleMode>>) {
for entity in slices.iter_mut() {
commands.entity(entity).despawn_recursive();
}
}
```
This code spawns a UiNode with a sliced image scale mode, and despawns
it in the same frame. The
bevy_ui::texture_slice::compute_slices_on_image_change system tries to
insert the ComputedTextureSlices component on that node, but that entity
is already despawned causing this error:
```md
error[B0003]: Could not insert a bundle (of type `bevy_ui::texture_slice::ComputedTextureSlices`) for entity Entity { index: 2, generation: 3 } because it doesn't
exist in this World. See: https://bevyengine.org/learn/errors/#b0003
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic when applying buffers for system `bevy_ui::texture_slice::compute_slices_on_image_change`!
Encountered a panic in system `bevy_ecs::schedule::executor::apply_deferred`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!
```
Note that you might have to run the code a few times before this error
appears.
## Solution
Use try_insert instead of insert for non critical inserts in the bevy_ui
crate.
## Some notes
In a lot of cases it does not makes much sense to despawn ui nodes after
the layout system has finished. Except maybe if you delete the root ui
node of a tree. I personally encountered this issue in bevy `0.13.2`
with a system that was running before the layout system. And in `0.13.2`
the `compute_slices_on_image_change` system was also running before the
layout system. But now it runs after the layout system. So the only way
that this bug appears now is if you despawn ui nodes after the layout
system. So I am not 100% sure if using try_insert in this system is the
best option. But personally I still think it is better then the program
panicking.
However the `update_children_target_camera` system does still run before
the layout system. So I do think it might still be able to panic when ui
nodes are despawned before the layout system. Though I haven't been able
to verify that.
2024-04-19 18:12:08 +00:00
|
|
|
.try_insert(AccessibilityNode::from(node));
|
2023-03-01 22:45:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `AccessKit` integration for `bevy_ui`.
|
|
|
|
pub(crate) struct AccessibilityPlugin;
|
|
|
|
|
|
|
|
impl Plugin for AccessibilityPlugin {
|
|
|
|
fn build(&self, app: &mut App) {
|
2023-03-18 01:45:34 +00:00
|
|
|
app.add_systems(
|
2023-05-23 23:50:48 +00:00
|
|
|
PostUpdate,
|
|
|
|
(
|
2024-01-09 19:08:15 +00:00
|
|
|
calc_bounds
|
|
|
|
.after(bevy_transform::TransformSystem::TransformPropagate)
|
|
|
|
.after(CameraUpdateSystem)
|
|
|
|
// the listed systems do not affect calculated size
|
|
|
|
.ambiguous_with(crate::ui_stack_system),
|
2023-05-23 23:50:48 +00:00
|
|
|
button_changed,
|
|
|
|
image_changed,
|
|
|
|
label_changed,
|
|
|
|
),
|
2023-03-18 01:45:34 +00:00
|
|
|
);
|
2023-03-01 22:45:04 +00:00
|
|
|
}
|
|
|
|
}
|