mirror of
https://github.com/bevyengine/bevy
synced 2024-11-26 06:30:19 +00:00
b995827013
# Objective Make `bevy_ui` "root" nodes more intuitive to use/style by: - Removing the implicit flexbox styling (such as stretch alignment) that is applied to them, and replacing it with more intuitive CSS Grid styling (notably with stretch alignment disabled in both axes). - Making root nodes layout independently of each other. Instead of there being a single implicit "viewport" node that all root nodes are children of, there is now an implicit "viewport" node *per root node*. And layout of each tree is computed separately. ## Solution - Remove the global implicit viewport node, and instead create an implicit viewport node for each user-specified root node. - Keep track of both the user-specified root nodes and the implicit viewport nodes in a separate `Vec`. - Use the window's size as the `available_space` parameter to `Taffy.compute_layout` rather than setting it on the implicit viewport node (and set the viewport to `height: 100%; width: 100%` to make this "just work"). --- ## Changelog - Bevy UI now lays out root nodes independently of each other in separate layout contexts. - The implicit viewport node (which contains each user-specified root node) is now `Display::Grid` with `align_items` and `justify_items` both set to `Start`. ## Migration Guide - Bevy UI now lays out root nodes independently of each other in separate layout contexts. If you were relying on your root nodes being able to affect each other's layouts, then you may need to wrap them in a single root node. - The implicit viewport node (which contains each user-specified root node) is now `Display::Grid` with `align_items` and `justify_items` both set to `Start`. You may need to add `height: Val::Percent(100.)` to your root nodes if you were previously relying on being implicitly set.
181 lines
5.6 KiB
Rust
181 lines
5.6 KiB
Rust
//! This example illustrates how to use the `apply_deferred` system
|
|
//! to flush commands added by systems that have already run,
|
|
//! but have not had their buffers applied yet.
|
|
//!
|
|
//! This is useful when you don't want to wait until the next flush set
|
|
//! automatically added by Bevy (usually `CoreSet::UpdateFlush`, for systems
|
|
//! added to `CoreSet::Update`) but want to flush commands immediately.
|
|
//!
|
|
//! It is important that systems are ordered correctly with respect to
|
|
//! `apply_deferred`, to avoid surprising non-deterministic system execution order.
|
|
|
|
use bevy::prelude::*;
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
.init_resource::<Timers>()
|
|
.add_systems(Startup, setup)
|
|
.add_systems(
|
|
Update,
|
|
(
|
|
(
|
|
despawn_old_and_spawn_new_fruits,
|
|
// We encourage adding apply_deferred to a custom set
|
|
// to improve diagnostics. This is optional, but useful when debugging!
|
|
apply_deferred.in_set(CustomFlush),
|
|
count_apple,
|
|
)
|
|
.chain(),
|
|
count_orange,
|
|
bevy::window::close_on_esc,
|
|
),
|
|
)
|
|
.run();
|
|
}
|
|
|
|
#[derive(Resource)]
|
|
struct Timers {
|
|
repeating: Timer,
|
|
}
|
|
|
|
impl Default for Timers {
|
|
fn default() -> Self {
|
|
Self {
|
|
repeating: Timer::from_seconds(0.5, TimerMode::Repeating),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
|
struct CustomFlush;
|
|
|
|
#[derive(Component)]
|
|
struct Apple;
|
|
|
|
#[derive(Component)]
|
|
struct Orange;
|
|
|
|
#[derive(Component)]
|
|
struct AppleCount;
|
|
|
|
#[derive(Component)]
|
|
struct OrangeCount;
|
|
|
|
// Setup the counters in the UI.
|
|
fn setup(mut commands: Commands) {
|
|
commands.spawn(Camera2dBundle::default());
|
|
|
|
commands
|
|
.spawn(NodeBundle {
|
|
style: Style {
|
|
width: Val::Percent(100.0),
|
|
height: Val::Percent(100.0),
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::Center,
|
|
flex_direction: FlexDirection::Column,
|
|
..default()
|
|
},
|
|
..default()
|
|
})
|
|
.with_children(|parent| {
|
|
parent.spawn((
|
|
TextBundle::from_section(
|
|
"Apple: nothing counted yet".to_string(),
|
|
TextStyle {
|
|
font_size: 80.0,
|
|
color: Color::ORANGE,
|
|
..default()
|
|
},
|
|
),
|
|
AppleCount,
|
|
));
|
|
parent.spawn((
|
|
TextBundle::from_section(
|
|
"Orange: nothing counted yet".to_string(),
|
|
TextStyle {
|
|
font_size: 80.0,
|
|
color: Color::ORANGE,
|
|
..default()
|
|
},
|
|
),
|
|
OrangeCount,
|
|
));
|
|
});
|
|
}
|
|
|
|
// Every tick, before the CustomFlush we added, we despawn any Apple and Orange
|
|
// we have previously spawned, if any. Then we tick the timer, and if the timer
|
|
// has finished during this tick, we spawn a new Apple and a new Orange.
|
|
//
|
|
// The commands that we have added here will normally be flushed by Bevy
|
|
// after all systems in the schedule have run, but because we have ordered
|
|
// this system to run before `apply_deferred.in_set(CustomFlush)`,
|
|
// these commands added here will be flushed during our custom flush.
|
|
fn despawn_old_and_spawn_new_fruits(
|
|
mut commands: Commands,
|
|
time: Res<Time>,
|
|
mut timers: ResMut<Timers>,
|
|
apple: Query<Entity, With<Apple>>,
|
|
orange: Query<Entity, With<Orange>>,
|
|
) {
|
|
if let Ok(apple_entity) = apple.get_single() {
|
|
commands.entity(apple_entity).despawn();
|
|
}
|
|
|
|
if let Ok(orange_entity) = orange.get_single() {
|
|
commands.entity(orange_entity).despawn();
|
|
}
|
|
|
|
timers.repeating.tick(time.delta());
|
|
|
|
if timers.repeating.just_finished() {
|
|
commands.spawn(Apple);
|
|
commands.spawn(Orange);
|
|
}
|
|
}
|
|
|
|
// If the timer has finished during this tick, we see if there is an entity
|
|
// with an Apple component or not, and update the UI accordingly.
|
|
//
|
|
// Since this system is ordered `.after(CustomFlush)` it will be guaranteed
|
|
// to run after our CustomFlush set, so the Apple will always be counted.
|
|
//
|
|
// We will see the AppleCount go from "Apple: nothing counted yet" to "Apple: counted"
|
|
fn count_apple(
|
|
timers: Res<Timers>,
|
|
apple: Query<&Apple>,
|
|
mut apple_count: Query<&mut Text, With<AppleCount>>,
|
|
) {
|
|
if timers.repeating.just_finished() {
|
|
let mut apples_text = apple_count.single_mut();
|
|
apples_text.sections[0].value = if apple.is_empty() {
|
|
"Apple: not counted".to_string()
|
|
} else {
|
|
"Apple: counted".to_string()
|
|
};
|
|
}
|
|
}
|
|
|
|
// If the timer has finished during this tick, we see if there is an entity
|
|
// with an Orange component or not, and update the UI accordingly.
|
|
//
|
|
// Since this system is not ordered `.after(CustomFlush)`, it may or may not run
|
|
// before the custom flush, therefore you will see the UI either show "Orange: counted"
|
|
// or "Orange: not counted" or alternate between the two.
|
|
//
|
|
// Try to re-run the example multiple times as well.
|
|
fn count_orange(
|
|
timers: Res<Timers>,
|
|
orange: Query<&Orange>,
|
|
mut orange_count: Query<&mut Text, With<OrangeCount>>,
|
|
) {
|
|
if timers.repeating.just_finished() {
|
|
let mut oranges_text = orange_count.single_mut();
|
|
oranges_text.sections[0].value = if orange.is_empty() {
|
|
"Orange: not counted".to_string()
|
|
} else {
|
|
"Orange: counted".to_string()
|
|
};
|
|
}
|
|
}
|