Merge branch 'bevyengine:main' into schema-types-metadata
This commit is contained in:
commit
7beca6fd0e
11
Cargo.toml
11
Cargo.toml
@ -3543,6 +3543,17 @@ description = "Illustrates how to use 9 Slicing for TextureAtlases in UI"
|
|||||||
category = "UI (User Interface)"
|
category = "UI (User Interface)"
|
||||||
wasm = true
|
wasm = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "ui_transform"
|
||||||
|
path = "examples/ui/ui_transform.rs"
|
||||||
|
doc-scrape-examples = true
|
||||||
|
|
||||||
|
[package.metadata.example.ui_transform]
|
||||||
|
name = "UI Transform"
|
||||||
|
description = "An example demonstrating how to translate, rotate and scale UI elements."
|
||||||
|
category = "UI (User Interface)"
|
||||||
|
wasm = true
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "viewport_debug"
|
name = "viewport_debug"
|
||||||
path = "examples/ui/viewport_debug.rs"
|
path = "examples/ui/viewport_debug.rs"
|
||||||
|
|||||||
@ -340,8 +340,8 @@ let mut world = World::new();
|
|||||||
let entity = world.spawn_empty().id();
|
let entity = world.spawn_empty().id();
|
||||||
|
|
||||||
world.add_observer(|trigger: Trigger<Explode>, mut commands: Commands| {
|
world.add_observer(|trigger: Trigger<Explode>, mut commands: Commands| {
|
||||||
println!("Entity {} goes BOOM!", trigger.target());
|
println!("Entity {} goes BOOM!", trigger.target().unwrap());
|
||||||
commands.entity(trigger.target()).despawn();
|
commands.entity(trigger.target().unwrap()).despawn();
|
||||||
});
|
});
|
||||||
|
|
||||||
world.flush();
|
world.flush();
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
|
//! Macros for deriving ECS traits.
|
||||||
|
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
@ -29,6 +30,7 @@ enum BundleFieldKind {
|
|||||||
const BUNDLE_ATTRIBUTE_NAME: &str = "bundle";
|
const BUNDLE_ATTRIBUTE_NAME: &str = "bundle";
|
||||||
const BUNDLE_ATTRIBUTE_IGNORE_NAME: &str = "ignore";
|
const BUNDLE_ATTRIBUTE_IGNORE_NAME: &str = "ignore";
|
||||||
|
|
||||||
|
/// Implement the `Bundle` trait.
|
||||||
#[proc_macro_derive(Bundle, attributes(bundle))]
|
#[proc_macro_derive(Bundle, attributes(bundle))]
|
||||||
pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
||||||
let ast = parse_macro_input!(input as DeriveInput);
|
let ast = parse_macro_input!(input as DeriveInput);
|
||||||
@ -187,6 +189,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement the `MapEntities` trait.
|
||||||
#[proc_macro_derive(MapEntities, attributes(entities))]
|
#[proc_macro_derive(MapEntities, attributes(entities))]
|
||||||
pub fn derive_map_entities(input: TokenStream) -> TokenStream {
|
pub fn derive_map_entities(input: TokenStream) -> TokenStream {
|
||||||
let ast = parse_macro_input!(input as DeriveInput);
|
let ast = parse_macro_input!(input as DeriveInput);
|
||||||
@ -522,16 +525,19 @@ pub(crate) fn bevy_ecs_path() -> syn::Path {
|
|||||||
BevyManifest::shared().get_path("bevy_ecs")
|
BevyManifest::shared().get_path("bevy_ecs")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement the `Event` trait.
|
||||||
#[proc_macro_derive(Event, attributes(event))]
|
#[proc_macro_derive(Event, attributes(event))]
|
||||||
pub fn derive_event(input: TokenStream) -> TokenStream {
|
pub fn derive_event(input: TokenStream) -> TokenStream {
|
||||||
component::derive_event(input)
|
component::derive_event(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement the `Resource` trait.
|
||||||
#[proc_macro_derive(Resource)]
|
#[proc_macro_derive(Resource)]
|
||||||
pub fn derive_resource(input: TokenStream) -> TokenStream {
|
pub fn derive_resource(input: TokenStream) -> TokenStream {
|
||||||
component::derive_resource(input)
|
component::derive_resource(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement the `Component` trait.
|
||||||
#[proc_macro_derive(
|
#[proc_macro_derive(
|
||||||
Component,
|
Component,
|
||||||
attributes(component, require, relationship, relationship_target, entities)
|
attributes(component, require, relationship, relationship_target, entities)
|
||||||
@ -540,6 +546,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
|
|||||||
component::derive_component(input)
|
component::derive_component(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement the `FromWorld` trait.
|
||||||
#[proc_macro_derive(FromWorld, attributes(from_world))]
|
#[proc_macro_derive(FromWorld, attributes(from_world))]
|
||||||
pub fn derive_from_world(input: TokenStream) -> TokenStream {
|
pub fn derive_from_world(input: TokenStream) -> TokenStream {
|
||||||
let bevy_ecs_path = bevy_ecs_path();
|
let bevy_ecs_path = bevy_ecs_path();
|
||||||
|
|||||||
@ -1143,7 +1143,7 @@ impl<'w> BundleInserter<'w> {
|
|||||||
if archetype.has_replace_observer() {
|
if archetype.has_replace_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_REPLACE,
|
ON_REPLACE,
|
||||||
entity,
|
Some(entity),
|
||||||
archetype_after_insert.iter_existing(),
|
archetype_after_insert.iter_existing(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -1328,7 +1328,7 @@ impl<'w> BundleInserter<'w> {
|
|||||||
if new_archetype.has_add_observer() {
|
if new_archetype.has_add_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_ADD,
|
ON_ADD,
|
||||||
entity,
|
Some(entity),
|
||||||
archetype_after_insert.iter_added(),
|
archetype_after_insert.iter_added(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -1346,7 +1346,7 @@ impl<'w> BundleInserter<'w> {
|
|||||||
if new_archetype.has_insert_observer() {
|
if new_archetype.has_insert_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_INSERT,
|
ON_INSERT,
|
||||||
entity,
|
Some(entity),
|
||||||
archetype_after_insert.iter_inserted(),
|
archetype_after_insert.iter_inserted(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -1365,7 +1365,7 @@ impl<'w> BundleInserter<'w> {
|
|||||||
if new_archetype.has_insert_observer() {
|
if new_archetype.has_insert_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_INSERT,
|
ON_INSERT,
|
||||||
entity,
|
Some(entity),
|
||||||
archetype_after_insert.iter_added(),
|
archetype_after_insert.iter_added(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -1519,7 +1519,7 @@ impl<'w> BundleRemover<'w> {
|
|||||||
if self.old_archetype.as_ref().has_replace_observer() {
|
if self.old_archetype.as_ref().has_replace_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_REPLACE,
|
ON_REPLACE,
|
||||||
entity,
|
Some(entity),
|
||||||
bundle_components_in_archetype(),
|
bundle_components_in_archetype(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -1534,7 +1534,7 @@ impl<'w> BundleRemover<'w> {
|
|||||||
if self.old_archetype.as_ref().has_remove_observer() {
|
if self.old_archetype.as_ref().has_remove_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_REMOVE,
|
ON_REMOVE,
|
||||||
entity,
|
Some(entity),
|
||||||
bundle_components_in_archetype(),
|
bundle_components_in_archetype(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -1785,7 +1785,7 @@ impl<'w> BundleSpawner<'w> {
|
|||||||
if archetype.has_add_observer() {
|
if archetype.has_add_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_ADD,
|
ON_ADD,
|
||||||
entity,
|
Some(entity),
|
||||||
bundle_info.iter_contributed_components(),
|
bundle_info.iter_contributed_components(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -1800,7 +1800,7 @@ impl<'w> BundleSpawner<'w> {
|
|||||||
if archetype.has_insert_observer() {
|
if archetype.has_insert_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_INSERT,
|
ON_INSERT,
|
||||||
entity,
|
Some(entity),
|
||||||
bundle_info.iter_contributed_components(),
|
bundle_info.iter_contributed_components(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -898,63 +898,39 @@ impl_debug!(Ref<'w, T>,);
|
|||||||
|
|
||||||
/// Unique mutable borrow of an entity's component or of a resource.
|
/// Unique mutable borrow of an entity's component or of a resource.
|
||||||
///
|
///
|
||||||
/// This can be used in queries to opt into change detection on both their mutable and immutable forms, as opposed to
|
/// This can be used in queries to access change detection from immutable query methods, as opposed
|
||||||
/// `&mut T`, which only provides access to change detection while in its mutable form:
|
/// to `&mut T` which only provides access to change detection from mutable query methods.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use bevy_ecs::prelude::*;
|
/// # use bevy_ecs::prelude::*;
|
||||||
/// # use bevy_ecs::query::QueryData;
|
/// # use bevy_ecs::query::QueryData;
|
||||||
/// #
|
/// #
|
||||||
/// #[derive(Component, Clone)]
|
/// #[derive(Component, Clone, Debug)]
|
||||||
/// struct Name(String);
|
/// struct Name(String);
|
||||||
///
|
///
|
||||||
/// #[derive(Component, Clone, Copy)]
|
/// #[derive(Component, Clone, Copy, Debug)]
|
||||||
/// struct Health(f32);
|
/// struct Health(f32);
|
||||||
///
|
///
|
||||||
/// #[derive(Component, Clone, Copy)]
|
/// fn my_system(mut query: Query<(Mut<Name>, &mut Health)>) {
|
||||||
/// struct Position {
|
/// // Mutable access provides change detection information for both parameters:
|
||||||
/// x: f32,
|
/// // - `name` has type `Mut<Name>`
|
||||||
/// y: f32,
|
/// // - `health` has type `Mut<Health>`
|
||||||
/// };
|
/// for (name, health) in query.iter_mut() {
|
||||||
|
/// println!("Name: {:?} (last changed {:?})", name, name.last_changed());
|
||||||
|
/// println!("Health: {:?} (last changed: {:?})", health, health.last_changed());
|
||||||
|
/// # println!("{}{}", name.0, health.0); // Silence dead_code warning
|
||||||
|
/// }
|
||||||
///
|
///
|
||||||
/// #[derive(Component, Clone, Copy)]
|
/// // Immutable access only provides change detection for `Name`:
|
||||||
/// struct Player {
|
/// // - `name` has type `Ref<Name>`
|
||||||
/// id: usize,
|
/// // - `health` has type `&Health`
|
||||||
/// };
|
/// for (name, health) in query.iter() {
|
||||||
///
|
/// println!("Name: {:?} (last changed {:?})", name, name.last_changed());
|
||||||
/// #[derive(QueryData)]
|
/// println!("Health: {:?}", health);
|
||||||
/// #[query_data(mutable)]
|
|
||||||
/// struct PlayerQuery {
|
|
||||||
/// id: &'static Player,
|
|
||||||
///
|
|
||||||
/// // Reacting to `PlayerName` changes is expensive, so we need to enable change detection when reading it.
|
|
||||||
/// name: Mut<'static, Name>,
|
|
||||||
///
|
|
||||||
/// health: &'static mut Health,
|
|
||||||
/// position: &'static mut Position,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn update_player_avatars(players_query: Query<PlayerQuery>) {
|
|
||||||
/// // The item returned by the iterator is of type `PlayerQueryReadOnlyItem`.
|
|
||||||
/// for player in players_query.iter() {
|
|
||||||
/// if player.name.is_changed() {
|
|
||||||
/// // Update the player's name. This clones a String, and so is more expensive.
|
|
||||||
/// update_player_name(player.id, player.name.clone());
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// // Update the health bar.
|
|
||||||
/// update_player_health(player.id, *player.health);
|
|
||||||
///
|
|
||||||
/// // Update the player's position.
|
|
||||||
/// update_player_position(player.id, *player.position);
|
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// # bevy_ecs::system::assert_is_system(update_player_avatars);
|
/// # bevy_ecs::system::assert_is_system(my_system);
|
||||||
///
|
|
||||||
/// # fn update_player_name(player: &Player, new_name: Name) {}
|
|
||||||
/// # fn update_player_health(player: &Player, new_health: Health) {}
|
|
||||||
/// # fn update_player_position(player: &Player, new_position: Position) {}
|
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Mut<'w, T: ?Sized> {
|
pub struct Mut<'w, T: ?Sized> {
|
||||||
pub(crate) value: &'w mut T,
|
pub(crate) value: &'w mut T,
|
||||||
|
|||||||
@ -68,7 +68,7 @@ impl<'w, E, B: Bundle> Trigger<'w, E, B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the [`Entity`] that was targeted by the `event` that triggered this observer. It may
|
/// Returns the [`Entity`] that was targeted by the `event` that triggered this observer. It may
|
||||||
/// be [`Entity::PLACEHOLDER`].
|
/// be [`None`] if the trigger is not for a particular entity.
|
||||||
///
|
///
|
||||||
/// Observable events can target specific entities. When those events fire, they will trigger
|
/// Observable events can target specific entities. When those events fire, they will trigger
|
||||||
/// any observers on the targeted entities. In this case, the `target()` and `observer()` are
|
/// any observers on the targeted entities. In this case, the `target()` and `observer()` are
|
||||||
@ -81,7 +81,7 @@ impl<'w, E, B: Bundle> Trigger<'w, E, B> {
|
|||||||
///
|
///
|
||||||
/// This is an important distinction: the entity reacting to an event is not always the same as
|
/// This is an important distinction: the entity reacting to an event is not always the same as
|
||||||
/// the entity triggered by the event.
|
/// the entity triggered by the event.
|
||||||
pub fn target(&self) -> Entity {
|
pub fn target(&self) -> Option<Entity> {
|
||||||
self.trigger.target
|
self.trigger.target
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,7 +341,7 @@ pub struct ObserverTrigger {
|
|||||||
/// The [`ComponentId`]s the trigger targeted.
|
/// The [`ComponentId`]s the trigger targeted.
|
||||||
components: SmallVec<[ComponentId; 2]>,
|
components: SmallVec<[ComponentId; 2]>,
|
||||||
/// The entity the trigger targeted.
|
/// The entity the trigger targeted.
|
||||||
pub target: Entity,
|
pub target: Option<Entity>,
|
||||||
/// The location of the source code that triggered the observer.
|
/// The location of the source code that triggered the observer.
|
||||||
pub caller: MaybeLocation,
|
pub caller: MaybeLocation,
|
||||||
}
|
}
|
||||||
@ -416,7 +416,7 @@ impl Observers {
|
|||||||
pub(crate) fn invoke<T>(
|
pub(crate) fn invoke<T>(
|
||||||
mut world: DeferredWorld,
|
mut world: DeferredWorld,
|
||||||
event_type: ComponentId,
|
event_type: ComponentId,
|
||||||
target: Entity,
|
target: Option<Entity>,
|
||||||
components: impl Iterator<Item = ComponentId> + Clone,
|
components: impl Iterator<Item = ComponentId> + Clone,
|
||||||
data: &mut T,
|
data: &mut T,
|
||||||
propagate: &mut bool,
|
propagate: &mut bool,
|
||||||
@ -455,8 +455,8 @@ impl Observers {
|
|||||||
observers.map.iter().for_each(&mut trigger_observer);
|
observers.map.iter().for_each(&mut trigger_observer);
|
||||||
|
|
||||||
// Trigger entity observers listening for this kind of trigger
|
// Trigger entity observers listening for this kind of trigger
|
||||||
if target != Entity::PLACEHOLDER {
|
if let Some(target_entity) = target {
|
||||||
if let Some(map) = observers.entity_observers.get(&target) {
|
if let Some(map) = observers.entity_observers.get(&target_entity) {
|
||||||
map.iter().for_each(&mut trigger_observer);
|
map.iter().for_each(&mut trigger_observer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -469,8 +469,8 @@ impl Observers {
|
|||||||
.iter()
|
.iter()
|
||||||
.for_each(&mut trigger_observer);
|
.for_each(&mut trigger_observer);
|
||||||
|
|
||||||
if target != Entity::PLACEHOLDER {
|
if let Some(target_entity) = target {
|
||||||
if let Some(map) = component_observers.entity_map.get(&target) {
|
if let Some(map) = component_observers.entity_map.get(&target_entity) {
|
||||||
map.iter().for_each(&mut trigger_observer);
|
map.iter().for_each(&mut trigger_observer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -695,7 +695,7 @@ impl World {
|
|||||||
unsafe {
|
unsafe {
|
||||||
world.trigger_observers_with_data::<_, E::Traversal>(
|
world.trigger_observers_with_data::<_, E::Traversal>(
|
||||||
event_id,
|
event_id,
|
||||||
Entity::PLACEHOLDER,
|
None,
|
||||||
targets.components(),
|
targets.components(),
|
||||||
event_data,
|
event_data,
|
||||||
false,
|
false,
|
||||||
@ -708,7 +708,7 @@ impl World {
|
|||||||
unsafe {
|
unsafe {
|
||||||
world.trigger_observers_with_data::<_, E::Traversal>(
|
world.trigger_observers_with_data::<_, E::Traversal>(
|
||||||
event_id,
|
event_id,
|
||||||
target_entity,
|
Some(target_entity),
|
||||||
targets.components(),
|
targets.components(),
|
||||||
event_data,
|
event_data,
|
||||||
E::AUTO_PROPAGATE,
|
E::AUTO_PROPAGATE,
|
||||||
@ -999,20 +999,20 @@ mod tests {
|
|||||||
world.add_observer(
|
world.add_observer(
|
||||||
|obs: Trigger<OnAdd, A>, mut res: ResMut<Order>, mut commands: Commands| {
|
|obs: Trigger<OnAdd, A>, mut res: ResMut<Order>, mut commands: Commands| {
|
||||||
res.observed("add_a");
|
res.observed("add_a");
|
||||||
commands.entity(obs.target()).insert(B);
|
commands.entity(obs.target().unwrap()).insert(B);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
world.add_observer(
|
world.add_observer(
|
||||||
|obs: Trigger<OnRemove, A>, mut res: ResMut<Order>, mut commands: Commands| {
|
|obs: Trigger<OnRemove, A>, mut res: ResMut<Order>, mut commands: Commands| {
|
||||||
res.observed("remove_a");
|
res.observed("remove_a");
|
||||||
commands.entity(obs.target()).remove::<B>();
|
commands.entity(obs.target().unwrap()).remove::<B>();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
world.add_observer(
|
world.add_observer(
|
||||||
|obs: Trigger<OnAdd, B>, mut res: ResMut<Order>, mut commands: Commands| {
|
|obs: Trigger<OnAdd, B>, mut res: ResMut<Order>, mut commands: Commands| {
|
||||||
res.observed("add_b");
|
res.observed("add_b");
|
||||||
commands.entity(obs.target()).remove::<A>();
|
commands.entity(obs.target().unwrap()).remove::<A>();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
world.add_observer(|_: Trigger<OnRemove, B>, mut res: ResMut<Order>| {
|
world.add_observer(|_: Trigger<OnRemove, B>, mut res: ResMut<Order>| {
|
||||||
@ -1181,7 +1181,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
world.spawn_empty().observe(system);
|
world.spawn_empty().observe(system);
|
||||||
world.add_observer(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
|
world.add_observer(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
|
||||||
assert_eq!(obs.target(), Entity::PLACEHOLDER);
|
assert_eq!(obs.target(), None);
|
||||||
res.observed("event_a");
|
res.observed("event_a");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1208,7 +1208,7 @@ mod tests {
|
|||||||
.observe(|_: Trigger<EventA>, mut res: ResMut<Order>| res.observed("a_1"))
|
.observe(|_: Trigger<EventA>, mut res: ResMut<Order>| res.observed("a_1"))
|
||||||
.id();
|
.id();
|
||||||
world.add_observer(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
|
world.add_observer(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
|
||||||
assert_eq!(obs.target(), entity);
|
assert_eq!(obs.target().unwrap(), entity);
|
||||||
res.observed("a_2");
|
res.observed("a_2");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1628,7 +1628,7 @@ mod tests {
|
|||||||
|
|
||||||
world.add_observer(
|
world.add_observer(
|
||||||
|trigger: Trigger<EventPropagating>, query: Query<&A>, mut res: ResMut<Order>| {
|
|trigger: Trigger<EventPropagating>, query: Query<&A>, mut res: ResMut<Order>| {
|
||||||
if query.get(trigger.target()).is_ok() {
|
if query.get(trigger.target().unwrap()).is_ok() {
|
||||||
res.observed("event");
|
res.observed("event");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1651,7 +1651,7 @@ mod tests {
|
|||||||
fn observer_modifies_relationship() {
|
fn observer_modifies_relationship() {
|
||||||
fn on_add(trigger: Trigger<OnAdd, A>, mut commands: Commands) {
|
fn on_add(trigger: Trigger<OnAdd, A>, mut commands: Commands) {
|
||||||
commands
|
commands
|
||||||
.entity(trigger.target())
|
.entity(trigger.target().unwrap())
|
||||||
.with_related_entities::<crate::hierarchy::ChildOf>(|rsc| {
|
.with_related_entities::<crate::hierarchy::ChildOf>(|rsc| {
|
||||||
rsc.spawn_empty();
|
rsc.spawn_empty();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -123,8 +123,8 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
|
|||||||
/// struct Explode;
|
/// struct Explode;
|
||||||
///
|
///
|
||||||
/// world.add_observer(|trigger: Trigger<Explode>, mut commands: Commands| {
|
/// world.add_observer(|trigger: Trigger<Explode>, mut commands: Commands| {
|
||||||
/// println!("Entity {} goes BOOM!", trigger.target());
|
/// println!("Entity {} goes BOOM!", trigger.target().unwrap());
|
||||||
/// commands.entity(trigger.target()).despawn();
|
/// commands.entity(trigger.target().unwrap()).despawn();
|
||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// world.flush();
|
/// world.flush();
|
||||||
@ -157,7 +157,7 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
|
|||||||
/// # struct Explode;
|
/// # struct Explode;
|
||||||
/// world.entity_mut(e1).observe(|trigger: Trigger<Explode>, mut commands: Commands| {
|
/// world.entity_mut(e1).observe(|trigger: Trigger<Explode>, mut commands: Commands| {
|
||||||
/// println!("Boom!");
|
/// println!("Boom!");
|
||||||
/// commands.entity(trigger.target()).despawn();
|
/// commands.entity(trigger.target().unwrap()).despawn();
|
||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// world.entity_mut(e2).observe(|trigger: Trigger<Explode>, mut commands: Commands| {
|
/// world.entity_mut(e2).observe(|trigger: Trigger<Explode>, mut commands: Commands| {
|
||||||
|
|||||||
@ -47,6 +47,8 @@ use variadics_please::all_tuples;
|
|||||||
/// - **[`Ref`].**
|
/// - **[`Ref`].**
|
||||||
/// Similar to change detection filters but it is used as a query fetch parameter.
|
/// Similar to change detection filters but it is used as a query fetch parameter.
|
||||||
/// It exposes methods to check for changes to the wrapped component.
|
/// It exposes methods to check for changes to the wrapped component.
|
||||||
|
/// - **[`Mut`].**
|
||||||
|
/// Mutable component access, with change detection data.
|
||||||
/// - **[`Has`].**
|
/// - **[`Has`].**
|
||||||
/// Returns a bool indicating whether the entity has the specified component.
|
/// Returns a bool indicating whether the entity has the specified component.
|
||||||
///
|
///
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
bundle::Bundle,
|
bundle::Bundle,
|
||||||
entity::{hash_set::EntityHashSet, Entity},
|
entity::{hash_set::EntityHashSet, Entity},
|
||||||
|
prelude::Children,
|
||||||
relationship::{
|
relationship::{
|
||||||
Relationship, RelationshipHookMode, RelationshipSourceCollection, RelationshipTarget,
|
Relationship, RelationshipHookMode, RelationshipSourceCollection, RelationshipTarget,
|
||||||
},
|
},
|
||||||
@ -302,6 +303,15 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Despawns the children of this entity.
|
||||||
|
/// This entity will not be despawned.
|
||||||
|
///
|
||||||
|
/// This is a specialization of [`despawn_related`](EntityWorldMut::despawn_related), a more general method for despawning via relationships.
|
||||||
|
pub fn despawn_children(&mut self) -> &mut Self {
|
||||||
|
self.despawn_related::<Children>();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Inserts a component or bundle of components into the entity and all related entities,
|
/// Inserts a component or bundle of components into the entity and all related entities,
|
||||||
/// traversing the relationship tracked in `S` in a breadth-first manner.
|
/// traversing the relationship tracked in `S` in a breadth-first manner.
|
||||||
///
|
///
|
||||||
@ -467,6 +477,14 @@ impl<'a> EntityCommands<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Despawns the children of this entity.
|
||||||
|
/// This entity will not be despawned.
|
||||||
|
///
|
||||||
|
/// This is a specialization of [`despawn_related`](EntityCommands::despawn_related), a more general method for despawning via relationships.
|
||||||
|
pub fn despawn_children(&mut self) -> &mut Self {
|
||||||
|
self.despawn_related::<Children>()
|
||||||
|
}
|
||||||
|
|
||||||
/// Inserts a component or bundle of components into the entity and all related entities,
|
/// Inserts a component or bundle of components into the entity and all related entities,
|
||||||
/// traversing the relationship tracked in `S` in a breadth-first manner.
|
/// traversing the relationship tracked in `S` in a breadth-first manner.
|
||||||
///
|
///
|
||||||
|
|||||||
@ -20,7 +20,7 @@ use crate::{
|
|||||||
prelude::{IntoSystemSet, SystemSet},
|
prelude::{IntoSystemSet, SystemSet},
|
||||||
query::{Access, FilteredAccessSet},
|
query::{Access, FilteredAccessSet},
|
||||||
schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet},
|
schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet},
|
||||||
system::{ScheduleSystem, System, SystemIn, SystemParamValidationError},
|
system::{ScheduleSystem, System, SystemIn, SystemParamValidationError, SystemStateFlags},
|
||||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -171,26 +171,9 @@ impl System for ApplyDeferred {
|
|||||||
const { &FilteredAccessSet::new() }
|
const { &FilteredAccessSet::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_send(&self) -> bool {
|
fn flags(&self) -> SystemStateFlags {
|
||||||
// Although this system itself does nothing on its own, the system
|
// non-send , exclusive , no deferred
|
||||||
// executor uses it to apply deferred commands. Commands must be allowed
|
SystemStateFlags::NON_SEND | SystemStateFlags::EXCLUSIVE
|
||||||
// to access non-send resources, so this system must be non-send for
|
|
||||||
// scheduling purposes.
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_exclusive(&self) -> bool {
|
|
||||||
// This system is labeled exclusive because it is used by the system
|
|
||||||
// executor to find places where deferred commands should be applied,
|
|
||||||
// and commands can only be applied with exclusive access to the world.
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_deferred(&self) -> bool {
|
|
||||||
// This system itself doesn't have any commands to apply, but when it
|
|
||||||
// is pulled from the schedule to be ran, the executor will apply
|
|
||||||
// deferred commands from other systems.
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn run_unsafe(
|
unsafe fn run_unsafe(
|
||||||
|
|||||||
@ -874,7 +874,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "Known failing but fix is non-trivial: https://github.com/bevyengine/bevy/issues/4381"]
|
|
||||||
fn filtered_components() {
|
fn filtered_components() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
world.spawn(A);
|
world.spawn(A);
|
||||||
|
|||||||
@ -1418,26 +1418,24 @@ impl ScheduleGraph {
|
|||||||
if system_a.is_exclusive() || system_b.is_exclusive() {
|
if system_a.is_exclusive() || system_b.is_exclusive() {
|
||||||
conflicting_systems.push((a, b, Vec::new()));
|
conflicting_systems.push((a, b, Vec::new()));
|
||||||
} else {
|
} else {
|
||||||
let access_a = system_a.component_access();
|
let access_a = system_a.component_access_set();
|
||||||
let access_b = system_b.component_access();
|
let access_b = system_b.component_access_set();
|
||||||
if !access_a.is_compatible(access_b) {
|
match access_a.get_conflicts(access_b) {
|
||||||
match access_a.get_conflicts(access_b) {
|
AccessConflicts::Individual(conflicts) => {
|
||||||
AccessConflicts::Individual(conflicts) => {
|
let conflicts: Vec<_> = conflicts
|
||||||
let conflicts: Vec<_> = conflicts
|
.ones()
|
||||||
.ones()
|
.map(ComponentId::get_sparse_set_index)
|
||||||
.map(ComponentId::get_sparse_set_index)
|
.filter(|id| !ignored_ambiguities.contains(id))
|
||||||
.filter(|id| !ignored_ambiguities.contains(id))
|
.collect();
|
||||||
.collect();
|
if !conflicts.is_empty() {
|
||||||
if !conflicts.is_empty() {
|
conflicting_systems.push((a, b, conflicts));
|
||||||
conflicting_systems.push((a, b, conflicts));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AccessConflicts::All => {
|
|
||||||
// there is no specific component conflicting, but the systems are overall incompatible
|
|
||||||
// for example 2 systems with `Query<EntityMut>`
|
|
||||||
conflicting_systems.push((a, b, Vec::new()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AccessConflicts::All => {
|
||||||
|
// there is no specific component conflicting, but the systems are overall incompatible
|
||||||
|
// for example 2 systems with `Query<EntityMut>`
|
||||||
|
conflicting_systems.push((a, b, Vec::new()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -137,16 +137,9 @@ where
|
|||||||
self.system.component_access_set()
|
self.system.component_access_set()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_send(&self) -> bool {
|
#[inline]
|
||||||
self.system.is_send()
|
fn flags(&self) -> super::SystemStateFlags {
|
||||||
}
|
self.system.flags()
|
||||||
|
|
||||||
fn is_exclusive(&self) -> bool {
|
|
||||||
self.system.is_exclusive()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_deferred(&self) -> bool {
|
|
||||||
self.system.has_deferred()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@ -152,16 +152,9 @@ where
|
|||||||
&self.component_access_set
|
&self.component_access_set
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_send(&self) -> bool {
|
#[inline]
|
||||||
self.a.is_send() && self.b.is_send()
|
fn flags(&self) -> super::SystemStateFlags {
|
||||||
}
|
self.a.flags() | self.b.flags()
|
||||||
|
|
||||||
fn is_exclusive(&self) -> bool {
|
|
||||||
self.a.is_exclusive() || self.b.is_exclusive()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_deferred(&self) -> bool {
|
|
||||||
self.a.has_deferred() || self.b.has_deferred()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn run_unsafe(
|
unsafe fn run_unsafe(
|
||||||
@ -378,16 +371,9 @@ where
|
|||||||
&self.component_access_set
|
&self.component_access_set
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_send(&self) -> bool {
|
#[inline]
|
||||||
self.a.is_send() && self.b.is_send()
|
fn flags(&self) -> super::SystemStateFlags {
|
||||||
}
|
self.a.flags() | self.b.flags()
|
||||||
|
|
||||||
fn is_exclusive(&self) -> bool {
|
|
||||||
self.a.is_exclusive() || self.b.is_exclusive()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_deferred(&self) -> bool {
|
|
||||||
self.a.has_deferred() || self.b.has_deferred()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn run_unsafe(
|
unsafe fn run_unsafe(
|
||||||
|
|||||||
@ -13,7 +13,7 @@ use alloc::{borrow::Cow, vec, vec::Vec};
|
|||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
use variadics_please::all_tuples;
|
use variadics_please::all_tuples;
|
||||||
|
|
||||||
use super::SystemParamValidationError;
|
use super::{SystemParamValidationError, SystemStateFlags};
|
||||||
|
|
||||||
/// A function system that runs with exclusive [`World`] access.
|
/// A function system that runs with exclusive [`World`] access.
|
||||||
///
|
///
|
||||||
@ -98,22 +98,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_send(&self) -> bool {
|
fn flags(&self) -> SystemStateFlags {
|
||||||
// exclusive systems should have access to non-send resources
|
// non-send , exclusive , no deferred
|
||||||
// the executor runs exclusive systems on the main thread, so this
|
// the executor runs exclusive systems on the main thread, so this
|
||||||
// field reflects that constraint
|
// field reflects that constraint
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_exclusive(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn has_deferred(&self) -> bool {
|
|
||||||
// exclusive systems have no deferred system params
|
// exclusive systems have no deferred system params
|
||||||
false
|
SystemStateFlags::NON_SEND | SystemStateFlags::EXCLUSIVE
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@ -17,7 +17,9 @@ use variadics_please::all_tuples;
|
|||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
use tracing::{info_span, Span};
|
use tracing::{info_span, Span};
|
||||||
|
|
||||||
use super::{IntoSystem, ReadOnlySystem, SystemParamBuilder, SystemParamValidationError};
|
use super::{
|
||||||
|
IntoSystem, ReadOnlySystem, SystemParamBuilder, SystemParamValidationError, SystemStateFlags,
|
||||||
|
};
|
||||||
|
|
||||||
/// The metadata of a [`System`].
|
/// The metadata of a [`System`].
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -29,8 +31,7 @@ pub struct SystemMeta {
|
|||||||
pub(crate) component_access_set: FilteredAccessSet<ComponentId>,
|
pub(crate) component_access_set: FilteredAccessSet<ComponentId>,
|
||||||
// NOTE: this must be kept private. making a SystemMeta non-send is irreversible to prevent
|
// NOTE: this must be kept private. making a SystemMeta non-send is irreversible to prevent
|
||||||
// SystemParams from overriding each other
|
// SystemParams from overriding each other
|
||||||
is_send: bool,
|
flags: SystemStateFlags,
|
||||||
has_deferred: bool,
|
|
||||||
pub(crate) last_run: Tick,
|
pub(crate) last_run: Tick,
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
pub(crate) system_span: Span,
|
pub(crate) system_span: Span,
|
||||||
@ -44,8 +45,7 @@ impl SystemMeta {
|
|||||||
Self {
|
Self {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
component_access_set: FilteredAccessSet::default(),
|
component_access_set: FilteredAccessSet::default(),
|
||||||
is_send: true,
|
flags: SystemStateFlags::empty(),
|
||||||
has_deferred: false,
|
|
||||||
last_run: Tick::new(0),
|
last_run: Tick::new(0),
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
system_span: info_span!("system", name = name),
|
system_span: info_span!("system", name = name),
|
||||||
@ -78,7 +78,7 @@ impl SystemMeta {
|
|||||||
/// Returns true if the system is [`Send`].
|
/// Returns true if the system is [`Send`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_send(&self) -> bool {
|
pub fn is_send(&self) -> bool {
|
||||||
self.is_send
|
!self.flags.intersects(SystemStateFlags::NON_SEND)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the system to be not [`Send`].
|
/// Sets the system to be not [`Send`].
|
||||||
@ -86,20 +86,20 @@ impl SystemMeta {
|
|||||||
/// This is irreversible.
|
/// This is irreversible.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_non_send(&mut self) {
|
pub fn set_non_send(&mut self) {
|
||||||
self.is_send = false;
|
self.flags |= SystemStateFlags::NON_SEND;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the system has deferred [`SystemParam`]'s
|
/// Returns true if the system has deferred [`SystemParam`]'s
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn has_deferred(&self) -> bool {
|
pub fn has_deferred(&self) -> bool {
|
||||||
self.has_deferred
|
self.flags.intersects(SystemStateFlags::DEFERRED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marks the system as having deferred buffers like [`Commands`](`super::Commands`)
|
/// Marks the system as having deferred buffers like [`Commands`](`super::Commands`)
|
||||||
/// This lets the scheduler insert [`ApplyDeferred`](`crate::prelude::ApplyDeferred`) systems automatically.
|
/// This lets the scheduler insert [`ApplyDeferred`](`crate::prelude::ApplyDeferred`) systems automatically.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_has_deferred(&mut self) {
|
pub fn set_has_deferred(&mut self) {
|
||||||
self.has_deferred = true;
|
self.flags |= SystemStateFlags::DEFERRED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the [`FilteredAccessSet`] for [`ComponentId`].
|
/// Returns a reference to the [`FilteredAccessSet`] for [`ComponentId`].
|
||||||
@ -631,18 +631,8 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_send(&self) -> bool {
|
fn flags(&self) -> SystemStateFlags {
|
||||||
self.system_meta.is_send
|
self.system_meta.flags
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_exclusive(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn has_deferred(&self) -> bool {
|
|
||||||
self.system_meta.has_deferred
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@ -127,18 +127,8 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_send(&self) -> bool {
|
fn flags(&self) -> super::SystemStateFlags {
|
||||||
self.observer.is_send()
|
self.observer.flags()
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_exclusive(&self) -> bool {
|
|
||||||
self.observer.is_exclusive()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn has_deferred(&self) -> bool {
|
|
||||||
self.observer.has_deferred()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@ -2016,17 +2016,67 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
|
|||||||
self.as_nop().get(entity).is_ok()
|
self.as_nop().get(entity).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a [`QueryLens`] that can be used to get a query with a more general fetch.
|
/// Returns a [`QueryLens`] that can be used to construct a new [`Query`] giving more
|
||||||
|
/// restrictive access to the entities matched by the current query.
|
||||||
///
|
///
|
||||||
/// For example, this can transform a `Query<(&A, &mut B)>` to a `Query<&B>`.
|
/// A transmute is valid only if `NewD` has a subset of the read, write, and required access
|
||||||
/// This can be useful for passing the query to another function. Note that since
|
/// of the current query. A precise description of the access required by each parameter
|
||||||
/// filter terms are dropped, non-archetypal filters like [`Added`](crate::query::Added),
|
/// type is given in the table below, but typical uses are to:
|
||||||
/// [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) will not be
|
/// * Remove components, e.g. `Query<(&A, &B)>` to `Query<&A>`.
|
||||||
/// respected. To maintain or change filter terms see [`Self::transmute_lens_filtered`]
|
/// * Retrieve an existing component with reduced or equal access, e.g. `Query<&mut A>` to `Query<&A>`
|
||||||
|
/// or `Query<&T>` to `Query<Ref<T>>`.
|
||||||
|
/// * Add parameters with no new access, for example adding an `Entity` parameter.
|
||||||
|
///
|
||||||
|
/// Note that since filter terms are dropped, non-archetypal filters like
|
||||||
|
/// [`Added`], [`Changed`] and [`Spawned`] will not be respected. To maintain or change filter
|
||||||
|
/// terms see [`Self::transmute_lens_filtered`].
|
||||||
|
///
|
||||||
|
/// |`QueryData` parameter type|Access required|
|
||||||
|
/// |----|----|
|
||||||
|
/// |[`Entity`], [`EntityLocation`], [`SpawnDetails`], [`&Archetype`], [`Has<T>`], [`PhantomData<T>`]|No access|
|
||||||
|
/// |[`EntityMut`]|Read and write access to all components, but no required access|
|
||||||
|
/// |[`EntityRef`]|Read access to all components, but no required access|
|
||||||
|
/// |`&T`, [`Ref<T>`]|Read and required access to `T`|
|
||||||
|
/// |`&mut T`, [`Mut<T>`]|Read, write and required access to `T`|
|
||||||
|
/// |[`Option<T>`], [`AnyOf<(D, ...)>`]|Read and write access to `T`, but no required access|
|
||||||
|
/// |Tuples of query data and<br/>`#[derive(QueryData)]` structs|The union of the access of their subqueries|
|
||||||
|
/// |[`FilteredEntityRef`], [`FilteredEntityMut`]|Determined by the [`QueryBuilder`] used to construct them. Any query can be transmuted to them, and they will receive the access of the source query. When combined with other `QueryData`, they will receive any access of the source query that does not conflict with the other data|
|
||||||
|
///
|
||||||
|
/// `transmute_lens` drops filter terms, but [`Self::transmute_lens_filtered`] supports returning a [`QueryLens`] with a new
|
||||||
|
/// filter type - the access required by filter parameters are as follows.
|
||||||
|
///
|
||||||
|
/// |`QueryFilter` parameter type|Access required|
|
||||||
|
/// |----|----|
|
||||||
|
/// |[`Added<T>`], [`Changed<T>`]|Read and required access to `T`|
|
||||||
|
/// |[`With<T>`], [`Without<T>`]|No access|
|
||||||
|
/// |[`Or<(T, ...)>`]|Read access of the subqueries, but no required access|
|
||||||
|
/// |Tuples of query filters and `#[derive(QueryFilter)]` structs|The union of the access of their subqueries|
|
||||||
|
///
|
||||||
|
/// [`Added`]: crate::query::Added
|
||||||
|
/// [`Added<T>`]: crate::query::Added
|
||||||
|
/// [`AnyOf<(D, ...)>`]: crate::query::AnyOf
|
||||||
|
/// [`&Archetype`]: crate::archetype::Archetype
|
||||||
|
/// [`Changed`]: crate::query::Changed
|
||||||
|
/// [`Changed<T>`]: crate::query::Changed
|
||||||
|
/// [`EntityMut`]: crate::world::EntityMut
|
||||||
|
/// [`EntityLocation`]: crate::entity::EntityLocation
|
||||||
|
/// [`EntityRef`]: crate::world::EntityRef
|
||||||
|
/// [`FilteredEntityRef`]: crate::world::FilteredEntityRef
|
||||||
|
/// [`FilteredEntityMut`]: crate::world::FilteredEntityMut
|
||||||
|
/// [`Has<T>`]: crate::query::Has
|
||||||
|
/// [`Mut<T>`]: crate::world::Mut
|
||||||
|
/// [`Or<(T, ...)>`]: crate::query::Or
|
||||||
|
/// [`QueryBuilder`]: crate::query::QueryBuilder
|
||||||
|
/// [`Ref<T>`]: crate::world::Ref
|
||||||
|
/// [`SpawnDetails`]: crate::query::SpawnDetails
|
||||||
|
/// [`Spawned`]: crate::query::Spawned
|
||||||
|
/// [`With<T>`]: crate::query::With
|
||||||
|
/// [`Without<T>`]: crate::query::Without
|
||||||
///
|
///
|
||||||
/// ## Panics
|
/// ## Panics
|
||||||
///
|
///
|
||||||
/// This will panic if `NewD` is not a subset of the original fetch `D`
|
/// This will panic if the access required by `NewD` is not a subset of that required by
|
||||||
|
/// the original fetch `D`.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
@ -2065,30 +2115,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
|
|||||||
/// # schedule.run(&mut world);
|
/// # schedule.run(&mut world);
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Allowed Transmutes
|
|
||||||
///
|
|
||||||
/// Besides removing parameters from the query,
|
|
||||||
/// you can also make limited changes to the types of parameters.
|
|
||||||
/// The new query must have a subset of the *read*, *write*, and *required* access of the original query.
|
|
||||||
///
|
|
||||||
/// * `&mut T` and [`Mut<T>`](crate::change_detection::Mut) have read, write, and required access to `T`
|
|
||||||
/// * `&T` and [`Ref<T>`](crate::change_detection::Ref) have read and required access to `T`
|
|
||||||
/// * [`Option<D>`] and [`AnyOf<(D, ...)>`](crate::query::AnyOf) have the read and write access of the subqueries, but no required access
|
|
||||||
/// * Tuples of query data and `#[derive(QueryData)]` structs have the union of the access of their subqueries
|
|
||||||
/// * [`EntityMut`](crate::world::EntityMut) has read and write access to all components, but no required access
|
|
||||||
/// * [`EntityRef`](crate::world::EntityRef) has read access to all components, but no required access
|
|
||||||
/// * [`Entity`], [`EntityLocation`], [`SpawnDetails`], [`&Archetype`], [`Has<T>`], and [`PhantomData<T>`] have no access at all,
|
|
||||||
/// so can be added to any query
|
|
||||||
/// * [`FilteredEntityRef`](crate::world::FilteredEntityRef) and [`FilteredEntityMut`](crate::world::FilteredEntityMut)
|
|
||||||
/// have access determined by the [`QueryBuilder`](crate::query::QueryBuilder) used to construct them.
|
|
||||||
/// Any query can be transmuted to them, and they will receive the access of the source query.
|
|
||||||
/// When combined with other `QueryData`, they will receive any access of the source query that does not conflict with the other data.
|
|
||||||
/// * [`Added<T>`](crate::query::Added) and [`Changed<T>`](crate::query::Changed) filters have read and required access to `T`
|
|
||||||
/// * [`With<T>`](crate::query::With) and [`Without<T>`](crate::query::Without) filters have no access at all,
|
|
||||||
/// so can be added to any query
|
|
||||||
/// * Tuples of query filters and `#[derive(QueryFilter)]` structs have the union of the access of their subqueries
|
|
||||||
/// * [`Or<(F, ...)>`](crate::query::Or) filters have the read access of the subqueries, but no required access
|
|
||||||
///
|
|
||||||
/// ### Examples of valid transmutes
|
/// ### Examples of valid transmutes
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
@ -2165,28 +2191,21 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
|
|||||||
/// // Nested inside of an `Or` filter, they have the same access as `Option<&T>`.
|
/// // Nested inside of an `Or` filter, they have the same access as `Option<&T>`.
|
||||||
/// assert_valid_transmute_filtered::<Option<&T>, (), Entity, Or<(Changed<T>, With<U>)>>();
|
/// assert_valid_transmute_filtered::<Option<&T>, (), Entity, Or<(Changed<T>, With<U>)>>();
|
||||||
/// ```
|
/// ```
|
||||||
///
|
|
||||||
/// [`EntityLocation`]: crate::entity::EntityLocation
|
|
||||||
/// [`SpawnDetails`]: crate::query::SpawnDetails
|
|
||||||
/// [`&Archetype`]: crate::archetype::Archetype
|
|
||||||
/// [`Has<T>`]: crate::query::Has
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn transmute_lens<NewD: QueryData>(&mut self) -> QueryLens<'_, NewD> {
|
pub fn transmute_lens<NewD: QueryData>(&mut self) -> QueryLens<'_, NewD> {
|
||||||
self.transmute_lens_filtered::<NewD, ()>()
|
self.transmute_lens_filtered::<NewD, ()>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a [`QueryLens`] that can be used to get a query with a more general fetch.
|
/// Returns a [`QueryLens`] that can be used to construct a new `Query` giving more restrictive
|
||||||
|
/// access to the entities matched by the current query.
|
||||||
|
///
|
||||||
/// This consumes the [`Query`] to return results with the actual "inner" world lifetime.
|
/// This consumes the [`Query`] to return results with the actual "inner" world lifetime.
|
||||||
///
|
///
|
||||||
/// For example, this can transform a `Query<(&A, &mut B)>` to a `Query<&B>`.
|
/// See [`Self::transmute_lens`] for a description of allowed transmutes.
|
||||||
/// This can be useful for passing the query to another function. Note that since
|
|
||||||
/// filter terms are dropped, non-archetypal filters like [`Added`](crate::query::Added),
|
|
||||||
/// [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) will not be
|
|
||||||
/// respected. To maintain or change filter terms see [`Self::transmute_lens_filtered`]
|
|
||||||
///
|
///
|
||||||
/// ## Panics
|
/// ## Panics
|
||||||
///
|
///
|
||||||
/// This will panic if `NewD` is not a subset of the original fetch `Q`
|
/// This will panic if `NewD` is not a subset of the original fetch `D`
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
@ -2225,22 +2244,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
|
|||||||
/// # schedule.run(&mut world);
|
/// # schedule.run(&mut world);
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Allowed Transmutes
|
|
||||||
///
|
|
||||||
/// Besides removing parameters from the query, you can also
|
|
||||||
/// make limited changes to the types of parameters.
|
|
||||||
///
|
|
||||||
/// * Can always add/remove [`Entity`]
|
|
||||||
/// * Can always add/remove [`EntityLocation`]
|
|
||||||
/// * Can always add/remove [`&Archetype`]
|
|
||||||
/// * `Ref<T>` <-> `&T`
|
|
||||||
/// * `&mut T` -> `&T`
|
|
||||||
/// * `&mut T` -> `Ref<T>`
|
|
||||||
/// * [`EntityMut`](crate::world::EntityMut) -> [`EntityRef`](crate::world::EntityRef)
|
|
||||||
///
|
|
||||||
/// [`EntityLocation`]: crate::entity::EntityLocation
|
|
||||||
/// [`&Archetype`]: crate::archetype::Archetype
|
|
||||||
///
|
|
||||||
/// # See also
|
/// # See also
|
||||||
///
|
///
|
||||||
/// - [`transmute_lens`](Self::transmute_lens) to convert to a lens using a mutable borrow of the [`Query`].
|
/// - [`transmute_lens`](Self::transmute_lens) to convert to a lens using a mutable borrow of the [`Query`].
|
||||||
@ -2251,6 +2254,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
|
|||||||
|
|
||||||
/// Equivalent to [`Self::transmute_lens`] but also includes a [`QueryFilter`] type.
|
/// Equivalent to [`Self::transmute_lens`] but also includes a [`QueryFilter`] type.
|
||||||
///
|
///
|
||||||
|
/// See [`Self::transmute_lens`] for a description of allowed transmutes.
|
||||||
|
///
|
||||||
/// Note that the lens will iterate the same tables and archetypes as the original query. This means that
|
/// Note that the lens will iterate the same tables and archetypes as the original query. This means that
|
||||||
/// additional archetypal query terms like [`With`](crate::query::With) and [`Without`](crate::query::Without)
|
/// additional archetypal query terms like [`With`](crate::query::With) and [`Without`](crate::query::Without)
|
||||||
/// will not necessarily be respected and non-archetypal terms like [`Added`](crate::query::Added),
|
/// will not necessarily be respected and non-archetypal terms like [`Added`](crate::query::Added),
|
||||||
@ -2266,10 +2271,13 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
|
|||||||
/// Equivalent to [`Self::transmute_lens_inner`] but also includes a [`QueryFilter`] type.
|
/// Equivalent to [`Self::transmute_lens_inner`] but also includes a [`QueryFilter`] type.
|
||||||
/// This consumes the [`Query`] to return results with the actual "inner" world lifetime.
|
/// This consumes the [`Query`] to return results with the actual "inner" world lifetime.
|
||||||
///
|
///
|
||||||
|
/// See [`Self::transmute_lens`] for a description of allowed transmutes.
|
||||||
|
///
|
||||||
/// Note that the lens will iterate the same tables and archetypes as the original query. This means that
|
/// Note that the lens will iterate the same tables and archetypes as the original query. This means that
|
||||||
/// additional archetypal query terms like [`With`](crate::query::With) and [`Without`](crate::query::Without)
|
/// additional archetypal query terms like [`With`](crate::query::With) and [`Without`](crate::query::Without)
|
||||||
/// will not necessarily be respected and non-archetypal terms like [`Added`](crate::query::Added),
|
/// will not necessarily be respected and non-archetypal terms like [`Added`](crate::query::Added),
|
||||||
/// [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) will only be respected if they
|
/// [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) will only be respected if they
|
||||||
|
/// are in the type signature.
|
||||||
///
|
///
|
||||||
/// # See also
|
/// # See also
|
||||||
///
|
///
|
||||||
|
|||||||
@ -8,7 +8,7 @@ use crate::{
|
|||||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World},
|
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{IntoSystem, SystemParamValidationError};
|
use super::{IntoSystem, SystemParamValidationError, SystemStateFlags};
|
||||||
|
|
||||||
/// A wrapper system to change a system that returns `()` to return `Ok(())` to make it into a [`ScheduleSystem`]
|
/// A wrapper system to change a system that returns `()` to return `Ok(())` to make it into a [`ScheduleSystem`]
|
||||||
pub struct InfallibleSystemWrapper<S: System<In = ()>>(S);
|
pub struct InfallibleSystemWrapper<S: System<In = ()>>(S);
|
||||||
@ -44,18 +44,8 @@ impl<S: System<In = ()>> System for InfallibleSystemWrapper<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_send(&self) -> bool {
|
fn flags(&self) -> SystemStateFlags {
|
||||||
self.0.is_send()
|
self.0.flags()
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_exclusive(&self) -> bool {
|
|
||||||
self.0.is_exclusive()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn has_deferred(&self) -> bool {
|
|
||||||
self.0.has_deferred()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -172,16 +162,9 @@ where
|
|||||||
self.system.component_access_set()
|
self.system.component_access_set()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_send(&self) -> bool {
|
#[inline]
|
||||||
self.system.is_send()
|
fn flags(&self) -> SystemStateFlags {
|
||||||
}
|
self.system.flags()
|
||||||
|
|
||||||
fn is_exclusive(&self) -> bool {
|
|
||||||
self.system.is_exclusive()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_deferred(&self) -> bool {
|
|
||||||
self.system.has_deferred()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn run_unsafe(
|
unsafe fn run_unsafe(
|
||||||
@ -281,16 +264,9 @@ where
|
|||||||
self.system.component_access_set()
|
self.system.component_access_set()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_send(&self) -> bool {
|
#[inline]
|
||||||
self.system.is_send()
|
fn flags(&self) -> SystemStateFlags {
|
||||||
}
|
self.system.flags()
|
||||||
|
|
||||||
fn is_exclusive(&self) -> bool {
|
|
||||||
self.system.is_exclusive()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_deferred(&self) -> bool {
|
|
||||||
self.system.has_deferred()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn run_unsafe(
|
unsafe fn run_unsafe(
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
clippy::module_inception,
|
clippy::module_inception,
|
||||||
reason = "This instance of module inception is being discussed; see #17353."
|
reason = "This instance of module inception is being discussed; see #17353."
|
||||||
)]
|
)]
|
||||||
|
use bitflags::bitflags;
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@ -19,6 +20,18 @@ use core::any::TypeId;
|
|||||||
|
|
||||||
use super::{IntoSystem, SystemParamValidationError};
|
use super::{IntoSystem, SystemParamValidationError};
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
/// Bitflags representing system states and requirements.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct SystemStateFlags: u8 {
|
||||||
|
/// Set if system cannot be sent across threads
|
||||||
|
const NON_SEND = 1 << 0;
|
||||||
|
/// Set if system requires exclusive World access
|
||||||
|
const EXCLUSIVE = 1 << 1;
|
||||||
|
/// Set if system has deferred buffers.
|
||||||
|
const DEFERRED = 1 << 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
/// An ECS system that can be added to a [`Schedule`](crate::schedule::Schedule)
|
/// An ECS system that can be added to a [`Schedule`](crate::schedule::Schedule)
|
||||||
///
|
///
|
||||||
/// Systems are functions with all arguments implementing
|
/// Systems are functions with all arguments implementing
|
||||||
@ -50,14 +63,26 @@ pub trait System: Send + Sync + 'static {
|
|||||||
/// Returns the system's component [`FilteredAccessSet`].
|
/// Returns the system's component [`FilteredAccessSet`].
|
||||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId>;
|
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId>;
|
||||||
|
|
||||||
|
/// Returns the [`SystemStateFlags`] of the system.
|
||||||
|
fn flags(&self) -> SystemStateFlags;
|
||||||
|
|
||||||
/// Returns true if the system is [`Send`].
|
/// Returns true if the system is [`Send`].
|
||||||
fn is_send(&self) -> bool;
|
#[inline]
|
||||||
|
fn is_send(&self) -> bool {
|
||||||
|
!self.flags().intersects(SystemStateFlags::NON_SEND)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the system must be run exclusively.
|
/// Returns true if the system must be run exclusively.
|
||||||
fn is_exclusive(&self) -> bool;
|
#[inline]
|
||||||
|
fn is_exclusive(&self) -> bool {
|
||||||
|
self.flags().intersects(SystemStateFlags::EXCLUSIVE)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if system has deferred buffers.
|
/// Returns true if system has deferred buffers.
|
||||||
fn has_deferred(&self) -> bool;
|
#[inline]
|
||||||
|
fn has_deferred(&self) -> bool {
|
||||||
|
self.flags().intersects(SystemStateFlags::DEFERRED)
|
||||||
|
}
|
||||||
|
|
||||||
/// Runs the system with the given input in the world. Unlike [`System::run`], this function
|
/// Runs the system with the given input in the world. Unlike [`System::run`], this function
|
||||||
/// can be called in parallel with other systems and may break Rust's aliasing rules
|
/// can be called in parallel with other systems and may break Rust's aliasing rules
|
||||||
|
|||||||
@ -23,7 +23,7 @@ use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World, ON_INSERT, ON_REPLAC
|
|||||||
///
|
///
|
||||||
/// This means that in order to add entities, for example, you will need to use commands instead of the world directly.
|
/// This means that in order to add entities, for example, you will need to use commands instead of the world directly.
|
||||||
pub struct DeferredWorld<'w> {
|
pub struct DeferredWorld<'w> {
|
||||||
// SAFETY: Implementors must not use this reference to make structural changes
|
// SAFETY: Implementers must not use this reference to make structural changes
|
||||||
world: UnsafeWorldCell<'w>,
|
world: UnsafeWorldCell<'w>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +157,7 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
if archetype.has_replace_observer() {
|
if archetype.has_replace_observer() {
|
||||||
self.trigger_observers(
|
self.trigger_observers(
|
||||||
ON_REPLACE,
|
ON_REPLACE,
|
||||||
entity,
|
Some(entity),
|
||||||
[component_id].into_iter(),
|
[component_id].into_iter(),
|
||||||
MaybeLocation::caller(),
|
MaybeLocation::caller(),
|
||||||
);
|
);
|
||||||
@ -197,7 +197,7 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
if archetype.has_insert_observer() {
|
if archetype.has_insert_observer() {
|
||||||
self.trigger_observers(
|
self.trigger_observers(
|
||||||
ON_INSERT,
|
ON_INSERT,
|
||||||
entity,
|
Some(entity),
|
||||||
[component_id].into_iter(),
|
[component_id].into_iter(),
|
||||||
MaybeLocation::caller(),
|
MaybeLocation::caller(),
|
||||||
);
|
);
|
||||||
@ -738,7 +738,7 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
pub(crate) unsafe fn trigger_observers(
|
pub(crate) unsafe fn trigger_observers(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: ComponentId,
|
event: ComponentId,
|
||||||
target: Entity,
|
target: Option<Entity>,
|
||||||
components: impl Iterator<Item = ComponentId> + Clone,
|
components: impl Iterator<Item = ComponentId> + Clone,
|
||||||
caller: MaybeLocation,
|
caller: MaybeLocation,
|
||||||
) {
|
) {
|
||||||
@ -761,7 +761,7 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
pub(crate) unsafe fn trigger_observers_with_data<E, T>(
|
pub(crate) unsafe fn trigger_observers_with_data<E, T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: ComponentId,
|
event: ComponentId,
|
||||||
mut target: Entity,
|
target: Option<Entity>,
|
||||||
components: impl Iterator<Item = ComponentId> + Clone,
|
components: impl Iterator<Item = ComponentId> + Clone,
|
||||||
data: &mut E,
|
data: &mut E,
|
||||||
mut propagate: bool,
|
mut propagate: bool,
|
||||||
@ -769,18 +769,20 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
) where
|
) where
|
||||||
T: Traversal<E>,
|
T: Traversal<E>,
|
||||||
{
|
{
|
||||||
|
Observers::invoke::<_>(
|
||||||
|
self.reborrow(),
|
||||||
|
event,
|
||||||
|
target,
|
||||||
|
components.clone(),
|
||||||
|
data,
|
||||||
|
&mut propagate,
|
||||||
|
caller,
|
||||||
|
);
|
||||||
|
let Some(mut target) = target else { return };
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
Observers::invoke::<_>(
|
|
||||||
self.reborrow(),
|
|
||||||
event,
|
|
||||||
target,
|
|
||||||
components.clone(),
|
|
||||||
data,
|
|
||||||
&mut propagate,
|
|
||||||
caller,
|
|
||||||
);
|
|
||||||
if !propagate {
|
if !propagate {
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
if let Some(traverse_to) = self
|
if let Some(traverse_to) = self
|
||||||
.get_entity(target)
|
.get_entity(target)
|
||||||
@ -792,6 +794,15 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
Observers::invoke::<_>(
|
||||||
|
self.reborrow(),
|
||||||
|
event,
|
||||||
|
Some(target),
|
||||||
|
components.clone(),
|
||||||
|
data,
|
||||||
|
&mut propagate,
|
||||||
|
caller,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2371,7 +2371,7 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
if archetype.has_despawn_observer() {
|
if archetype.has_despawn_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_DESPAWN,
|
ON_DESPAWN,
|
||||||
self.entity,
|
Some(self.entity),
|
||||||
archetype.components(),
|
archetype.components(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -2385,7 +2385,7 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
if archetype.has_replace_observer() {
|
if archetype.has_replace_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_REPLACE,
|
ON_REPLACE,
|
||||||
self.entity,
|
Some(self.entity),
|
||||||
archetype.components(),
|
archetype.components(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -2400,7 +2400,7 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
if archetype.has_remove_observer() {
|
if archetype.has_remove_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_REMOVE,
|
ON_REMOVE,
|
||||||
self.entity,
|
Some(self.entity),
|
||||||
archetype.components(),
|
archetype.components(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -5749,7 +5749,9 @@ mod tests {
|
|||||||
let entity = world
|
let entity = world
|
||||||
.spawn_empty()
|
.spawn_empty()
|
||||||
.observe(|trigger: Trigger<TestEvent>, mut commands: Commands| {
|
.observe(|trigger: Trigger<TestEvent>, mut commands: Commands| {
|
||||||
commands.entity(trigger.target()).insert(TestComponent(0));
|
commands
|
||||||
|
.entity(trigger.target().unwrap())
|
||||||
|
.insert(TestComponent(0));
|
||||||
})
|
})
|
||||||
.id();
|
.id();
|
||||||
|
|
||||||
@ -5769,7 +5771,7 @@ mod tests {
|
|||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
world.add_observer(
|
world.add_observer(
|
||||||
|trigger: Trigger<OnAdd, TestComponent>, mut commands: Commands| {
|
|trigger: Trigger<OnAdd, TestComponent>, mut commands: Commands| {
|
||||||
commands.entity(trigger.target()).despawn();
|
commands.entity(trigger.target().unwrap()).despawn();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let entity = world.spawn_empty().id();
|
let entity = world.spawn_empty().id();
|
||||||
|
|||||||
@ -81,19 +81,35 @@ impl ImageLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How to determine an image's format when loading.
|
||||||
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
|
||||||
pub enum ImageFormatSetting {
|
pub enum ImageFormatSetting {
|
||||||
|
/// Determine the image format from its file extension.
|
||||||
|
///
|
||||||
|
/// This is the default.
|
||||||
#[default]
|
#[default]
|
||||||
FromExtension,
|
FromExtension,
|
||||||
|
/// Declare the image format explicitly.
|
||||||
Format(ImageFormat),
|
Format(ImageFormat),
|
||||||
|
/// Guess the image format by looking for magic bytes at the
|
||||||
|
/// beginning of its data.
|
||||||
Guess,
|
Guess,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Settings for loading an [`Image`] using an [`ImageLoader`].
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct ImageLoaderSettings {
|
pub struct ImageLoaderSettings {
|
||||||
|
/// How to determine the image's format.
|
||||||
pub format: ImageFormatSetting,
|
pub format: ImageFormatSetting,
|
||||||
|
/// Specifies whether image data is linear
|
||||||
|
/// or in sRGB space when this is not determined by
|
||||||
|
/// the image format.
|
||||||
pub is_srgb: bool,
|
pub is_srgb: bool,
|
||||||
|
/// [`ImageSampler`] to use when rendering - this does
|
||||||
|
/// not affect the loading of the image data.
|
||||||
pub sampler: ImageSampler,
|
pub sampler: ImageSampler,
|
||||||
|
/// Where the asset will be used - see the docs on
|
||||||
|
/// [`RenderAssetUsages`] for details.
|
||||||
pub asset_usage: RenderAssetUsages,
|
pub asset_usage: RenderAssetUsages,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,11 +124,14 @@ impl Default for ImageLoaderSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An error when loading an image using [`ImageLoader`].
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum ImageLoaderError {
|
pub enum ImageLoaderError {
|
||||||
#[error("Could load shader: {0}")]
|
/// An error occurred while trying to load the image bytes.
|
||||||
|
#[error("Failed to load image bytes: {0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
|
/// An error occurred while trying to decode the image bytes.
|
||||||
#[error("Could not load texture file: {0}")]
|
#[error("Could not load texture file: {0}")]
|
||||||
FileTexture(#[from] FileTextureError),
|
FileTexture(#[from] FileTextureError),
|
||||||
}
|
}
|
||||||
@ -170,7 +189,7 @@ impl AssetLoader for ImageLoader {
|
|||||||
|
|
||||||
/// An error that occurs when loading a texture from a file.
|
/// An error that occurs when loading a texture from a file.
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
#[error("Error reading image file {path}: {error}, this is an error in `bevy_render`.")]
|
#[error("Error reading image file {path}: {error}.")]
|
||||||
pub struct FileTextureError {
|
pub struct FileTextureError {
|
||||||
error: TextureError,
|
error: TextureError,
|
||||||
path: String,
|
path: String,
|
||||||
|
|||||||
@ -394,7 +394,7 @@ mod tests {
|
|||||||
trigger: Trigger<FocusedInput<KeyboardInput>>,
|
trigger: Trigger<FocusedInput<KeyboardInput>>,
|
||||||
mut query: Query<&mut GatherKeyboardEvents>,
|
mut query: Query<&mut GatherKeyboardEvents>,
|
||||||
) {
|
) {
|
||||||
if let Ok(mut gather) = query.get_mut(trigger.target()) {
|
if let Ok(mut gather) = query.get_mut(trigger.target().unwrap()) {
|
||||||
if let Key::Character(c) = &trigger.input.logical_key {
|
if let Key::Character(c) = &trigger.input.logical_key {
|
||||||
gather.0.push_str(c.as_str());
|
gather.0.push_str(c.as_str());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -551,7 +551,7 @@ pub(crate) fn add_light_view_entities(
|
|||||||
trigger: Trigger<OnAdd, (ExtractedDirectionalLight, ExtractedPointLight)>,
|
trigger: Trigger<OnAdd, (ExtractedDirectionalLight, ExtractedPointLight)>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
) {
|
) {
|
||||||
if let Ok(mut v) = commands.get_entity(trigger.target()) {
|
if let Ok(mut v) = commands.get_entity(trigger.target().unwrap()) {
|
||||||
v.insert(LightViewEntities::default());
|
v.insert(LightViewEntities::default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -561,7 +561,7 @@ pub(crate) fn extracted_light_removed(
|
|||||||
trigger: Trigger<OnRemove, (ExtractedDirectionalLight, ExtractedPointLight)>,
|
trigger: Trigger<OnRemove, (ExtractedDirectionalLight, ExtractedPointLight)>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
) {
|
) {
|
||||||
if let Ok(mut v) = commands.get_entity(trigger.target()) {
|
if let Ok(mut v) = commands.get_entity(trigger.target().unwrap()) {
|
||||||
v.try_remove::<LightViewEntities>();
|
v.try_remove::<LightViewEntities>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -571,7 +571,7 @@ pub(crate) fn remove_light_view_entities(
|
|||||||
query: Query<&LightViewEntities>,
|
query: Query<&LightViewEntities>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
) {
|
) {
|
||||||
if let Ok(entities) = query.get(trigger.target()) {
|
if let Ok(entities) = query.get(trigger.target().unwrap()) {
|
||||||
for v in entities.0.values() {
|
for v in entities.0.values() {
|
||||||
for e in v.iter().copied() {
|
for e in v.iter().copied() {
|
||||||
if let Ok(mut v) = commands.get_entity(e) {
|
if let Ok(mut v) = commands.get_entity(e) {
|
||||||
|
|||||||
@ -208,18 +208,6 @@ pub fn update_interactions(
|
|||||||
mut pointers: Query<(&PointerId, &PointerPress, &mut PointerInteraction)>,
|
mut pointers: Query<(&PointerId, &PointerPress, &mut PointerInteraction)>,
|
||||||
mut interact: Query<&mut PickingInteraction>,
|
mut interact: Query<&mut PickingInteraction>,
|
||||||
) {
|
) {
|
||||||
// Clear all previous hover data from pointers and entities
|
|
||||||
for (pointer, _, mut pointer_interaction) in &mut pointers {
|
|
||||||
pointer_interaction.sorted_entities.clear();
|
|
||||||
if let Some(previously_hovered_entities) = previous_hover_map.get(pointer) {
|
|
||||||
for entity in previously_hovered_entities.keys() {
|
|
||||||
if let Ok(mut interaction) = interact.get_mut(*entity) {
|
|
||||||
*interaction = PickingInteraction::None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a map to hold the aggregated interaction for each entity. This is needed because we
|
// Create a map to hold the aggregated interaction for each entity. This is needed because we
|
||||||
// need to be able to insert the interaction component on entities if they do not exist. To do
|
// need to be able to insert the interaction component on entities if they do not exist. To do
|
||||||
// so we need to know the final aggregated interaction state to avoid the scenario where we set
|
// so we need to know the final aggregated interaction state to avoid the scenario where we set
|
||||||
@ -239,13 +227,29 @@ pub fn update_interactions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Take the aggregated entity states and update or insert the component if missing.
|
// Take the aggregated entity states and update or insert the component if missing.
|
||||||
for (hovered_entity, new_interaction) in new_interaction_state.drain() {
|
for (&hovered_entity, &new_interaction) in new_interaction_state.iter() {
|
||||||
if let Ok(mut interaction) = interact.get_mut(hovered_entity) {
|
if let Ok(mut interaction) = interact.get_mut(hovered_entity) {
|
||||||
*interaction = new_interaction;
|
interaction.set_if_neq(new_interaction);
|
||||||
} else if let Ok(mut entity_commands) = commands.get_entity(hovered_entity) {
|
} else if let Ok(mut entity_commands) = commands.get_entity(hovered_entity) {
|
||||||
entity_commands.try_insert(new_interaction);
|
entity_commands.try_insert(new_interaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear all previous hover data from pointers that are no longer hovering any entities.
|
||||||
|
// We do this last to preserve change detection for picking interactions.
|
||||||
|
for (pointer, _, _) in &mut pointers {
|
||||||
|
let Some(previously_hovered_entities) = previous_hover_map.get(pointer) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
for entity in previously_hovered_entities.keys() {
|
||||||
|
if !new_interaction_state.contains_key(entity) {
|
||||||
|
if let Ok(mut interaction) = interact.get_mut(*entity) {
|
||||||
|
interaction.set_if_neq(PickingInteraction::None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Merge the interaction state of this entity into the aggregated map.
|
/// Merge the interaction state of this entity into the aggregated map.
|
||||||
|
|||||||
@ -55,13 +55,13 @@
|
|||||||
//! // Spawn your entity here, e.g. a Mesh.
|
//! // Spawn your entity here, e.g. a Mesh.
|
||||||
//! // When dragged, mutate the `Transform` component on the dragged target entity:
|
//! // When dragged, mutate the `Transform` component on the dragged target entity:
|
||||||
//! .observe(|trigger: Trigger<Pointer<Drag>>, mut transforms: Query<&mut Transform>| {
|
//! .observe(|trigger: Trigger<Pointer<Drag>>, mut transforms: Query<&mut Transform>| {
|
||||||
//! let mut transform = transforms.get_mut(trigger.target()).unwrap();
|
//! let mut transform = transforms.get_mut(trigger.target().unwrap()).unwrap();
|
||||||
//! let drag = trigger.event();
|
//! let drag = trigger.event();
|
||||||
//! transform.rotate_local_y(drag.delta.x / 50.0);
|
//! transform.rotate_local_y(drag.delta.x / 50.0);
|
||||||
//! })
|
//! })
|
||||||
//! .observe(|trigger: Trigger<Pointer<Click>>, mut commands: Commands| {
|
//! .observe(|trigger: Trigger<Pointer<Click>>, mut commands: Commands| {
|
||||||
//! println!("Entity {} goes BOOM!", trigger.target());
|
//! println!("Entity {} goes BOOM!", trigger.target().unwrap());
|
||||||
//! commands.entity(trigger.target()).despawn();
|
//! commands.entity(trigger.target().unwrap()).despawn();
|
||||||
//! })
|
//! })
|
||||||
//! .observe(|trigger: Trigger<Pointer<Over>>, mut events: EventWriter<Greeting>| {
|
//! .observe(|trigger: Trigger<Pointer<Over>>, mut events: EventWriter<Greeting>| {
|
||||||
//! events.write(Greeting);
|
//! events.write(Greeting);
|
||||||
|
|||||||
@ -119,7 +119,7 @@ wesl = { version = "0.1.2", optional = true }
|
|||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
# Omit the `glsl` feature in non-WebAssembly by default.
|
# Omit the `glsl` feature in non-WebAssembly by default.
|
||||||
naga_oil = { version = "0.17", default-features = false, features = [
|
naga_oil = { version = "0.17.1", default-features = false, features = [
|
||||||
"test_shader",
|
"test_shader",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
@ -127,7 +127,7 @@ naga_oil = { version = "0.17", default-features = false, features = [
|
|||||||
proptest = "1"
|
proptest = "1"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
naga_oil = "0.17"
|
naga_oil = "0.17.1"
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
web-sys = { version = "0.3.67", features = [
|
web-sys = { version = "0.3.67", features = [
|
||||||
'Blob',
|
'Blob',
|
||||||
|
|||||||
@ -1060,6 +1060,7 @@ pub fn camera_system(
|
|||||||
#[reflect(opaque)]
|
#[reflect(opaque)]
|
||||||
#[reflect(Component, Default, Clone)]
|
#[reflect(Component, Default, Clone)]
|
||||||
pub struct CameraMainTextureUsages(pub TextureUsages);
|
pub struct CameraMainTextureUsages(pub TextureUsages);
|
||||||
|
|
||||||
impl Default for CameraMainTextureUsages {
|
impl Default for CameraMainTextureUsages {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self(
|
Self(
|
||||||
@ -1070,6 +1071,13 @@ impl Default for CameraMainTextureUsages {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CameraMainTextureUsages {
|
||||||
|
pub fn with(mut self, usages: TextureUsages) -> Self {
|
||||||
|
self.0 |= usages;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug)]
|
||||||
pub struct ExtractedCamera {
|
pub struct ExtractedCamera {
|
||||||
pub target: Option<NormalizedRenderTarget>,
|
pub target: Option<NormalizedRenderTarget>,
|
||||||
|
|||||||
@ -147,6 +147,13 @@ impl<'a> IntoBinding<'a> for &'a TextureView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoBinding<'a> for &'a wgpu::TextureView {
|
||||||
|
#[inline]
|
||||||
|
fn into_binding(self) -> BindingResource<'a> {
|
||||||
|
BindingResource::TextureView(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> IntoBinding<'a> for &'a [&'a wgpu::TextureView] {
|
impl<'a> IntoBinding<'a> for &'a [&'a wgpu::TextureView] {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_binding(self) -> BindingResource<'a> {
|
fn into_binding(self) -> BindingResource<'a> {
|
||||||
@ -161,6 +168,13 @@ impl<'a> IntoBinding<'a> for &'a Sampler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoBinding<'a> for &'a [&'a wgpu::Sampler] {
|
||||||
|
#[inline]
|
||||||
|
fn into_binding(self) -> BindingResource<'a> {
|
||||||
|
BindingResource::SamplerArray(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> IntoBinding<'a> for BindingResource<'a> {
|
impl<'a> IntoBinding<'a> for BindingResource<'a> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_binding(self) -> BindingResource<'a> {
|
fn into_binding(self) -> BindingResource<'a> {
|
||||||
@ -175,6 +189,13 @@ impl<'a> IntoBinding<'a> for wgpu::BufferBinding<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoBinding<'a> for &'a [wgpu::BufferBinding<'a>] {
|
||||||
|
#[inline]
|
||||||
|
fn into_binding(self) -> BindingResource<'a> {
|
||||||
|
BindingResource::BufferArray(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait IntoBindingArray<'b, const N: usize> {
|
pub trait IntoBindingArray<'b, const N: usize> {
|
||||||
fn into_array(self) -> [BindingResource<'b>; N];
|
fn into_array(self) -> [BindingResource<'b>; N];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -568,4 +568,8 @@ pub mod binding_types {
|
|||||||
}
|
}
|
||||||
.into_bind_group_layout_entry_builder()
|
.into_bind_group_layout_entry_builder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn acceleration_structure() -> BindGroupLayoutEntryBuilder {
|
||||||
|
BindingType::AccelerationStructure.into_bind_group_layout_entry_builder()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,18 +38,21 @@ pub use wgpu::{
|
|||||||
BufferInitDescriptor, DispatchIndirectArgs, DrawIndexedIndirectArgs, DrawIndirectArgs,
|
BufferInitDescriptor, DispatchIndirectArgs, DrawIndexedIndirectArgs, DrawIndirectArgs,
|
||||||
TextureDataOrder,
|
TextureDataOrder,
|
||||||
},
|
},
|
||||||
AdapterInfo as WgpuAdapterInfo, AddressMode, AstcBlock, AstcChannel, BindGroupDescriptor,
|
AccelerationStructureFlags, AccelerationStructureGeometryFlags,
|
||||||
BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType,
|
AccelerationStructureUpdateMode, AdapterInfo as WgpuAdapterInfo, AddressMode, AstcBlock,
|
||||||
|
AstcChannel, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor,
|
||||||
|
BindGroupLayoutEntry, BindingResource, BindingType, Blas, BlasBuildEntry, BlasGeometries,
|
||||||
|
BlasGeometrySizeDescriptors, BlasTriangleGeometry, BlasTriangleGeometrySizeDescriptor,
|
||||||
BlendComponent, BlendFactor, BlendOperation, BlendState, BufferAddress, BufferAsyncError,
|
BlendComponent, BlendFactor, BlendOperation, BlendState, BufferAddress, BufferAsyncError,
|
||||||
BufferBinding, BufferBindingType, BufferDescriptor, BufferSize, BufferUsages, ColorTargetState,
|
BufferBinding, BufferBindingType, BufferDescriptor, BufferSize, BufferUsages, ColorTargetState,
|
||||||
ColorWrites, CommandEncoder, CommandEncoderDescriptor, CompareFunction, ComputePass,
|
ColorWrites, CommandEncoder, CommandEncoderDescriptor, CompareFunction, ComputePass,
|
||||||
ComputePassDescriptor, ComputePipelineDescriptor as RawComputePipelineDescriptor,
|
ComputePassDescriptor, ComputePipelineDescriptor as RawComputePipelineDescriptor,
|
||||||
DepthBiasState, DepthStencilState, DownlevelFlags, Extent3d, Face, Features as WgpuFeatures,
|
CreateBlasDescriptor, CreateTlasDescriptor, DepthBiasState, DepthStencilState, DownlevelFlags,
|
||||||
FilterMode, FragmentState as RawFragmentState, FrontFace, ImageSubresourceRange, IndexFormat,
|
Extent3d, Face, Features as WgpuFeatures, FilterMode, FragmentState as RawFragmentState,
|
||||||
Limits as WgpuLimits, LoadOp, Maintain, MapMode, MultisampleState, Operations, Origin3d,
|
FrontFace, ImageSubresourceRange, IndexFormat, Limits as WgpuLimits, LoadOp, Maintain, MapMode,
|
||||||
PipelineCompilationOptions, PipelineLayout, PipelineLayoutDescriptor, PolygonMode,
|
MultisampleState, Operations, Origin3d, PipelineCompilationOptions, PipelineLayout,
|
||||||
PrimitiveState, PrimitiveTopology, PushConstantRange, RenderPassColorAttachment,
|
PipelineLayoutDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology, PushConstantRange,
|
||||||
RenderPassDepthStencilAttachment, RenderPassDescriptor,
|
RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor,
|
||||||
RenderPipelineDescriptor as RawRenderPipelineDescriptor, Sampler as WgpuSampler,
|
RenderPipelineDescriptor as RawRenderPipelineDescriptor, Sampler as WgpuSampler,
|
||||||
SamplerBindingType, SamplerBindingType as WgpuSamplerBindingType, SamplerDescriptor,
|
SamplerBindingType, SamplerBindingType as WgpuSamplerBindingType, SamplerDescriptor,
|
||||||
ShaderModule, ShaderModuleDescriptor, ShaderSource, ShaderStages, StencilFaceState,
|
ShaderModule, ShaderModuleDescriptor, ShaderSource, ShaderStages, StencilFaceState,
|
||||||
@ -57,8 +60,9 @@ pub use wgpu::{
|
|||||||
TexelCopyBufferLayout, TexelCopyTextureInfo, TextureAspect, TextureDescriptor,
|
TexelCopyBufferLayout, TexelCopyTextureInfo, TextureAspect, TextureDescriptor,
|
||||||
TextureDimension, TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures,
|
TextureDimension, TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures,
|
||||||
TextureSampleType, TextureUsages, TextureView as WgpuTextureView, TextureViewDescriptor,
|
TextureSampleType, TextureUsages, TextureView as WgpuTextureView, TextureViewDescriptor,
|
||||||
TextureViewDimension, VertexAttribute, VertexBufferLayout as RawVertexBufferLayout,
|
TextureViewDimension, Tlas, TlasInstance, TlasPackage, VertexAttribute,
|
||||||
VertexFormat, VertexState as RawVertexState, VertexStepMode, COPY_BUFFER_ALIGNMENT,
|
VertexBufferLayout as RawVertexBufferLayout, VertexFormat, VertexState as RawVertexState,
|
||||||
|
VertexStepMode, COPY_BUFFER_ALIGNMENT,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::mesh::VertexBufferLayout;
|
pub use crate::mesh::VertexBufferLayout;
|
||||||
|
|||||||
@ -94,14 +94,14 @@ impl Plugin for SyncWorldPlugin {
|
|||||||
app.init_resource::<PendingSyncEntity>();
|
app.init_resource::<PendingSyncEntity>();
|
||||||
app.add_observer(
|
app.add_observer(
|
||||||
|trigger: Trigger<OnAdd, SyncToRenderWorld>, mut pending: ResMut<PendingSyncEntity>| {
|
|trigger: Trigger<OnAdd, SyncToRenderWorld>, mut pending: ResMut<PendingSyncEntity>| {
|
||||||
pending.push(EntityRecord::Added(trigger.target()));
|
pending.push(EntityRecord::Added(trigger.target().unwrap()));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
app.add_observer(
|
app.add_observer(
|
||||||
|trigger: Trigger<OnRemove, SyncToRenderWorld>,
|
|trigger: Trigger<OnRemove, SyncToRenderWorld>,
|
||||||
mut pending: ResMut<PendingSyncEntity>,
|
mut pending: ResMut<PendingSyncEntity>,
|
||||||
query: Query<&RenderEntity>| {
|
query: Query<&RenderEntity>| {
|
||||||
if let Ok(e) = query.get(trigger.target()) {
|
if let Ok(e) = query.get(trigger.target().unwrap()) {
|
||||||
pending.push(EntityRecord::Removed(*e));
|
pending.push(EntityRecord::Removed(*e));
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -512,14 +512,14 @@ mod tests {
|
|||||||
|
|
||||||
main_world.add_observer(
|
main_world.add_observer(
|
||||||
|trigger: Trigger<OnAdd, SyncToRenderWorld>, mut pending: ResMut<PendingSyncEntity>| {
|
|trigger: Trigger<OnAdd, SyncToRenderWorld>, mut pending: ResMut<PendingSyncEntity>| {
|
||||||
pending.push(EntityRecord::Added(trigger.target()));
|
pending.push(EntityRecord::Added(trigger.target().unwrap()));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
main_world.add_observer(
|
main_world.add_observer(
|
||||||
|trigger: Trigger<OnRemove, SyncToRenderWorld>,
|
|trigger: Trigger<OnRemove, SyncToRenderWorld>,
|
||||||
mut pending: ResMut<PendingSyncEntity>,
|
mut pending: ResMut<PendingSyncEntity>,
|
||||||
query: Query<&RenderEntity>| {
|
query: Query<&RenderEntity>| {
|
||||||
if let Ok(e) = query.get(trigger.target()) {
|
if let Ok(e) = query.get(trigger.target().unwrap()) {
|
||||||
pending.push(EntityRecord::Removed(*e));
|
pending.push(EntityRecord::Removed(*e));
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -721,7 +721,7 @@ mod tests {
|
|||||||
.expect("Failed to run dynamic scene builder system.")
|
.expect("Failed to run dynamic scene builder system.")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn observe_trigger(app: &mut App, scene_id: InstanceId, scene_entity: Entity) {
|
fn observe_trigger(app: &mut App, scene_id: InstanceId, scene_entity: Option<Entity>) {
|
||||||
// Add observer
|
// Add observer
|
||||||
app.world_mut().add_observer(
|
app.world_mut().add_observer(
|
||||||
move |trigger: Trigger<SceneInstanceReady>,
|
move |trigger: Trigger<SceneInstanceReady>,
|
||||||
@ -773,7 +773,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Check trigger.
|
// Check trigger.
|
||||||
observe_trigger(&mut app, scene_id, Entity::PLACEHOLDER);
|
observe_trigger(&mut app, scene_id, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -792,7 +792,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Check trigger.
|
// Check trigger.
|
||||||
observe_trigger(&mut app, scene_id, Entity::PLACEHOLDER);
|
observe_trigger(&mut app, scene_id, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -816,7 +816,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Check trigger.
|
// Check trigger.
|
||||||
observe_trigger(&mut app, scene_id, scene_entity);
|
observe_trigger(&mut app, scene_id, Some(scene_entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -840,7 +840,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Check trigger.
|
// Check trigger.
|
||||||
observe_trigger(&mut app, scene_id, scene_entity);
|
observe_trigger(&mut app, scene_id, Some(scene_entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
experimental::UiChildren,
|
experimental::UiChildren,
|
||||||
prelude::{Button, Label},
|
prelude::{Button, Label},
|
||||||
|
ui_transform::UiGlobalTransform,
|
||||||
widget::{ImageNode, TextUiReader},
|
widget::{ImageNode, TextUiReader},
|
||||||
ComputedNode,
|
ComputedNode,
|
||||||
};
|
};
|
||||||
@ -13,11 +14,9 @@ use bevy_ecs::{
|
|||||||
system::{Commands, Query},
|
system::{Commands, Query},
|
||||||
world::Ref,
|
world::Ref,
|
||||||
};
|
};
|
||||||
use bevy_math::Vec3Swizzles;
|
|
||||||
use bevy_render::camera::CameraUpdateSystems;
|
|
||||||
use bevy_transform::prelude::GlobalTransform;
|
|
||||||
|
|
||||||
use accesskit::{Node, Rect, Role};
|
use accesskit::{Node, Rect, Role};
|
||||||
|
use bevy_render::camera::CameraUpdateSystems;
|
||||||
|
|
||||||
fn calc_label(
|
fn calc_label(
|
||||||
text_reader: &mut TextUiReader,
|
text_reader: &mut TextUiReader,
|
||||||
@ -40,12 +39,12 @@ fn calc_bounds(
|
|||||||
mut nodes: Query<(
|
mut nodes: Query<(
|
||||||
&mut AccessibilityNode,
|
&mut AccessibilityNode,
|
||||||
Ref<ComputedNode>,
|
Ref<ComputedNode>,
|
||||||
Ref<GlobalTransform>,
|
Ref<UiGlobalTransform>,
|
||||||
)>,
|
)>,
|
||||||
) {
|
) {
|
||||||
for (mut accessible, node, transform) in &mut nodes {
|
for (mut accessible, node, transform) in &mut nodes {
|
||||||
if node.is_changed() || transform.is_changed() {
|
if node.is_changed() || transform.is_changed() {
|
||||||
let center = transform.translation().xy();
|
let center = transform.translation;
|
||||||
let half_size = 0.5 * node.size;
|
let half_size = 0.5 * node.size;
|
||||||
let min = center - half_size;
|
let min = center - half_size;
|
||||||
let max = center + half_size;
|
let max = center + half_size;
|
||||||
|
|||||||
@ -1,18 +1,21 @@
|
|||||||
use crate::{CalculatedClip, ComputedNode, ComputedNodeTarget, ResolvedBorderRadius, UiStack};
|
use crate::{
|
||||||
|
picking_backend::clip_check_recursive, ui_transform::UiGlobalTransform, ComputedNode,
|
||||||
|
ComputedNodeTarget, Node, UiStack,
|
||||||
|
};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
change_detection::DetectChangesMut,
|
change_detection::DetectChangesMut,
|
||||||
entity::{ContainsEntity, Entity},
|
entity::{ContainsEntity, Entity},
|
||||||
|
hierarchy::ChildOf,
|
||||||
prelude::{Component, With},
|
prelude::{Component, With},
|
||||||
query::QueryData,
|
query::QueryData,
|
||||||
reflect::ReflectComponent,
|
reflect::ReflectComponent,
|
||||||
system::{Local, Query, Res},
|
system::{Local, Query, Res},
|
||||||
};
|
};
|
||||||
use bevy_input::{mouse::MouseButton, touch::Touches, ButtonInput};
|
use bevy_input::{mouse::MouseButton, touch::Touches, ButtonInput};
|
||||||
use bevy_math::{Rect, Vec2};
|
use bevy_math::Vec2;
|
||||||
use bevy_platform::collections::HashMap;
|
use bevy_platform::collections::HashMap;
|
||||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||||
use bevy_render::{camera::NormalizedRenderTarget, prelude::Camera, view::InheritedVisibility};
|
use bevy_render::{camera::NormalizedRenderTarget, prelude::Camera, view::InheritedVisibility};
|
||||||
use bevy_transform::components::GlobalTransform;
|
|
||||||
use bevy_window::{PrimaryWindow, Window};
|
use bevy_window::{PrimaryWindow, Window};
|
||||||
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
@ -67,12 +70,12 @@ impl Default for Interaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A component storing the position of the mouse relative to the node, (0., 0.) being the top-left corner and (1., 1.) being the bottom-right
|
/// A component storing the position of the mouse relative to the node, (0., 0.) being the center and (0.5, 0.5) being the bottom-right
|
||||||
/// If the mouse is not over the node, the value will go beyond the range of (0., 0.) to (1., 1.)
|
/// If the mouse is not over the node, the value will go beyond the range of (-0.5, -0.5) to (0.5, 0.5)
|
||||||
///
|
///
|
||||||
/// It can be used alongside [`Interaction`] to get the position of the press.
|
/// It can be used alongside [`Interaction`] to get the position of the press.
|
||||||
///
|
///
|
||||||
/// The component is updated when it is in the same entity with [`Node`](crate::Node).
|
/// The component is updated when it is in the same entity with [`Node`].
|
||||||
#[derive(Component, Copy, Clone, Default, PartialEq, Debug, Reflect)]
|
#[derive(Component, Copy, Clone, Default, PartialEq, Debug, Reflect)]
|
||||||
#[reflect(Component, Default, PartialEq, Debug, Clone)]
|
#[reflect(Component, Default, PartialEq, Debug, Clone)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@ -81,8 +84,8 @@ impl Default for Interaction {
|
|||||||
reflect(Serialize, Deserialize)
|
reflect(Serialize, Deserialize)
|
||||||
)]
|
)]
|
||||||
pub struct RelativeCursorPosition {
|
pub struct RelativeCursorPosition {
|
||||||
/// Visible area of the Node relative to the size of the entire Node.
|
/// True if the cursor position is over an unclipped area of the Node.
|
||||||
pub normalized_visible_node_rect: Rect,
|
pub cursor_over: bool,
|
||||||
/// Cursor position relative to the size and position of the Node.
|
/// Cursor position relative to the size and position of the Node.
|
||||||
/// A None value indicates that the cursor position is unknown.
|
/// A None value indicates that the cursor position is unknown.
|
||||||
pub normalized: Option<Vec2>,
|
pub normalized: Option<Vec2>,
|
||||||
@ -90,9 +93,8 @@ pub struct RelativeCursorPosition {
|
|||||||
|
|
||||||
impl RelativeCursorPosition {
|
impl RelativeCursorPosition {
|
||||||
/// A helper function to check if the mouse is over the node
|
/// A helper function to check if the mouse is over the node
|
||||||
pub fn mouse_over(&self) -> bool {
|
pub fn cursor_over(&self) -> bool {
|
||||||
self.normalized
|
self.cursor_over
|
||||||
.is_some_and(|position| self.normalized_visible_node_rect.contains(position))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,11 +135,10 @@ pub struct State {
|
|||||||
pub struct NodeQuery {
|
pub struct NodeQuery {
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
node: &'static ComputedNode,
|
node: &'static ComputedNode,
|
||||||
global_transform: &'static GlobalTransform,
|
transform: &'static UiGlobalTransform,
|
||||||
interaction: Option<&'static mut Interaction>,
|
interaction: Option<&'static mut Interaction>,
|
||||||
relative_cursor_position: Option<&'static mut RelativeCursorPosition>,
|
relative_cursor_position: Option<&'static mut RelativeCursorPosition>,
|
||||||
focus_policy: Option<&'static FocusPolicy>,
|
focus_policy: Option<&'static FocusPolicy>,
|
||||||
calculated_clip: Option<&'static CalculatedClip>,
|
|
||||||
inherited_visibility: Option<&'static InheritedVisibility>,
|
inherited_visibility: Option<&'static InheritedVisibility>,
|
||||||
target_camera: &'static ComputedNodeTarget,
|
target_camera: &'static ComputedNodeTarget,
|
||||||
}
|
}
|
||||||
@ -154,6 +155,8 @@ pub fn ui_focus_system(
|
|||||||
touches_input: Res<Touches>,
|
touches_input: Res<Touches>,
|
||||||
ui_stack: Res<UiStack>,
|
ui_stack: Res<UiStack>,
|
||||||
mut node_query: Query<NodeQuery>,
|
mut node_query: Query<NodeQuery>,
|
||||||
|
clipping_query: Query<(&ComputedNode, &UiGlobalTransform, &Node)>,
|
||||||
|
child_of_query: Query<&ChildOf>,
|
||||||
) {
|
) {
|
||||||
let primary_window = primary_window.iter().next();
|
let primary_window = primary_window.iter().next();
|
||||||
|
|
||||||
@ -234,46 +237,30 @@ pub fn ui_focus_system(
|
|||||||
}
|
}
|
||||||
let camera_entity = node.target_camera.camera()?;
|
let camera_entity = node.target_camera.camera()?;
|
||||||
|
|
||||||
let node_rect = Rect::from_center_size(
|
|
||||||
node.global_transform.translation().truncate(),
|
|
||||||
node.node.size(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Intersect with the calculated clip rect to find the bounds of the visible region of the node
|
|
||||||
let visible_rect = node
|
|
||||||
.calculated_clip
|
|
||||||
.map(|clip| node_rect.intersect(clip.clip))
|
|
||||||
.unwrap_or(node_rect);
|
|
||||||
|
|
||||||
let cursor_position = camera_cursor_positions.get(&camera_entity);
|
let cursor_position = camera_cursor_positions.get(&camera_entity);
|
||||||
|
|
||||||
|
let contains_cursor = cursor_position.is_some_and(|point| {
|
||||||
|
node.node.contains_point(*node.transform, *point)
|
||||||
|
&& clip_check_recursive(*point, *entity, &clipping_query, &child_of_query)
|
||||||
|
});
|
||||||
|
|
||||||
// The mouse position relative to the node
|
// The mouse position relative to the node
|
||||||
// (0., 0.) is the top-left corner, (1., 1.) is the bottom-right corner
|
// (-0.5, -0.5) is the top-left corner, (0.5, 0.5) is the bottom-right corner
|
||||||
// Coordinates are relative to the entire node, not just the visible region.
|
// Coordinates are relative to the entire node, not just the visible region.
|
||||||
let relative_cursor_position = cursor_position.and_then(|cursor_position| {
|
let normalized_cursor_position = cursor_position.and_then(|cursor_position| {
|
||||||
// ensure node size is non-zero in all dimensions, otherwise relative position will be
|
// ensure node size is non-zero in all dimensions, otherwise relative position will be
|
||||||
// +/-inf. if the node is hidden, the visible rect min/max will also be -inf leading to
|
// +/-inf. if the node is hidden, the visible rect min/max will also be -inf leading to
|
||||||
// false positives for mouse_over (#12395)
|
// false positives for mouse_over (#12395)
|
||||||
(node_rect.size().cmpgt(Vec2::ZERO).all())
|
node.node.normalize_point(*node.transform, *cursor_position)
|
||||||
.then_some((*cursor_position - node_rect.min) / node_rect.size())
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// If the current cursor position is within the bounds of the node's visible area, consider it for
|
// If the current cursor position is within the bounds of the node's visible area, consider it for
|
||||||
// clicking
|
// clicking
|
||||||
let relative_cursor_position_component = RelativeCursorPosition {
|
let relative_cursor_position_component = RelativeCursorPosition {
|
||||||
normalized_visible_node_rect: visible_rect.normalize(node_rect),
|
cursor_over: contains_cursor,
|
||||||
normalized: relative_cursor_position,
|
normalized: normalized_cursor_position,
|
||||||
};
|
};
|
||||||
|
|
||||||
let contains_cursor = relative_cursor_position_component.mouse_over()
|
|
||||||
&& cursor_position.is_some_and(|point| {
|
|
||||||
pick_rounded_rect(
|
|
||||||
*point - node_rect.center(),
|
|
||||||
node_rect.size(),
|
|
||||||
node.node.border_radius,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save the relative cursor position to the correct component
|
// Save the relative cursor position to the correct component
|
||||||
if let Some(mut node_relative_cursor_position_component) = node.relative_cursor_position
|
if let Some(mut node_relative_cursor_position_component) = node.relative_cursor_position
|
||||||
{
|
{
|
||||||
@ -284,7 +271,8 @@ pub fn ui_focus_system(
|
|||||||
Some(*entity)
|
Some(*entity)
|
||||||
} else {
|
} else {
|
||||||
if let Some(mut interaction) = node.interaction {
|
if let Some(mut interaction) = node.interaction {
|
||||||
if *interaction == Interaction::Hovered || (relative_cursor_position.is_none())
|
if *interaction == Interaction::Hovered
|
||||||
|
|| (normalized_cursor_position.is_none())
|
||||||
{
|
{
|
||||||
interaction.set_if_neq(Interaction::None);
|
interaction.set_if_neq(Interaction::None);
|
||||||
}
|
}
|
||||||
@ -334,26 +322,3 @@ pub fn ui_focus_system(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if `point` (relative to the rectangle's center) is within the bounds of a rounded rectangle with
|
|
||||||
// the given size and border radius.
|
|
||||||
//
|
|
||||||
// Matches the sdf function in `ui.wgsl` that is used by the UI renderer to draw rounded rectangles.
|
|
||||||
pub(crate) fn pick_rounded_rect(
|
|
||||||
point: Vec2,
|
|
||||||
size: Vec2,
|
|
||||||
border_radius: ResolvedBorderRadius,
|
|
||||||
) -> bool {
|
|
||||||
let [top, bottom] = if point.x < 0. {
|
|
||||||
[border_radius.top_left, border_radius.bottom_left]
|
|
||||||
} else {
|
|
||||||
[border_radius.top_right, border_radius.bottom_right]
|
|
||||||
};
|
|
||||||
let r = if point.y < 0. { top } else { bottom };
|
|
||||||
|
|
||||||
let corner_to_point = point.abs() - 0.5 * size;
|
|
||||||
let q = corner_to_point + r;
|
|
||||||
let l = q.max(Vec2::ZERO).length();
|
|
||||||
let m = q.max_element().min(0.);
|
|
||||||
l + m - r < 0.
|
|
||||||
}
|
|
||||||
|
|||||||
@ -448,6 +448,8 @@ impl RepeatedGridTrack {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use bevy_math::Vec2;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -523,7 +525,7 @@ mod tests {
|
|||||||
grid_column: GridPlacement::start(4),
|
grid_column: GridPlacement::start(4),
|
||||||
grid_row: GridPlacement::span(3),
|
grid_row: GridPlacement::span(3),
|
||||||
};
|
};
|
||||||
let viewport_values = LayoutContext::new(1.0, bevy_math::Vec2::new(800., 600.));
|
let viewport_values = LayoutContext::new(1.0, Vec2::new(800., 600.));
|
||||||
let taffy_style = from_node(&node, &viewport_values, false);
|
let taffy_style = from_node(&node, &viewport_values, false);
|
||||||
assert_eq!(taffy_style.display, taffy::style::Display::Flex);
|
assert_eq!(taffy_style.display, taffy::style::Display::Flex);
|
||||||
assert_eq!(taffy_style.box_sizing, taffy::style::BoxSizing::ContentBox);
|
assert_eq!(taffy_style.box_sizing, taffy::style::BoxSizing::ContentBox);
|
||||||
@ -661,7 +663,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_into_length_percentage() {
|
fn test_into_length_percentage() {
|
||||||
use taffy::style::LengthPercentage;
|
use taffy::style::LengthPercentage;
|
||||||
let context = LayoutContext::new(2.0, bevy_math::Vec2::new(800., 600.));
|
let context = LayoutContext::new(2.0, Vec2::new(800., 600.));
|
||||||
let cases = [
|
let cases = [
|
||||||
(Val::Auto, LengthPercentage::Length(0.)),
|
(Val::Auto, LengthPercentage::Length(0.)),
|
||||||
(Val::Percent(1.), LengthPercentage::Percent(0.01)),
|
(Val::Percent(1.), LengthPercentage::Percent(0.01)),
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
experimental::{UiChildren, UiRootNodes},
|
experimental::{UiChildren, UiRootNodes},
|
||||||
|
ui_transform::{UiGlobalTransform, UiTransform},
|
||||||
BorderRadius, ComputedNode, ComputedNodeTarget, ContentSize, Display, LayoutConfig, Node,
|
BorderRadius, ComputedNode, ComputedNodeTarget, ContentSize, Display, LayoutConfig, Node,
|
||||||
Outline, OverflowAxis, ScrollPosition,
|
Outline, OverflowAxis, ScrollPosition,
|
||||||
};
|
};
|
||||||
@ -12,9 +13,9 @@ use bevy_ecs::{
|
|||||||
system::{Commands, Query, ResMut},
|
system::{Commands, Query, ResMut},
|
||||||
world::Ref,
|
world::Ref,
|
||||||
};
|
};
|
||||||
use bevy_math::Vec2;
|
|
||||||
|
use bevy_math::{Affine2, Vec2};
|
||||||
use bevy_sprite::BorderRect;
|
use bevy_sprite::BorderRect;
|
||||||
use bevy_transform::components::Transform;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use ui_surface::UiSurface;
|
use ui_surface::UiSurface;
|
||||||
@ -81,9 +82,10 @@ pub fn ui_layout_system(
|
|||||||
)>,
|
)>,
|
||||||
computed_node_query: Query<(Entity, Option<Ref<ChildOf>>), With<ComputedNode>>,
|
computed_node_query: Query<(Entity, Option<Ref<ChildOf>>), With<ComputedNode>>,
|
||||||
ui_children: UiChildren,
|
ui_children: UiChildren,
|
||||||
mut node_transform_query: Query<(
|
mut node_update_query: Query<(
|
||||||
&mut ComputedNode,
|
&mut ComputedNode,
|
||||||
&mut Transform,
|
&UiTransform,
|
||||||
|
&mut UiGlobalTransform,
|
||||||
&Node,
|
&Node,
|
||||||
Option<&LayoutConfig>,
|
Option<&LayoutConfig>,
|
||||||
Option<&BorderRadius>,
|
Option<&BorderRadius>,
|
||||||
@ -175,7 +177,8 @@ with UI components as a child of an entity without UI components, your UI layout
|
|||||||
&mut ui_surface,
|
&mut ui_surface,
|
||||||
true,
|
true,
|
||||||
computed_target.physical_size().as_vec2(),
|
computed_target.physical_size().as_vec2(),
|
||||||
&mut node_transform_query,
|
Affine2::IDENTITY,
|
||||||
|
&mut node_update_query,
|
||||||
&ui_children,
|
&ui_children,
|
||||||
computed_target.scale_factor.recip(),
|
computed_target.scale_factor.recip(),
|
||||||
Vec2::ZERO,
|
Vec2::ZERO,
|
||||||
@ -190,9 +193,11 @@ with UI components as a child of an entity without UI components, your UI layout
|
|||||||
ui_surface: &mut UiSurface,
|
ui_surface: &mut UiSurface,
|
||||||
inherited_use_rounding: bool,
|
inherited_use_rounding: bool,
|
||||||
target_size: Vec2,
|
target_size: Vec2,
|
||||||
node_transform_query: &mut Query<(
|
mut inherited_transform: Affine2,
|
||||||
|
node_update_query: &mut Query<(
|
||||||
&mut ComputedNode,
|
&mut ComputedNode,
|
||||||
&mut Transform,
|
&UiTransform,
|
||||||
|
&mut UiGlobalTransform,
|
||||||
&Node,
|
&Node,
|
||||||
Option<&LayoutConfig>,
|
Option<&LayoutConfig>,
|
||||||
Option<&BorderRadius>,
|
Option<&BorderRadius>,
|
||||||
@ -206,13 +211,14 @@ with UI components as a child of an entity without UI components, your UI layout
|
|||||||
) {
|
) {
|
||||||
if let Ok((
|
if let Ok((
|
||||||
mut node,
|
mut node,
|
||||||
mut transform,
|
transform,
|
||||||
|
mut global_transform,
|
||||||
style,
|
style,
|
||||||
maybe_layout_config,
|
maybe_layout_config,
|
||||||
maybe_border_radius,
|
maybe_border_radius,
|
||||||
maybe_outline,
|
maybe_outline,
|
||||||
maybe_scroll_position,
|
maybe_scroll_position,
|
||||||
)) = node_transform_query.get_mut(entity)
|
)) = node_update_query.get_mut(entity)
|
||||||
{
|
{
|
||||||
let use_rounding = maybe_layout_config
|
let use_rounding = maybe_layout_config
|
||||||
.map(|layout_config| layout_config.use_rounding)
|
.map(|layout_config| layout_config.use_rounding)
|
||||||
@ -224,10 +230,11 @@ with UI components as a child of an entity without UI components, your UI layout
|
|||||||
|
|
||||||
let layout_size = Vec2::new(layout.size.width, layout.size.height);
|
let layout_size = Vec2::new(layout.size.width, layout.size.height);
|
||||||
|
|
||||||
|
// Taffy layout position of the top-left corner of the node, relative to its parent.
|
||||||
let layout_location = Vec2::new(layout.location.x, layout.location.y);
|
let layout_location = Vec2::new(layout.location.x, layout.location.y);
|
||||||
|
|
||||||
// The position of the center of the node, stored in the node's transform
|
// The position of the center of the node relative to its top-left corner.
|
||||||
let node_center =
|
let local_center =
|
||||||
layout_location - parent_scroll_position + 0.5 * (layout_size - parent_size);
|
layout_location - parent_scroll_position + 0.5 * (layout_size - parent_size);
|
||||||
|
|
||||||
// only trigger change detection when the new values are different
|
// only trigger change detection when the new values are different
|
||||||
@ -253,6 +260,16 @@ with UI components as a child of an entity without UI components, your UI layout
|
|||||||
node.bypass_change_detection().border = taffy_rect_to_border_rect(layout.border);
|
node.bypass_change_detection().border = taffy_rect_to_border_rect(layout.border);
|
||||||
node.bypass_change_detection().padding = taffy_rect_to_border_rect(layout.padding);
|
node.bypass_change_detection().padding = taffy_rect_to_border_rect(layout.padding);
|
||||||
|
|
||||||
|
// Computer the node's new global transform
|
||||||
|
let mut local_transform =
|
||||||
|
transform.compute_affine(inverse_target_scale_factor, layout_size, target_size);
|
||||||
|
local_transform.translation += local_center;
|
||||||
|
inherited_transform *= local_transform;
|
||||||
|
|
||||||
|
if inherited_transform != **global_transform {
|
||||||
|
*global_transform = inherited_transform.into();
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(border_radius) = maybe_border_radius {
|
if let Some(border_radius) = maybe_border_radius {
|
||||||
// We don't trigger change detection for changes to border radius
|
// We don't trigger change detection for changes to border radius
|
||||||
node.bypass_change_detection().border_radius = border_radius.resolve(
|
node.bypass_change_detection().border_radius = border_radius.resolve(
|
||||||
@ -290,10 +307,6 @@ with UI components as a child of an entity without UI components, your UI layout
|
|||||||
.max(0.);
|
.max(0.);
|
||||||
}
|
}
|
||||||
|
|
||||||
if transform.translation.truncate() != node_center {
|
|
||||||
transform.translation = node_center.extend(0.);
|
|
||||||
}
|
|
||||||
|
|
||||||
let scroll_position: Vec2 = maybe_scroll_position
|
let scroll_position: Vec2 = maybe_scroll_position
|
||||||
.map(|scroll_pos| {
|
.map(|scroll_pos| {
|
||||||
Vec2::new(
|
Vec2::new(
|
||||||
@ -333,7 +346,8 @@ with UI components as a child of an entity without UI components, your UI layout
|
|||||||
ui_surface,
|
ui_surface,
|
||||||
use_rounding,
|
use_rounding,
|
||||||
target_size,
|
target_size,
|
||||||
node_transform_query,
|
inherited_transform,
|
||||||
|
node_update_query,
|
||||||
ui_children,
|
ui_children,
|
||||||
inverse_target_scale_factor,
|
inverse_target_scale_factor,
|
||||||
layout_size,
|
layout_size,
|
||||||
@ -356,10 +370,7 @@ mod tests {
|
|||||||
use bevy_platform::collections::HashMap;
|
use bevy_platform::collections::HashMap;
|
||||||
use bevy_render::{camera::ManualTextureViews, prelude::Camera};
|
use bevy_render::{camera::ManualTextureViews, prelude::Camera};
|
||||||
use bevy_transform::systems::mark_dirty_trees;
|
use bevy_transform::systems::mark_dirty_trees;
|
||||||
use bevy_transform::{
|
use bevy_transform::systems::{propagate_parent_transforms, sync_simple_transforms};
|
||||||
prelude::GlobalTransform,
|
|
||||||
systems::{propagate_parent_transforms, sync_simple_transforms},
|
|
||||||
};
|
|
||||||
use bevy_utils::prelude::default;
|
use bevy_utils::prelude::default;
|
||||||
use bevy_window::{
|
use bevy_window::{
|
||||||
PrimaryWindow, Window, WindowCreated, WindowResized, WindowResolution,
|
PrimaryWindow, Window, WindowCreated, WindowResized, WindowResolution,
|
||||||
@ -684,23 +695,20 @@ mod tests {
|
|||||||
ui_schedule.run(&mut world);
|
ui_schedule.run(&mut world);
|
||||||
|
|
||||||
let overlap_check = world
|
let overlap_check = world
|
||||||
.query_filtered::<(Entity, &ComputedNode, &GlobalTransform), Without<ChildOf>>()
|
.query_filtered::<(Entity, &ComputedNode, &UiGlobalTransform), Without<ChildOf>>()
|
||||||
.iter(&world)
|
.iter(&world)
|
||||||
.fold(
|
.fold(
|
||||||
Option::<(Rect, bool)>::None,
|
Option::<(Rect, bool)>::None,
|
||||||
|option_rect, (entity, node, global_transform)| {
|
|option_rect, (entity, node, transform)| {
|
||||||
let current_rect = Rect::from_center_size(
|
let current_rect = Rect::from_center_size(transform.translation, node.size());
|
||||||
global_transform.translation().truncate(),
|
|
||||||
node.size(),
|
|
||||||
);
|
|
||||||
assert!(
|
assert!(
|
||||||
current_rect.height().abs() + current_rect.width().abs() > 0.,
|
current_rect.height().abs() + current_rect.width().abs() > 0.,
|
||||||
"root ui node {entity} doesn't have a logical size"
|
"root ui node {entity} doesn't have a logical size"
|
||||||
);
|
);
|
||||||
assert_ne!(
|
assert_ne!(
|
||||||
global_transform.affine(),
|
*transform,
|
||||||
GlobalTransform::default().affine(),
|
UiGlobalTransform::default(),
|
||||||
"root ui node {entity} global transform is not populated"
|
"root ui node {entity} transform is not populated"
|
||||||
);
|
);
|
||||||
let Some((rect, is_overlapping)) = option_rect else {
|
let Some((rect, is_overlapping)) = option_rect else {
|
||||||
return Some((current_rect, false));
|
return Some((current_rect, false));
|
||||||
|
|||||||
@ -18,6 +18,7 @@ pub mod widget;
|
|||||||
pub mod gradients;
|
pub mod gradients;
|
||||||
#[cfg(feature = "bevy_ui_picking_backend")]
|
#[cfg(feature = "bevy_ui_picking_backend")]
|
||||||
pub mod picking_backend;
|
pub mod picking_backend;
|
||||||
|
pub mod ui_transform;
|
||||||
|
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
#[cfg(feature = "bevy_ui_picking_backend")]
|
#[cfg(feature = "bevy_ui_picking_backend")]
|
||||||
@ -42,6 +43,7 @@ pub use measurement::*;
|
|||||||
pub use render::*;
|
pub use render::*;
|
||||||
pub use ui_material::*;
|
pub use ui_material::*;
|
||||||
pub use ui_node::*;
|
pub use ui_node::*;
|
||||||
|
pub use ui_transform::*;
|
||||||
|
|
||||||
use widget::{ImageNode, ImageNodeSize, ViewportNode};
|
use widget::{ImageNode, ImageNodeSize, ViewportNode};
|
||||||
|
|
||||||
@ -64,6 +66,7 @@ pub mod prelude {
|
|||||||
gradients::*,
|
gradients::*,
|
||||||
ui_material::*,
|
ui_material::*,
|
||||||
ui_node::*,
|
ui_node::*,
|
||||||
|
ui_transform::*,
|
||||||
widget::{Button, ImageNode, Label, NodeImageMode, ViewportNode},
|
widget::{Button, ImageNode, Label, NodeImageMode, ViewportNode},
|
||||||
Interaction, MaterialNode, UiMaterialPlugin, UiScale,
|
Interaction, MaterialNode, UiMaterialPlugin, UiScale,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -24,14 +24,13 @@
|
|||||||
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use crate::{focus::pick_rounded_rect, prelude::*, UiStack};
|
use crate::{prelude::*, ui_transform::UiGlobalTransform, UiStack};
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::prelude::*;
|
||||||
use bevy_ecs::{prelude::*, query::QueryData};
|
use bevy_ecs::{prelude::*, query::QueryData};
|
||||||
use bevy_math::{Rect, Vec2};
|
use bevy_math::Vec2;
|
||||||
use bevy_platform::collections::HashMap;
|
use bevy_platform::collections::HashMap;
|
||||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||||
use bevy_render::prelude::*;
|
use bevy_render::prelude::*;
|
||||||
use bevy_transform::prelude::*;
|
|
||||||
use bevy_window::PrimaryWindow;
|
use bevy_window::PrimaryWindow;
|
||||||
|
|
||||||
use bevy_picking::backend::prelude::*;
|
use bevy_picking::backend::prelude::*;
|
||||||
@ -91,9 +90,8 @@ impl Plugin for UiPickingPlugin {
|
|||||||
pub struct NodeQuery {
|
pub struct NodeQuery {
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
node: &'static ComputedNode,
|
node: &'static ComputedNode,
|
||||||
global_transform: &'static GlobalTransform,
|
transform: &'static UiGlobalTransform,
|
||||||
pickable: Option<&'static Pickable>,
|
pickable: Option<&'static Pickable>,
|
||||||
calculated_clip: Option<&'static CalculatedClip>,
|
|
||||||
inherited_visibility: Option<&'static InheritedVisibility>,
|
inherited_visibility: Option<&'static InheritedVisibility>,
|
||||||
target_camera: &'static ComputedNodeTarget,
|
target_camera: &'static ComputedNodeTarget,
|
||||||
}
|
}
|
||||||
@ -110,6 +108,8 @@ pub fn ui_picking(
|
|||||||
ui_stack: Res<UiStack>,
|
ui_stack: Res<UiStack>,
|
||||||
node_query: Query<NodeQuery>,
|
node_query: Query<NodeQuery>,
|
||||||
mut output: EventWriter<PointerHits>,
|
mut output: EventWriter<PointerHits>,
|
||||||
|
clipping_query: Query<(&ComputedNode, &UiGlobalTransform, &Node)>,
|
||||||
|
child_of_query: Query<&ChildOf>,
|
||||||
) {
|
) {
|
||||||
// For each camera, the pointer and its position
|
// For each camera, the pointer and its position
|
||||||
let mut pointer_pos_by_camera = HashMap::<Entity, HashMap<PointerId, Vec2>>::default();
|
let mut pointer_pos_by_camera = HashMap::<Entity, HashMap<PointerId, Vec2>>::default();
|
||||||
@ -181,43 +181,33 @@ pub fn ui_picking(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let node_rect = Rect::from_center_size(
|
|
||||||
node.global_transform.translation().truncate(),
|
|
||||||
node.node.size(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Nodes with Display::None have a (0., 0.) logical rect and can be ignored
|
// Nodes with Display::None have a (0., 0.) logical rect and can be ignored
|
||||||
if node_rect.size() == Vec2::ZERO {
|
if node.node.size() == Vec2::ZERO {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intersect with the calculated clip rect to find the bounds of the visible region of the node
|
|
||||||
let visible_rect = node
|
|
||||||
.calculated_clip
|
|
||||||
.map(|clip| node_rect.intersect(clip.clip))
|
|
||||||
.unwrap_or(node_rect);
|
|
||||||
|
|
||||||
let pointers_on_this_cam = pointer_pos_by_camera.get(&camera_entity);
|
let pointers_on_this_cam = pointer_pos_by_camera.get(&camera_entity);
|
||||||
|
|
||||||
// The mouse position relative to the node
|
// Find the normalized cursor position relative to the node.
|
||||||
// (0., 0.) is the top-left corner, (1., 1.) is the bottom-right corner
|
// (±0., 0.) is the center with the corners at points (±0.5, ±0.5).
|
||||||
// Coordinates are relative to the entire node, not just the visible region.
|
// Coordinates are relative to the entire node, not just the visible region.
|
||||||
for (pointer_id, cursor_position) in pointers_on_this_cam.iter().flat_map(|h| h.iter()) {
|
for (pointer_id, cursor_position) in pointers_on_this_cam.iter().flat_map(|h| h.iter()) {
|
||||||
let relative_cursor_position = (*cursor_position - node_rect.min) / node_rect.size();
|
if node.node.contains_point(*node.transform, *cursor_position)
|
||||||
|
&& clip_check_recursive(
|
||||||
if visible_rect
|
*cursor_position,
|
||||||
.normalize(node_rect)
|
*node_entity,
|
||||||
.contains(relative_cursor_position)
|
&clipping_query,
|
||||||
&& pick_rounded_rect(
|
&child_of_query,
|
||||||
*cursor_position - node_rect.center(),
|
|
||||||
node_rect.size(),
|
|
||||||
node.node.border_radius,
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
hit_nodes
|
hit_nodes
|
||||||
.entry((camera_entity, *pointer_id))
|
.entry((camera_entity, *pointer_id))
|
||||||
.or_default()
|
.or_default()
|
||||||
.push((*node_entity, relative_cursor_position));
|
.push((
|
||||||
|
*node_entity,
|
||||||
|
node.transform.inverse().transform_point2(*cursor_position)
|
||||||
|
/ node.node.size(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -262,3 +252,27 @@ pub fn ui_picking(
|
|||||||
output.write(PointerHits::new(*pointer, picks, order));
|
output.write(PointerHits::new(*pointer, picks, order));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Walk up the tree child-to-parent checking that `point` is not clipped by any ancestor node.
|
||||||
|
pub fn clip_check_recursive(
|
||||||
|
point: Vec2,
|
||||||
|
entity: Entity,
|
||||||
|
clipping_query: &Query<'_, '_, (&ComputedNode, &UiGlobalTransform, &Node)>,
|
||||||
|
child_of_query: &Query<&ChildOf>,
|
||||||
|
) -> bool {
|
||||||
|
if let Ok(child_of) = child_of_query.get(entity) {
|
||||||
|
let parent = child_of.0;
|
||||||
|
if let Ok((computed_node, transform, node)) = clipping_query.get(parent) {
|
||||||
|
if !computed_node
|
||||||
|
.resolve_clip_rect(node.overflow, node.overflow_clip_margin)
|
||||||
|
.contains(transform.inverse().transform_point2(point))
|
||||||
|
{
|
||||||
|
// The point is clipped and should be ignored by picking
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clip_check_recursive(point, parent, clipping_query, child_of_query);
|
||||||
|
}
|
||||||
|
// Reached root, point unclipped by all ancestors
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use core::{hash::Hash, ops::Range};
|
use core::{hash::Hash, ops::Range};
|
||||||
|
|
||||||
|
use crate::prelude::UiGlobalTransform;
|
||||||
use crate::{
|
use crate::{
|
||||||
BoxShadow, BoxShadowSamples, CalculatedClip, ComputedNode, ComputedNodeTarget, RenderUiSystems,
|
BoxShadow, BoxShadowSamples, CalculatedClip, ComputedNode, ComputedNodeTarget, RenderUiSystems,
|
||||||
ResolvedBorderRadius, TransparentUi, Val,
|
ResolvedBorderRadius, TransparentUi, Val,
|
||||||
@ -18,7 +19,7 @@ use bevy_ecs::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
use bevy_image::BevyDefault as _;
|
use bevy_image::BevyDefault as _;
|
||||||
use bevy_math::{vec2, FloatOrd, Mat4, Rect, Vec2, Vec3Swizzles, Vec4Swizzles};
|
use bevy_math::{vec2, Affine2, FloatOrd, Rect, Vec2};
|
||||||
use bevy_render::sync_world::MainEntity;
|
use bevy_render::sync_world::MainEntity;
|
||||||
use bevy_render::RenderApp;
|
use bevy_render::RenderApp;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
@ -29,7 +30,6 @@ use bevy_render::{
|
|||||||
view::*,
|
view::*,
|
||||||
Extract, ExtractSchedule, Render, RenderSystems,
|
Extract, ExtractSchedule, Render, RenderSystems,
|
||||||
};
|
};
|
||||||
use bevy_transform::prelude::GlobalTransform;
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
|
||||||
use super::{stack_z_offsets, UiCameraMap, UiCameraView, QUAD_INDICES, QUAD_VERTEX_POSITIONS};
|
use super::{stack_z_offsets, UiCameraMap, UiCameraView, QUAD_INDICES, QUAD_VERTEX_POSITIONS};
|
||||||
@ -211,7 +211,7 @@ impl SpecializedRenderPipeline for BoxShadowPipeline {
|
|||||||
/// Description of a shadow to be sorted and queued for rendering
|
/// Description of a shadow to be sorted and queued for rendering
|
||||||
pub struct ExtractedBoxShadow {
|
pub struct ExtractedBoxShadow {
|
||||||
pub stack_index: u32,
|
pub stack_index: u32,
|
||||||
pub transform: Mat4,
|
pub transform: Affine2,
|
||||||
pub bounds: Vec2,
|
pub bounds: Vec2,
|
||||||
pub clip: Option<Rect>,
|
pub clip: Option<Rect>,
|
||||||
pub extracted_camera_entity: Entity,
|
pub extracted_camera_entity: Entity,
|
||||||
@ -236,7 +236,7 @@ pub fn extract_shadows(
|
|||||||
Query<(
|
Query<(
|
||||||
Entity,
|
Entity,
|
||||||
&ComputedNode,
|
&ComputedNode,
|
||||||
&GlobalTransform,
|
&UiGlobalTransform,
|
||||||
&InheritedVisibility,
|
&InheritedVisibility,
|
||||||
&BoxShadow,
|
&BoxShadow,
|
||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
@ -302,7 +302,7 @@ pub fn extract_shadows(
|
|||||||
extracted_box_shadows.box_shadows.push(ExtractedBoxShadow {
|
extracted_box_shadows.box_shadows.push(ExtractedBoxShadow {
|
||||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||||
stack_index: uinode.stack_index,
|
stack_index: uinode.stack_index,
|
||||||
transform: transform.compute_matrix() * Mat4::from_translation(offset.extend(0.)),
|
transform: Affine2::from(transform) * Affine2::from_translation(offset),
|
||||||
color: drop_shadow.color.into(),
|
color: drop_shadow.color.into(),
|
||||||
bounds: shadow_size + 6. * blur_radius,
|
bounds: shadow_size + 6. * blur_radius,
|
||||||
clip: clip.map(|clip| clip.clip),
|
clip: clip.map(|clip| clip.clip),
|
||||||
@ -405,11 +405,15 @@ pub fn prepare_shadows(
|
|||||||
.get(item.index)
|
.get(item.index)
|
||||||
.filter(|n| item.entity() == n.render_entity)
|
.filter(|n| item.entity() == n.render_entity)
|
||||||
{
|
{
|
||||||
let rect_size = box_shadow.bounds.extend(1.0);
|
let rect_size = box_shadow.bounds;
|
||||||
|
|
||||||
// Specify the corners of the node
|
// Specify the corners of the node
|
||||||
let positions = QUAD_VERTEX_POSITIONS
|
let positions = QUAD_VERTEX_POSITIONS.map(|pos| {
|
||||||
.map(|pos| (box_shadow.transform * (pos * rect_size).extend(1.)).xyz());
|
box_shadow
|
||||||
|
.transform
|
||||||
|
.transform_point2(pos * rect_size)
|
||||||
|
.extend(0.)
|
||||||
|
});
|
||||||
|
|
||||||
// Calculate the effect of clipping
|
// Calculate the effect of clipping
|
||||||
// Note: this won't work with rotation/scaling, but that's much more complex (may need more that 2 quads)
|
// Note: this won't work with rotation/scaling, but that's much more complex (may need more that 2 quads)
|
||||||
@ -443,7 +447,7 @@ pub fn prepare_shadows(
|
|||||||
positions[3] + positions_diff[3].extend(0.),
|
positions[3] + positions_diff[3].extend(0.),
|
||||||
];
|
];
|
||||||
|
|
||||||
let transformed_rect_size = box_shadow.transform.transform_vector3(rect_size);
|
let transformed_rect_size = box_shadow.transform.transform_vector2(rect_size);
|
||||||
|
|
||||||
// Don't try to cull nodes that have a rotation
|
// Don't try to cull nodes that have a rotation
|
||||||
// In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
|
// In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
|
||||||
@ -492,7 +496,7 @@ pub fn prepare_shadows(
|
|||||||
size: box_shadow.size.into(),
|
size: box_shadow.size.into(),
|
||||||
radius,
|
radius,
|
||||||
blur: box_shadow.blur_radius,
|
blur: box_shadow.blur_radius,
|
||||||
bounds: rect_size.xy().into(),
|
bounds: rect_size.into(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use crate::shader_flags;
|
use crate::shader_flags;
|
||||||
use crate::ui_node::ComputedNodeTarget;
|
use crate::ui_node::ComputedNodeTarget;
|
||||||
|
use crate::ui_transform::UiGlobalTransform;
|
||||||
use crate::CalculatedClip;
|
use crate::CalculatedClip;
|
||||||
use crate::ComputedNode;
|
use crate::ComputedNode;
|
||||||
use bevy_asset::AssetId;
|
use bevy_asset::AssetId;
|
||||||
@ -16,7 +17,6 @@ use bevy_render::sync_world::TemporaryRenderEntity;
|
|||||||
use bevy_render::view::InheritedVisibility;
|
use bevy_render::view::InheritedVisibility;
|
||||||
use bevy_render::Extract;
|
use bevy_render::Extract;
|
||||||
use bevy_sprite::BorderRect;
|
use bevy_sprite::BorderRect;
|
||||||
use bevy_transform::components::GlobalTransform;
|
|
||||||
|
|
||||||
use super::ExtractedUiItem;
|
use super::ExtractedUiItem;
|
||||||
use super::ExtractedUiNode;
|
use super::ExtractedUiNode;
|
||||||
@ -62,9 +62,9 @@ pub fn extract_debug_overlay(
|
|||||||
Query<(
|
Query<(
|
||||||
Entity,
|
Entity,
|
||||||
&ComputedNode,
|
&ComputedNode,
|
||||||
|
&UiGlobalTransform,
|
||||||
&InheritedVisibility,
|
&InheritedVisibility,
|
||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
&GlobalTransform,
|
|
||||||
&ComputedNodeTarget,
|
&ComputedNodeTarget,
|
||||||
)>,
|
)>,
|
||||||
>,
|
>,
|
||||||
@ -76,7 +76,7 @@ pub fn extract_debug_overlay(
|
|||||||
|
|
||||||
let mut camera_mapper = camera_map.get_mapper();
|
let mut camera_mapper = camera_map.get_mapper();
|
||||||
|
|
||||||
for (entity, uinode, visibility, maybe_clip, transform, computed_target) in &uinode_query {
|
for (entity, uinode, transform, visibility, maybe_clip, computed_target) in &uinode_query {
|
||||||
if !debug_options.show_hidden && !visibility.get() {
|
if !debug_options.show_hidden && !visibility.get() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -102,7 +102,7 @@ pub fn extract_debug_overlay(
|
|||||||
extracted_camera_entity,
|
extracted_camera_entity,
|
||||||
item: ExtractedUiItem::Node {
|
item: ExtractedUiItem::Node {
|
||||||
atlas_scaling: None,
|
atlas_scaling: None,
|
||||||
transform: transform.compute_matrix(),
|
transform: transform.into(),
|
||||||
flip_x: false,
|
flip_x: false,
|
||||||
flip_y: false,
|
flip_y: false,
|
||||||
border: BorderRect::all(debug_options.line_width / uinode.inverse_scale_factor()),
|
border: BorderRect::all(debug_options.line_width / uinode.inverse_scale_factor()),
|
||||||
|
|||||||
@ -17,8 +17,9 @@ use bevy_ecs::{
|
|||||||
use bevy_image::prelude::*;
|
use bevy_image::prelude::*;
|
||||||
use bevy_math::{
|
use bevy_math::{
|
||||||
ops::{cos, sin},
|
ops::{cos, sin},
|
||||||
FloatOrd, Mat4, Rect, Vec2, Vec3Swizzles, Vec4Swizzles,
|
FloatOrd, Rect, Vec2,
|
||||||
};
|
};
|
||||||
|
use bevy_math::{Affine2, Vec2Swizzles};
|
||||||
use bevy_render::sync_world::MainEntity;
|
use bevy_render::sync_world::MainEntity;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
render_phase::*,
|
render_phase::*,
|
||||||
@ -29,7 +30,6 @@ use bevy_render::{
|
|||||||
Extract, ExtractSchedule, Render, RenderSystems,
|
Extract, ExtractSchedule, Render, RenderSystems,
|
||||||
};
|
};
|
||||||
use bevy_sprite::BorderRect;
|
use bevy_sprite::BorderRect;
|
||||||
use bevy_transform::prelude::GlobalTransform;
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
|
||||||
use super::shader_flags::BORDER_ALL;
|
use super::shader_flags::BORDER_ALL;
|
||||||
@ -238,7 +238,7 @@ pub enum ResolvedGradient {
|
|||||||
|
|
||||||
pub struct ExtractedGradient {
|
pub struct ExtractedGradient {
|
||||||
pub stack_index: u32,
|
pub stack_index: u32,
|
||||||
pub transform: Mat4,
|
pub transform: Affine2,
|
||||||
pub rect: Rect,
|
pub rect: Rect,
|
||||||
pub clip: Option<Rect>,
|
pub clip: Option<Rect>,
|
||||||
pub extracted_camera_entity: Entity,
|
pub extracted_camera_entity: Entity,
|
||||||
@ -354,7 +354,7 @@ pub fn extract_gradients(
|
|||||||
Entity,
|
Entity,
|
||||||
&ComputedNode,
|
&ComputedNode,
|
||||||
&ComputedNodeTarget,
|
&ComputedNodeTarget,
|
||||||
&GlobalTransform,
|
&UiGlobalTransform,
|
||||||
&InheritedVisibility,
|
&InheritedVisibility,
|
||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
AnyOf<(&BackgroundGradient, &BorderGradient)>,
|
AnyOf<(&BackgroundGradient, &BorderGradient)>,
|
||||||
@ -414,7 +414,7 @@ pub fn extract_gradients(
|
|||||||
border_radius: uinode.border_radius,
|
border_radius: uinode.border_radius,
|
||||||
border: uinode.border,
|
border: uinode.border,
|
||||||
node_type,
|
node_type,
|
||||||
transform: transform.compute_matrix(),
|
transform: transform.into(),
|
||||||
},
|
},
|
||||||
main_entity: entity.into(),
|
main_entity: entity.into(),
|
||||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||||
@ -439,7 +439,7 @@ pub fn extract_gradients(
|
|||||||
extracted_gradients.items.push(ExtractedGradient {
|
extracted_gradients.items.push(ExtractedGradient {
|
||||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||||
stack_index: uinode.stack_index,
|
stack_index: uinode.stack_index,
|
||||||
transform: transform.compute_matrix(),
|
transform: transform.into(),
|
||||||
stops_range: range_start..extracted_color_stops.0.len(),
|
stops_range: range_start..extracted_color_stops.0.len(),
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
min: Vec2::ZERO,
|
min: Vec2::ZERO,
|
||||||
@ -487,7 +487,7 @@ pub fn extract_gradients(
|
|||||||
extracted_gradients.items.push(ExtractedGradient {
|
extracted_gradients.items.push(ExtractedGradient {
|
||||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||||
stack_index: uinode.stack_index,
|
stack_index: uinode.stack_index,
|
||||||
transform: transform.compute_matrix(),
|
transform: transform.into(),
|
||||||
stops_range: range_start..extracted_color_stops.0.len(),
|
stops_range: range_start..extracted_color_stops.0.len(),
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
min: Vec2::ZERO,
|
min: Vec2::ZERO,
|
||||||
@ -541,7 +541,7 @@ pub fn extract_gradients(
|
|||||||
extracted_gradients.items.push(ExtractedGradient {
|
extracted_gradients.items.push(ExtractedGradient {
|
||||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||||
stack_index: uinode.stack_index,
|
stack_index: uinode.stack_index,
|
||||||
transform: transform.compute_matrix(),
|
transform: transform.into(),
|
||||||
stops_range: range_start..extracted_color_stops.0.len(),
|
stops_range: range_start..extracted_color_stops.0.len(),
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
min: Vec2::ZERO,
|
min: Vec2::ZERO,
|
||||||
@ -675,12 +675,16 @@ pub fn prepare_gradient(
|
|||||||
*item.batch_range_mut() = item_index as u32..item_index as u32 + 1;
|
*item.batch_range_mut() = item_index as u32..item_index as u32 + 1;
|
||||||
let uinode_rect = gradient.rect;
|
let uinode_rect = gradient.rect;
|
||||||
|
|
||||||
let rect_size = uinode_rect.size().extend(1.0);
|
let rect_size = uinode_rect.size();
|
||||||
|
|
||||||
// Specify the corners of the node
|
// Specify the corners of the node
|
||||||
let positions = QUAD_VERTEX_POSITIONS
|
let positions = QUAD_VERTEX_POSITIONS.map(|pos| {
|
||||||
.map(|pos| (gradient.transform * (pos * rect_size).extend(1.)).xyz());
|
gradient
|
||||||
let corner_points = QUAD_VERTEX_POSITIONS.map(|pos| pos.xy() * rect_size.xy());
|
.transform
|
||||||
|
.transform_point2(pos * rect_size)
|
||||||
|
.extend(0.)
|
||||||
|
});
|
||||||
|
let corner_points = QUAD_VERTEX_POSITIONS.map(|pos| pos * rect_size);
|
||||||
|
|
||||||
// Calculate the effect of clipping
|
// Calculate the effect of clipping
|
||||||
// Note: this won't work with rotation/scaling, but that's much more complex (may need more that 2 quads)
|
// Note: this won't work with rotation/scaling, but that's much more complex (may need more that 2 quads)
|
||||||
@ -721,7 +725,7 @@ pub fn prepare_gradient(
|
|||||||
corner_points[3] + positions_diff[3],
|
corner_points[3] + positions_diff[3],
|
||||||
];
|
];
|
||||||
|
|
||||||
let transformed_rect_size = gradient.transform.transform_vector3(rect_size);
|
let transformed_rect_size = gradient.transform.transform_vector2(rect_size);
|
||||||
|
|
||||||
// Don't try to cull nodes that have a rotation
|
// Don't try to cull nodes that have a rotation
|
||||||
// In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
|
// In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
|
||||||
|
|||||||
@ -8,7 +8,9 @@ pub mod ui_texture_slice_pipeline;
|
|||||||
mod debug_overlay;
|
mod debug_overlay;
|
||||||
mod gradient;
|
mod gradient;
|
||||||
|
|
||||||
|
use crate::prelude::UiGlobalTransform;
|
||||||
use crate::widget::{ImageNode, ViewportNode};
|
use crate::widget::{ImageNode, ViewportNode};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
BackgroundColor, BorderColor, BoxShadowSamples, CalculatedClip, ComputedNode,
|
BackgroundColor, BorderColor, BoxShadowSamples, CalculatedClip, ComputedNode,
|
||||||
ComputedNodeTarget, Outline, ResolvedBorderRadius, TextShadow, UiAntiAlias,
|
ComputedNodeTarget, Outline, ResolvedBorderRadius, TextShadow, UiAntiAlias,
|
||||||
@ -22,7 +24,7 @@ use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
|
|||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_ecs::system::SystemParam;
|
use bevy_ecs::system::SystemParam;
|
||||||
use bevy_image::prelude::*;
|
use bevy_image::prelude::*;
|
||||||
use bevy_math::{FloatOrd, Mat4, Rect, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4Swizzles};
|
use bevy_math::{Affine2, FloatOrd, Mat4, Rect, UVec4, Vec2};
|
||||||
use bevy_render::load_shader_library;
|
use bevy_render::load_shader_library;
|
||||||
use bevy_render::render_graph::{NodeRunError, RenderGraphContext};
|
use bevy_render::render_graph::{NodeRunError, RenderGraphContext};
|
||||||
use bevy_render::render_phase::ViewSortedRenderPhases;
|
use bevy_render::render_phase::ViewSortedRenderPhases;
|
||||||
@ -243,7 +245,7 @@ pub enum ExtractedUiItem {
|
|||||||
/// Ordering: left, top, right, bottom.
|
/// Ordering: left, top, right, bottom.
|
||||||
border: BorderRect,
|
border: BorderRect,
|
||||||
node_type: NodeType,
|
node_type: NodeType,
|
||||||
transform: Mat4,
|
transform: Affine2,
|
||||||
},
|
},
|
||||||
/// A contiguous sequence of text glyphs from the same section
|
/// A contiguous sequence of text glyphs from the same section
|
||||||
Glyphs {
|
Glyphs {
|
||||||
@ -253,7 +255,7 @@ pub enum ExtractedUiItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExtractedGlyph {
|
pub struct ExtractedGlyph {
|
||||||
pub transform: Mat4,
|
pub transform: Affine2,
|
||||||
pub rect: Rect,
|
pub rect: Rect,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,7 +346,7 @@ pub fn extract_uinode_background_colors(
|
|||||||
Query<(
|
Query<(
|
||||||
Entity,
|
Entity,
|
||||||
&ComputedNode,
|
&ComputedNode,
|
||||||
&GlobalTransform,
|
&UiGlobalTransform,
|
||||||
&InheritedVisibility,
|
&InheritedVisibility,
|
||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
&ComputedNodeTarget,
|
&ComputedNodeTarget,
|
||||||
@ -383,7 +385,7 @@ pub fn extract_uinode_background_colors(
|
|||||||
extracted_camera_entity,
|
extracted_camera_entity,
|
||||||
item: ExtractedUiItem::Node {
|
item: ExtractedUiItem::Node {
|
||||||
atlas_scaling: None,
|
atlas_scaling: None,
|
||||||
transform: transform.compute_matrix(),
|
transform: transform.into(),
|
||||||
flip_x: false,
|
flip_x: false,
|
||||||
flip_y: false,
|
flip_y: false,
|
||||||
border: uinode.border(),
|
border: uinode.border(),
|
||||||
@ -403,7 +405,7 @@ pub fn extract_uinode_images(
|
|||||||
Query<(
|
Query<(
|
||||||
Entity,
|
Entity,
|
||||||
&ComputedNode,
|
&ComputedNode,
|
||||||
&GlobalTransform,
|
&UiGlobalTransform,
|
||||||
&InheritedVisibility,
|
&InheritedVisibility,
|
||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
&ComputedNodeTarget,
|
&ComputedNodeTarget,
|
||||||
@ -467,7 +469,7 @@ pub fn extract_uinode_images(
|
|||||||
extracted_camera_entity,
|
extracted_camera_entity,
|
||||||
item: ExtractedUiItem::Node {
|
item: ExtractedUiItem::Node {
|
||||||
atlas_scaling,
|
atlas_scaling,
|
||||||
transform: transform.compute_matrix(),
|
transform: transform.into(),
|
||||||
flip_x: image.flip_x,
|
flip_x: image.flip_x,
|
||||||
flip_y: image.flip_y,
|
flip_y: image.flip_y,
|
||||||
border: uinode.border,
|
border: uinode.border,
|
||||||
@ -487,7 +489,7 @@ pub fn extract_uinode_borders(
|
|||||||
Entity,
|
Entity,
|
||||||
&Node,
|
&Node,
|
||||||
&ComputedNode,
|
&ComputedNode,
|
||||||
&GlobalTransform,
|
&UiGlobalTransform,
|
||||||
&InheritedVisibility,
|
&InheritedVisibility,
|
||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
&ComputedNodeTarget,
|
&ComputedNodeTarget,
|
||||||
@ -503,7 +505,7 @@ pub fn extract_uinode_borders(
|
|||||||
entity,
|
entity,
|
||||||
node,
|
node,
|
||||||
computed_node,
|
computed_node,
|
||||||
global_transform,
|
transform,
|
||||||
inherited_visibility,
|
inherited_visibility,
|
||||||
maybe_clip,
|
maybe_clip,
|
||||||
camera,
|
camera,
|
||||||
@ -567,7 +569,7 @@ pub fn extract_uinode_borders(
|
|||||||
extracted_camera_entity,
|
extracted_camera_entity,
|
||||||
item: ExtractedUiItem::Node {
|
item: ExtractedUiItem::Node {
|
||||||
atlas_scaling: None,
|
atlas_scaling: None,
|
||||||
transform: global_transform.compute_matrix(),
|
transform: transform.into(),
|
||||||
flip_x: false,
|
flip_x: false,
|
||||||
flip_y: false,
|
flip_y: false,
|
||||||
border: computed_node.border(),
|
border: computed_node.border(),
|
||||||
@ -600,7 +602,7 @@ pub fn extract_uinode_borders(
|
|||||||
clip: maybe_clip.map(|clip| clip.clip),
|
clip: maybe_clip.map(|clip| clip.clip),
|
||||||
extracted_camera_entity,
|
extracted_camera_entity,
|
||||||
item: ExtractedUiItem::Node {
|
item: ExtractedUiItem::Node {
|
||||||
transform: global_transform.compute_matrix(),
|
transform: transform.into(),
|
||||||
atlas_scaling: None,
|
atlas_scaling: None,
|
||||||
flip_x: false,
|
flip_x: false,
|
||||||
flip_y: false,
|
flip_y: false,
|
||||||
@ -749,7 +751,7 @@ pub fn extract_viewport_nodes(
|
|||||||
Query<(
|
Query<(
|
||||||
Entity,
|
Entity,
|
||||||
&ComputedNode,
|
&ComputedNode,
|
||||||
&GlobalTransform,
|
&UiGlobalTransform,
|
||||||
&InheritedVisibility,
|
&InheritedVisibility,
|
||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
&ComputedNodeTarget,
|
&ComputedNodeTarget,
|
||||||
@ -792,7 +794,7 @@ pub fn extract_viewport_nodes(
|
|||||||
extracted_camera_entity,
|
extracted_camera_entity,
|
||||||
item: ExtractedUiItem::Node {
|
item: ExtractedUiItem::Node {
|
||||||
atlas_scaling: None,
|
atlas_scaling: None,
|
||||||
transform: transform.compute_matrix(),
|
transform: transform.into(),
|
||||||
flip_x: false,
|
flip_x: false,
|
||||||
flip_y: false,
|
flip_y: false,
|
||||||
border: uinode.border(),
|
border: uinode.border(),
|
||||||
@ -812,7 +814,7 @@ pub fn extract_text_sections(
|
|||||||
Query<(
|
Query<(
|
||||||
Entity,
|
Entity,
|
||||||
&ComputedNode,
|
&ComputedNode,
|
||||||
&GlobalTransform,
|
&UiGlobalTransform,
|
||||||
&InheritedVisibility,
|
&InheritedVisibility,
|
||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
&ComputedNodeTarget,
|
&ComputedNodeTarget,
|
||||||
@ -830,7 +832,7 @@ pub fn extract_text_sections(
|
|||||||
for (
|
for (
|
||||||
entity,
|
entity,
|
||||||
uinode,
|
uinode,
|
||||||
global_transform,
|
transform,
|
||||||
inherited_visibility,
|
inherited_visibility,
|
||||||
clip,
|
clip,
|
||||||
camera,
|
camera,
|
||||||
@ -847,8 +849,7 @@ pub fn extract_text_sections(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let transform = global_transform.affine()
|
let transform = Affine2::from(*transform) * Affine2::from_translation(-0.5 * uinode.size());
|
||||||
* bevy_math::Affine3A::from_translation((-0.5 * uinode.size()).extend(0.));
|
|
||||||
|
|
||||||
for (
|
for (
|
||||||
i,
|
i,
|
||||||
@ -866,7 +867,7 @@ pub fn extract_text_sections(
|
|||||||
.textures[atlas_info.location.glyph_index]
|
.textures[atlas_info.location.glyph_index]
|
||||||
.as_rect();
|
.as_rect();
|
||||||
extracted_uinodes.glyphs.push(ExtractedGlyph {
|
extracted_uinodes.glyphs.push(ExtractedGlyph {
|
||||||
transform: transform * Mat4::from_translation(position.extend(0.)),
|
transform: transform * Affine2::from_translation(*position),
|
||||||
rect,
|
rect,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -910,8 +911,8 @@ pub fn extract_text_shadows(
|
|||||||
Query<(
|
Query<(
|
||||||
Entity,
|
Entity,
|
||||||
&ComputedNode,
|
&ComputedNode,
|
||||||
|
&UiGlobalTransform,
|
||||||
&ComputedNodeTarget,
|
&ComputedNodeTarget,
|
||||||
&GlobalTransform,
|
|
||||||
&InheritedVisibility,
|
&InheritedVisibility,
|
||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
&TextLayoutInfo,
|
&TextLayoutInfo,
|
||||||
@ -924,16 +925,8 @@ pub fn extract_text_shadows(
|
|||||||
let mut end = start + 1;
|
let mut end = start + 1;
|
||||||
|
|
||||||
let mut camera_mapper = camera_map.get_mapper();
|
let mut camera_mapper = camera_map.get_mapper();
|
||||||
for (
|
for (entity, uinode, transform, target, inherited_visibility, clip, text_layout_info, shadow) in
|
||||||
entity,
|
&uinode_query
|
||||||
uinode,
|
|
||||||
target,
|
|
||||||
global_transform,
|
|
||||||
inherited_visibility,
|
|
||||||
clip,
|
|
||||||
text_layout_info,
|
|
||||||
shadow,
|
|
||||||
) in &uinode_query
|
|
||||||
{
|
{
|
||||||
// Skip if not visible or if size is set to zero (e.g. when a parent is set to `Display::None`)
|
// Skip if not visible or if size is set to zero (e.g. when a parent is set to `Display::None`)
|
||||||
if !inherited_visibility.get() || uinode.is_empty() {
|
if !inherited_visibility.get() || uinode.is_empty() {
|
||||||
@ -944,9 +937,9 @@ pub fn extract_text_shadows(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let transform = global_transform.affine()
|
let node_transform = Affine2::from(*transform)
|
||||||
* Mat4::from_translation(
|
* Affine2::from_translation(
|
||||||
(-0.5 * uinode.size() + shadow.offset / uinode.inverse_scale_factor()).extend(0.),
|
-0.5 * uinode.size() + shadow.offset / uinode.inverse_scale_factor(),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (
|
for (
|
||||||
@ -965,7 +958,7 @@ pub fn extract_text_shadows(
|
|||||||
.textures[atlas_info.location.glyph_index]
|
.textures[atlas_info.location.glyph_index]
|
||||||
.as_rect();
|
.as_rect();
|
||||||
extracted_uinodes.glyphs.push(ExtractedGlyph {
|
extracted_uinodes.glyphs.push(ExtractedGlyph {
|
||||||
transform: transform * Mat4::from_translation(position.extend(0.)),
|
transform: node_transform * Affine2::from_translation(*position),
|
||||||
rect,
|
rect,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -998,7 +991,7 @@ pub fn extract_text_background_colors(
|
|||||||
Query<(
|
Query<(
|
||||||
Entity,
|
Entity,
|
||||||
&ComputedNode,
|
&ComputedNode,
|
||||||
&GlobalTransform,
|
&UiGlobalTransform,
|
||||||
&InheritedVisibility,
|
&InheritedVisibility,
|
||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
&ComputedNodeTarget,
|
&ComputedNodeTarget,
|
||||||
@ -1021,8 +1014,8 @@ pub fn extract_text_background_colors(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let transform = global_transform.affine()
|
let transform =
|
||||||
* bevy_math::Affine3A::from_translation(-0.5 * uinode.size().extend(0.));
|
Affine2::from(global_transform) * Affine2::from_translation(-0.5 * uinode.size());
|
||||||
|
|
||||||
for &(section_entity, rect) in text_layout_info.section_rects.iter() {
|
for &(section_entity, rect) in text_layout_info.section_rects.iter() {
|
||||||
let Ok(text_background_color) = text_background_colors_query.get(section_entity) else {
|
let Ok(text_background_color) = text_background_colors_query.get(section_entity) else {
|
||||||
@ -1042,7 +1035,7 @@ pub fn extract_text_background_colors(
|
|||||||
extracted_camera_entity,
|
extracted_camera_entity,
|
||||||
item: ExtractedUiItem::Node {
|
item: ExtractedUiItem::Node {
|
||||||
atlas_scaling: None,
|
atlas_scaling: None,
|
||||||
transform: transform * Mat4::from_translation(rect.center().extend(0.)),
|
transform: transform * Affine2::from_translation(rect.center()),
|
||||||
flip_x: false,
|
flip_x: false,
|
||||||
flip_y: false,
|
flip_y: false,
|
||||||
border: uinode.border(),
|
border: uinode.border(),
|
||||||
@ -1093,11 +1086,11 @@ impl Default for UiMeta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const QUAD_VERTEX_POSITIONS: [Vec3; 4] = [
|
pub(crate) const QUAD_VERTEX_POSITIONS: [Vec2; 4] = [
|
||||||
Vec3::new(-0.5, -0.5, 0.0),
|
Vec2::new(-0.5, -0.5),
|
||||||
Vec3::new(0.5, -0.5, 0.0),
|
Vec2::new(0.5, -0.5),
|
||||||
Vec3::new(0.5, 0.5, 0.0),
|
Vec2::new(0.5, 0.5),
|
||||||
Vec3::new(-0.5, 0.5, 0.0),
|
Vec2::new(-0.5, 0.5),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub(crate) const QUAD_INDICES: [usize; 6] = [0, 2, 3, 0, 1, 2];
|
pub(crate) const QUAD_INDICES: [usize; 6] = [0, 2, 3, 0, 1, 2];
|
||||||
@ -1321,12 +1314,12 @@ pub fn prepare_uinodes(
|
|||||||
|
|
||||||
let mut uinode_rect = extracted_uinode.rect;
|
let mut uinode_rect = extracted_uinode.rect;
|
||||||
|
|
||||||
let rect_size = uinode_rect.size().extend(1.0);
|
let rect_size = uinode_rect.size();
|
||||||
|
|
||||||
// Specify the corners of the node
|
// Specify the corners of the node
|
||||||
let positions = QUAD_VERTEX_POSITIONS
|
let positions = QUAD_VERTEX_POSITIONS
|
||||||
.map(|pos| (*transform * (pos * rect_size).extend(1.)).xyz());
|
.map(|pos| transform.transform_point2(pos * rect_size).extend(0.));
|
||||||
let points = QUAD_VERTEX_POSITIONS.map(|pos| pos.xy() * rect_size.xy());
|
let points = QUAD_VERTEX_POSITIONS.map(|pos| pos * rect_size);
|
||||||
|
|
||||||
// Calculate the effect of clipping
|
// Calculate the effect of clipping
|
||||||
// Note: this won't work with rotation/scaling, but that's much more complex (may need more that 2 quads)
|
// Note: this won't work with rotation/scaling, but that's much more complex (may need more that 2 quads)
|
||||||
@ -1367,7 +1360,7 @@ pub fn prepare_uinodes(
|
|||||||
points[3] + positions_diff[3],
|
points[3] + positions_diff[3],
|
||||||
];
|
];
|
||||||
|
|
||||||
let transformed_rect_size = transform.transform_vector3(rect_size);
|
let transformed_rect_size = transform.transform_vector2(rect_size);
|
||||||
|
|
||||||
// Don't try to cull nodes that have a rotation
|
// Don't try to cull nodes that have a rotation
|
||||||
// In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
|
// In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
|
||||||
@ -1448,7 +1441,7 @@ pub fn prepare_uinodes(
|
|||||||
border_radius.bottom_left,
|
border_radius.bottom_left,
|
||||||
],
|
],
|
||||||
border: [border.left, border.top, border.right, border.bottom],
|
border: [border.left, border.top, border.right, border.bottom],
|
||||||
size: rect_size.xy().into(),
|
size: rect_size.into(),
|
||||||
point: points[i].into(),
|
point: points[i].into(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1470,13 +1463,14 @@ pub fn prepare_uinodes(
|
|||||||
let color = extracted_uinode.color.to_f32_array();
|
let color = extracted_uinode.color.to_f32_array();
|
||||||
for glyph in &extracted_uinodes.glyphs[range.clone()] {
|
for glyph in &extracted_uinodes.glyphs[range.clone()] {
|
||||||
let glyph_rect = glyph.rect;
|
let glyph_rect = glyph.rect;
|
||||||
let size = glyph.rect.size();
|
let rect_size = glyph_rect.size();
|
||||||
|
|
||||||
let rect_size = glyph_rect.size().extend(1.0);
|
|
||||||
|
|
||||||
// Specify the corners of the glyph
|
// Specify the corners of the glyph
|
||||||
let positions = QUAD_VERTEX_POSITIONS.map(|pos| {
|
let positions = QUAD_VERTEX_POSITIONS.map(|pos| {
|
||||||
(glyph.transform * (pos * rect_size).extend(1.)).xyz()
|
glyph
|
||||||
|
.transform
|
||||||
|
.transform_point2(pos * glyph_rect.size())
|
||||||
|
.extend(0.)
|
||||||
});
|
});
|
||||||
|
|
||||||
let positions_diff = if let Some(clip) = extracted_uinode.clip {
|
let positions_diff = if let Some(clip) = extracted_uinode.clip {
|
||||||
@ -1511,7 +1505,7 @@ pub fn prepare_uinodes(
|
|||||||
|
|
||||||
// cull nodes that are completely clipped
|
// cull nodes that are completely clipped
|
||||||
let transformed_rect_size =
|
let transformed_rect_size =
|
||||||
glyph.transform.transform_vector3(rect_size);
|
glyph.transform.transform_vector2(rect_size);
|
||||||
if positions_diff[0].x - positions_diff[1].x
|
if positions_diff[0].x - positions_diff[1].x
|
||||||
>= transformed_rect_size.x.abs()
|
>= transformed_rect_size.x.abs()
|
||||||
|| positions_diff[1].y - positions_diff[2].y
|
|| positions_diff[1].y - positions_diff[2].y
|
||||||
@ -1548,7 +1542,7 @@ pub fn prepare_uinodes(
|
|||||||
flags: shader_flags::TEXTURED | shader_flags::CORNERS[i],
|
flags: shader_flags::TEXTURED | shader_flags::CORNERS[i],
|
||||||
radius: [0.0; 4],
|
radius: [0.0; 4],
|
||||||
border: [0.0; 4],
|
border: [0.0; 4],
|
||||||
size: size.into(),
|
size: rect_size.into(),
|
||||||
point: [0.0; 2],
|
point: [0.0; 2],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
use core::{hash::Hash, marker::PhantomData, ops::Range};
|
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use bevy_asset::*;
|
use bevy_asset::*;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
prelude::Component,
|
prelude::{Component, With},
|
||||||
query::ROQueryItem,
|
query::ROQueryItem,
|
||||||
system::{
|
system::{
|
||||||
lifetimeless::{Read, SRes},
|
lifetimeless::{Read, SRes},
|
||||||
@ -11,24 +9,22 @@ use bevy_ecs::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
use bevy_image::BevyDefault as _;
|
use bevy_image::BevyDefault as _;
|
||||||
use bevy_math::{FloatOrd, Mat4, Rect, Vec2, Vec4Swizzles};
|
use bevy_math::{Affine2, FloatOrd, Rect, Vec2};
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
extract_component::ExtractComponentPlugin,
|
extract_component::ExtractComponentPlugin,
|
||||||
globals::{GlobalsBuffer, GlobalsUniform},
|
globals::{GlobalsBuffer, GlobalsUniform},
|
||||||
|
load_shader_library,
|
||||||
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
|
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
|
||||||
render_phase::*,
|
render_phase::*,
|
||||||
render_resource::{binding_types::uniform_buffer, *},
|
render_resource::{binding_types::uniform_buffer, *},
|
||||||
renderer::{RenderDevice, RenderQueue},
|
renderer::{RenderDevice, RenderQueue},
|
||||||
|
sync_world::{MainEntity, TemporaryRenderEntity},
|
||||||
view::*,
|
view::*,
|
||||||
Extract, ExtractSchedule, Render, RenderSystems,
|
Extract, ExtractSchedule, Render, RenderSystems,
|
||||||
};
|
};
|
||||||
use bevy_render::{
|
|
||||||
load_shader_library,
|
|
||||||
sync_world::{MainEntity, TemporaryRenderEntity},
|
|
||||||
};
|
|
||||||
use bevy_sprite::BorderRect;
|
use bevy_sprite::BorderRect;
|
||||||
use bevy_transform::prelude::GlobalTransform;
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use core::{hash::Hash, marker::PhantomData, ops::Range};
|
||||||
|
|
||||||
/// Adds the necessary ECS resources and render logic to enable rendering entities using the given
|
/// Adds the necessary ECS resources and render logic to enable rendering entities using the given
|
||||||
/// [`UiMaterial`] asset type (which includes [`UiMaterial`] types).
|
/// [`UiMaterial`] asset type (which includes [`UiMaterial`] types).
|
||||||
@ -321,7 +317,7 @@ impl<P: PhaseItem, M: UiMaterial> RenderCommand<P> for DrawUiMaterialNode<M> {
|
|||||||
|
|
||||||
pub struct ExtractedUiMaterialNode<M: UiMaterial> {
|
pub struct ExtractedUiMaterialNode<M: UiMaterial> {
|
||||||
pub stack_index: u32,
|
pub stack_index: u32,
|
||||||
pub transform: Mat4,
|
pub transform: Affine2,
|
||||||
pub rect: Rect,
|
pub rect: Rect,
|
||||||
pub border: BorderRect,
|
pub border: BorderRect,
|
||||||
pub border_radius: ResolvedBorderRadius,
|
pub border_radius: ResolvedBorderRadius,
|
||||||
@ -356,7 +352,7 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
|
|||||||
Query<(
|
Query<(
|
||||||
Entity,
|
Entity,
|
||||||
&ComputedNode,
|
&ComputedNode,
|
||||||
&GlobalTransform,
|
&UiGlobalTransform,
|
||||||
&MaterialNode<M>,
|
&MaterialNode<M>,
|
||||||
&InheritedVisibility,
|
&InheritedVisibility,
|
||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
@ -387,7 +383,7 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
|
|||||||
extracted_uinodes.uinodes.push(ExtractedUiMaterialNode {
|
extracted_uinodes.uinodes.push(ExtractedUiMaterialNode {
|
||||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||||
stack_index: computed_node.stack_index,
|
stack_index: computed_node.stack_index,
|
||||||
transform: transform.compute_matrix(),
|
transform: transform.into(),
|
||||||
material: handle.id(),
|
material: handle.id(),
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
min: Vec2::ZERO,
|
min: Vec2::ZERO,
|
||||||
@ -459,10 +455,13 @@ pub fn prepare_uimaterial_nodes<M: UiMaterial>(
|
|||||||
|
|
||||||
let uinode_rect = extracted_uinode.rect;
|
let uinode_rect = extracted_uinode.rect;
|
||||||
|
|
||||||
let rect_size = uinode_rect.size().extend(1.0);
|
let rect_size = uinode_rect.size();
|
||||||
|
|
||||||
let positions = QUAD_VERTEX_POSITIONS.map(|pos| {
|
let positions = QUAD_VERTEX_POSITIONS.map(|pos| {
|
||||||
(extracted_uinode.transform * (pos * rect_size).extend(1.0)).xyz()
|
extracted_uinode
|
||||||
|
.transform
|
||||||
|
.transform_point2(pos * rect_size)
|
||||||
|
.extend(1.0)
|
||||||
});
|
});
|
||||||
|
|
||||||
let positions_diff = if let Some(clip) = extracted_uinode.clip {
|
let positions_diff = if let Some(clip) = extracted_uinode.clip {
|
||||||
@ -496,7 +495,7 @@ pub fn prepare_uimaterial_nodes<M: UiMaterial>(
|
|||||||
];
|
];
|
||||||
|
|
||||||
let transformed_rect_size =
|
let transformed_rect_size =
|
||||||
extracted_uinode.transform.transform_vector3(rect_size);
|
extracted_uinode.transform.transform_vector2(rect_size);
|
||||||
|
|
||||||
// Don't try to cull nodes that have a rotation
|
// Don't try to cull nodes that have a rotation
|
||||||
// In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
|
// In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use core::{hash::Hash, ops::Range};
|
use core::{hash::Hash, ops::Range};
|
||||||
|
|
||||||
|
use crate::prelude::UiGlobalTransform;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use bevy_asset::*;
|
use bevy_asset::*;
|
||||||
use bevy_color::{Alpha, ColorToComponents, LinearRgba};
|
use bevy_color::{Alpha, ColorToComponents, LinearRgba};
|
||||||
@ -11,7 +12,7 @@ use bevy_ecs::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
use bevy_image::prelude::*;
|
use bevy_image::prelude::*;
|
||||||
use bevy_math::{FloatOrd, Mat4, Rect, Vec2, Vec4Swizzles};
|
use bevy_math::{Affine2, FloatOrd, Rect, Vec2};
|
||||||
use bevy_platform::collections::HashMap;
|
use bevy_platform::collections::HashMap;
|
||||||
use bevy_render::sync_world::MainEntity;
|
use bevy_render::sync_world::MainEntity;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
@ -25,7 +26,6 @@ use bevy_render::{
|
|||||||
Extract, ExtractSchedule, Render, RenderSystems,
|
Extract, ExtractSchedule, Render, RenderSystems,
|
||||||
};
|
};
|
||||||
use bevy_sprite::{SliceScaleMode, SpriteAssetEvents, SpriteImageMode, TextureSlicer};
|
use bevy_sprite::{SliceScaleMode, SpriteAssetEvents, SpriteImageMode, TextureSlicer};
|
||||||
use bevy_transform::prelude::GlobalTransform;
|
|
||||||
use binding_types::{sampler, texture_2d};
|
use binding_types::{sampler, texture_2d};
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use widget::ImageNode;
|
use widget::ImageNode;
|
||||||
@ -218,7 +218,7 @@ impl SpecializedRenderPipeline for UiTextureSlicePipeline {
|
|||||||
|
|
||||||
pub struct ExtractedUiTextureSlice {
|
pub struct ExtractedUiTextureSlice {
|
||||||
pub stack_index: u32,
|
pub stack_index: u32,
|
||||||
pub transform: Mat4,
|
pub transform: Affine2,
|
||||||
pub rect: Rect,
|
pub rect: Rect,
|
||||||
pub atlas_rect: Option<Rect>,
|
pub atlas_rect: Option<Rect>,
|
||||||
pub image: AssetId<Image>,
|
pub image: AssetId<Image>,
|
||||||
@ -246,7 +246,7 @@ pub fn extract_ui_texture_slices(
|
|||||||
Query<(
|
Query<(
|
||||||
Entity,
|
Entity,
|
||||||
&ComputedNode,
|
&ComputedNode,
|
||||||
&GlobalTransform,
|
&UiGlobalTransform,
|
||||||
&InheritedVisibility,
|
&InheritedVisibility,
|
||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
&ComputedNodeTarget,
|
&ComputedNodeTarget,
|
||||||
@ -306,7 +306,7 @@ pub fn extract_ui_texture_slices(
|
|||||||
extracted_ui_slicers.slices.push(ExtractedUiTextureSlice {
|
extracted_ui_slicers.slices.push(ExtractedUiTextureSlice {
|
||||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||||
stack_index: uinode.stack_index,
|
stack_index: uinode.stack_index,
|
||||||
transform: transform.compute_matrix(),
|
transform: transform.into(),
|
||||||
color: image.color.into(),
|
color: image.color.into(),
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
min: Vec2::ZERO,
|
min: Vec2::ZERO,
|
||||||
@ -497,11 +497,12 @@ pub fn prepare_ui_slices(
|
|||||||
|
|
||||||
let uinode_rect = texture_slices.rect;
|
let uinode_rect = texture_slices.rect;
|
||||||
|
|
||||||
let rect_size = uinode_rect.size().extend(1.0);
|
let rect_size = uinode_rect.size();
|
||||||
|
|
||||||
// Specify the corners of the node
|
// Specify the corners of the node
|
||||||
let positions = QUAD_VERTEX_POSITIONS
|
let positions = QUAD_VERTEX_POSITIONS.map(|pos| {
|
||||||
.map(|pos| (texture_slices.transform * (pos * rect_size).extend(1.)).xyz());
|
(texture_slices.transform.transform_point2(pos * rect_size)).extend(0.)
|
||||||
|
});
|
||||||
|
|
||||||
// Calculate the effect of clipping
|
// Calculate the effect of clipping
|
||||||
// Note: this won't work with rotation/scaling, but that's much more complex (may need more that 2 quads)
|
// Note: this won't work with rotation/scaling, but that's much more complex (may need more that 2 quads)
|
||||||
@ -536,7 +537,7 @@ pub fn prepare_ui_slices(
|
|||||||
];
|
];
|
||||||
|
|
||||||
let transformed_rect_size =
|
let transformed_rect_size =
|
||||||
texture_slices.transform.transform_vector3(rect_size);
|
texture_slices.transform.transform_vector2(rect_size);
|
||||||
|
|
||||||
// Don't try to cull nodes that have a rotation
|
// Don't try to cull nodes that have a rotation
|
||||||
// In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
|
// In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
use crate::{FocusPolicy, UiRect, Val};
|
use crate::{
|
||||||
|
ui_transform::{UiGlobalTransform, UiTransform},
|
||||||
|
FocusPolicy, UiRect, Val,
|
||||||
|
};
|
||||||
use bevy_color::{Alpha, Color};
|
use bevy_color::{Alpha, Color};
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::{prelude::*, system::SystemParam};
|
use bevy_ecs::{prelude::*, system::SystemParam};
|
||||||
@ -9,7 +12,6 @@ use bevy_render::{
|
|||||||
view::Visibility,
|
view::Visibility,
|
||||||
};
|
};
|
||||||
use bevy_sprite::BorderRect;
|
use bevy_sprite::BorderRect;
|
||||||
use bevy_transform::components::Transform;
|
|
||||||
use bevy_utils::once;
|
use bevy_utils::once;
|
||||||
use bevy_window::{PrimaryWindow, WindowRef};
|
use bevy_window::{PrimaryWindow, WindowRef};
|
||||||
use core::{f32, num::NonZero};
|
use core::{f32, num::NonZero};
|
||||||
@ -229,6 +231,73 @@ impl ComputedNode {
|
|||||||
pub const fn inverse_scale_factor(&self) -> f32 {
|
pub const fn inverse_scale_factor(&self) -> f32 {
|
||||||
self.inverse_scale_factor
|
self.inverse_scale_factor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true if `point` within the node.
|
||||||
|
//
|
||||||
|
// Matches the sdf function in `ui.wgsl` that is used by the UI renderer to draw rounded rectangles.
|
||||||
|
pub fn contains_point(&self, transform: UiGlobalTransform, point: Vec2) -> bool {
|
||||||
|
let Some(local_point) = transform
|
||||||
|
.try_inverse()
|
||||||
|
.map(|transform| transform.transform_point2(point))
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let [top, bottom] = if local_point.x < 0. {
|
||||||
|
[self.border_radius.top_left, self.border_radius.bottom_left]
|
||||||
|
} else {
|
||||||
|
[
|
||||||
|
self.border_radius.top_right,
|
||||||
|
self.border_radius.bottom_right,
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let r = if local_point.y < 0. { top } else { bottom };
|
||||||
|
let corner_to_point = local_point.abs() - 0.5 * self.size;
|
||||||
|
let q = corner_to_point + r;
|
||||||
|
let l = q.max(Vec2::ZERO).length();
|
||||||
|
let m = q.max_element().min(0.);
|
||||||
|
l + m - r < 0.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transform a point to normalized node space with the center of the node at the origin and the corners at [+/-0.5, +/-0.5]
|
||||||
|
pub fn normalize_point(&self, transform: UiGlobalTransform, point: Vec2) -> Option<Vec2> {
|
||||||
|
self.size
|
||||||
|
.cmpgt(Vec2::ZERO)
|
||||||
|
.all()
|
||||||
|
.then(|| transform.try_inverse())
|
||||||
|
.flatten()
|
||||||
|
.map(|transform| transform.transform_point2(point) / self.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve the node's clipping rect in local space
|
||||||
|
pub fn resolve_clip_rect(
|
||||||
|
&self,
|
||||||
|
overflow: Overflow,
|
||||||
|
overflow_clip_margin: OverflowClipMargin,
|
||||||
|
) -> Rect {
|
||||||
|
let mut clip_rect = Rect::from_center_size(Vec2::ZERO, self.size);
|
||||||
|
|
||||||
|
let clip_inset = match overflow_clip_margin.visual_box {
|
||||||
|
OverflowClipBox::BorderBox => BorderRect::ZERO,
|
||||||
|
OverflowClipBox::ContentBox => self.content_inset(),
|
||||||
|
OverflowClipBox::PaddingBox => self.border(),
|
||||||
|
};
|
||||||
|
|
||||||
|
clip_rect.min.x += clip_inset.left;
|
||||||
|
clip_rect.min.y += clip_inset.top;
|
||||||
|
clip_rect.max.x -= clip_inset.right;
|
||||||
|
clip_rect.max.y -= clip_inset.bottom;
|
||||||
|
|
||||||
|
if overflow.x == OverflowAxis::Visible {
|
||||||
|
clip_rect.min.x = -f32::INFINITY;
|
||||||
|
clip_rect.max.x = f32::INFINITY;
|
||||||
|
}
|
||||||
|
if overflow.y == OverflowAxis::Visible {
|
||||||
|
clip_rect.min.y = -f32::INFINITY;
|
||||||
|
clip_rect.max.y = f32::INFINITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
clip_rect
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComputedNode {
|
impl ComputedNode {
|
||||||
@ -323,12 +392,12 @@ impl From<Vec2> for ScrollPosition {
|
|||||||
#[require(
|
#[require(
|
||||||
ComputedNode,
|
ComputedNode,
|
||||||
ComputedNodeTarget,
|
ComputedNodeTarget,
|
||||||
|
UiTransform,
|
||||||
BackgroundColor,
|
BackgroundColor,
|
||||||
BorderColor,
|
BorderColor,
|
||||||
BorderRadius,
|
BorderRadius,
|
||||||
FocusPolicy,
|
FocusPolicy,
|
||||||
ScrollPosition,
|
ScrollPosition,
|
||||||
Transform,
|
|
||||||
Visibility,
|
Visibility,
|
||||||
ZIndex
|
ZIndex
|
||||||
)]
|
)]
|
||||||
|
|||||||
191
crates/bevy_ui/src/ui_transform.rs
Normal file
191
crates/bevy_ui/src/ui_transform.rs
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
use crate::Val;
|
||||||
|
use bevy_derive::Deref;
|
||||||
|
use bevy_ecs::component::Component;
|
||||||
|
use bevy_ecs::prelude::ReflectComponent;
|
||||||
|
use bevy_math::Affine2;
|
||||||
|
use bevy_math::Rot2;
|
||||||
|
use bevy_math::Vec2;
|
||||||
|
use bevy_reflect::prelude::*;
|
||||||
|
|
||||||
|
/// A pair of [`Val`]s used to represent a 2-dimensional size or offset.
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy, Reflect)]
|
||||||
|
#[reflect(Default, PartialEq, Debug, Clone)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serialize",
|
||||||
|
derive(serde::Serialize, serde::Deserialize),
|
||||||
|
reflect(Serialize, Deserialize)
|
||||||
|
)]
|
||||||
|
pub struct Val2 {
|
||||||
|
/// Translate the node along the x-axis.
|
||||||
|
/// `Val::Percent` values are resolved based on the computed width of the Ui Node.
|
||||||
|
/// `Val::Auto` is resolved to `0.`.
|
||||||
|
pub x: Val,
|
||||||
|
/// Translate the node along the y-axis.
|
||||||
|
/// `Val::Percent` values are resolved based on the computed height of the UI Node.
|
||||||
|
/// `Val::Auto` is resolved to `0.`.
|
||||||
|
pub y: Val,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Val2 {
|
||||||
|
pub const ZERO: Self = Self {
|
||||||
|
x: Val::ZERO,
|
||||||
|
y: Val::ZERO,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Creates a new [`Val2`] where both components are in logical pixels
|
||||||
|
pub const fn px(x: f32, y: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
x: Val::Px(x),
|
||||||
|
y: Val::Px(y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`Val2`] where both components are percentage values
|
||||||
|
pub const fn percent(x: f32, y: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
x: Val::Percent(x),
|
||||||
|
y: Val::Percent(y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`Val2`]
|
||||||
|
pub const fn new(x: Val, y: Val) -> Self {
|
||||||
|
Self { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves this [`Val2`] from the given `scale_factor`, `parent_size`,
|
||||||
|
/// and `viewport_size`.
|
||||||
|
///
|
||||||
|
/// Component values of [`Val::Auto`] are resolved to 0.
|
||||||
|
pub fn resolve(&self, scale_factor: f32, base_size: Vec2, viewport_size: Vec2) -> Vec2 {
|
||||||
|
Vec2::new(
|
||||||
|
self.x
|
||||||
|
.resolve(scale_factor, base_size.x, viewport_size)
|
||||||
|
.unwrap_or(0.),
|
||||||
|
self.y
|
||||||
|
.resolve(scale_factor, base_size.y, viewport_size)
|
||||||
|
.unwrap_or(0.),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Val2 {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::ZERO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Relative 2D transform for UI nodes
|
||||||
|
///
|
||||||
|
/// [`UiGlobalTransform`] is automatically inserted whenever [`UiTransform`] is inserted.
|
||||||
|
#[derive(Component, Debug, PartialEq, Clone, Copy, Reflect)]
|
||||||
|
#[reflect(Component, Default, PartialEq, Debug, Clone)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serialize",
|
||||||
|
derive(serde::Serialize, serde::Deserialize),
|
||||||
|
reflect(Serialize, Deserialize)
|
||||||
|
)]
|
||||||
|
#[require(UiGlobalTransform)]
|
||||||
|
pub struct UiTransform {
|
||||||
|
/// Translate the node.
|
||||||
|
pub translation: Val2,
|
||||||
|
/// Scale the node. A negative value reflects the node in that axis.
|
||||||
|
pub scale: Vec2,
|
||||||
|
/// Rotate the node clockwise.
|
||||||
|
pub rotation: Rot2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiTransform {
|
||||||
|
pub const IDENTITY: Self = Self {
|
||||||
|
translation: Val2::ZERO,
|
||||||
|
scale: Vec2::ONE,
|
||||||
|
rotation: Rot2::IDENTITY,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Creates a UI transform representing a rotation.
|
||||||
|
pub fn from_rotation(rotation: Rot2) -> Self {
|
||||||
|
Self {
|
||||||
|
rotation,
|
||||||
|
..Self::IDENTITY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a UI transform representing a responsive translation.
|
||||||
|
pub fn from_translation(translation: Val2) -> Self {
|
||||||
|
Self {
|
||||||
|
translation,
|
||||||
|
..Self::IDENTITY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a UI transform representing a scaling.
|
||||||
|
pub fn from_scale(scale: Vec2) -> Self {
|
||||||
|
Self {
|
||||||
|
scale,
|
||||||
|
..Self::IDENTITY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves the translation from the given `scale_factor`, `base_value`, and `target_size`
|
||||||
|
/// and returns a 2d affine transform from the resolved translation, and the `UiTransform`'s rotation, and scale.
|
||||||
|
pub fn compute_affine(&self, scale_factor: f32, base_size: Vec2, target_size: Vec2) -> Affine2 {
|
||||||
|
Affine2::from_scale_angle_translation(
|
||||||
|
self.scale,
|
||||||
|
self.rotation.as_radians(),
|
||||||
|
self.translation
|
||||||
|
.resolve(scale_factor, base_size, target_size),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for UiTransform {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::IDENTITY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Absolute 2D transform for UI nodes
|
||||||
|
///
|
||||||
|
/// [`UiGlobalTransform`]s are updated from [`UiTransform`] and [`Node`](crate::ui_node::Node)
|
||||||
|
/// in [`ui_layout_system`](crate::layout::ui_layout_system)
|
||||||
|
#[derive(Component, Debug, PartialEq, Clone, Copy, Reflect, Deref)]
|
||||||
|
#[reflect(Component, Default, PartialEq, Debug, Clone)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serialize",
|
||||||
|
derive(serde::Serialize, serde::Deserialize),
|
||||||
|
reflect(Serialize, Deserialize)
|
||||||
|
)]
|
||||||
|
pub struct UiGlobalTransform(Affine2);
|
||||||
|
|
||||||
|
impl Default for UiGlobalTransform {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(Affine2::IDENTITY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiGlobalTransform {
|
||||||
|
/// If the transform is invertible returns its inverse.
|
||||||
|
/// Otherwise returns `None`.
|
||||||
|
#[inline]
|
||||||
|
pub fn try_inverse(&self) -> Option<Affine2> {
|
||||||
|
(self.matrix2.determinant() != 0.).then_some(self.inverse())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Affine2> for UiGlobalTransform {
|
||||||
|
fn from(value: Affine2) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<UiGlobalTransform> for Affine2 {
|
||||||
|
fn from(value: UiGlobalTransform) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&UiGlobalTransform> for Affine2 {
|
||||||
|
fn from(value: &UiGlobalTransform) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
experimental::{UiChildren, UiRootNodes},
|
experimental::{UiChildren, UiRootNodes},
|
||||||
|
ui_transform::UiGlobalTransform,
|
||||||
CalculatedClip, ComputedNodeTarget, DefaultUiCamera, Display, Node, OverflowAxis, UiScale,
|
CalculatedClip, ComputedNodeTarget, DefaultUiCamera, Display, Node, OverflowAxis, UiScale,
|
||||||
UiTargetCamera,
|
UiTargetCamera,
|
||||||
};
|
};
|
||||||
@ -17,7 +18,6 @@ use bevy_ecs::{
|
|||||||
use bevy_math::{Rect, UVec2};
|
use bevy_math::{Rect, UVec2};
|
||||||
use bevy_render::camera::Camera;
|
use bevy_render::camera::Camera;
|
||||||
use bevy_sprite::BorderRect;
|
use bevy_sprite::BorderRect;
|
||||||
use bevy_transform::components::GlobalTransform;
|
|
||||||
|
|
||||||
/// Updates clipping for all nodes
|
/// Updates clipping for all nodes
|
||||||
pub fn update_clipping_system(
|
pub fn update_clipping_system(
|
||||||
@ -26,7 +26,7 @@ pub fn update_clipping_system(
|
|||||||
mut node_query: Query<(
|
mut node_query: Query<(
|
||||||
&Node,
|
&Node,
|
||||||
&ComputedNode,
|
&ComputedNode,
|
||||||
&GlobalTransform,
|
&UiGlobalTransform,
|
||||||
Option<&mut CalculatedClip>,
|
Option<&mut CalculatedClip>,
|
||||||
)>,
|
)>,
|
||||||
ui_children: UiChildren,
|
ui_children: UiChildren,
|
||||||
@ -48,14 +48,13 @@ fn update_clipping(
|
|||||||
node_query: &mut Query<(
|
node_query: &mut Query<(
|
||||||
&Node,
|
&Node,
|
||||||
&ComputedNode,
|
&ComputedNode,
|
||||||
&GlobalTransform,
|
&UiGlobalTransform,
|
||||||
Option<&mut CalculatedClip>,
|
Option<&mut CalculatedClip>,
|
||||||
)>,
|
)>,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
mut maybe_inherited_clip: Option<Rect>,
|
mut maybe_inherited_clip: Option<Rect>,
|
||||||
) {
|
) {
|
||||||
let Ok((node, computed_node, global_transform, maybe_calculated_clip)) =
|
let Ok((node, computed_node, transform, maybe_calculated_clip)) = node_query.get_mut(entity)
|
||||||
node_query.get_mut(entity)
|
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -91,10 +90,7 @@ fn update_clipping(
|
|||||||
maybe_inherited_clip
|
maybe_inherited_clip
|
||||||
} else {
|
} else {
|
||||||
// Find the current node's clipping rect and intersect it with the inherited clipping rect, if one exists
|
// Find the current node's clipping rect and intersect it with the inherited clipping rect, if one exists
|
||||||
let mut clip_rect = Rect::from_center_size(
|
let mut clip_rect = Rect::from_center_size(transform.translation, computed_node.size());
|
||||||
global_transform.translation().truncate(),
|
|
||||||
computed_node.size(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Content isn't clipped at the edges of the node but at the edges of the region specified by [`Node::overflow_clip_margin`].
|
// Content isn't clipped at the edges of the node but at the edges of the region specified by [`Node::overflow_clip_margin`].
|
||||||
//
|
//
|
||||||
|
|||||||
@ -171,11 +171,6 @@ pub fn update_viewport_render_target_size(
|
|||||||
height: u32::max(1, size.y as u32),
|
height: u32::max(1, size.y as u32),
|
||||||
..default()
|
..default()
|
||||||
};
|
};
|
||||||
let image = images.get_mut(image_handle).unwrap();
|
images.get_mut(image_handle).unwrap().resize(size);
|
||||||
if image.data.is_some() {
|
|
||||||
image.resize(size);
|
|
||||||
} else {
|
|
||||||
image.texture_descriptor.size = size;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -194,7 +194,7 @@ fn update_cursors(
|
|||||||
fn on_remove_cursor_icon(trigger: Trigger<OnRemove, CursorIcon>, mut commands: Commands) {
|
fn on_remove_cursor_icon(trigger: Trigger<OnRemove, CursorIcon>, mut commands: Commands) {
|
||||||
// Use `try_insert` to avoid panic if the window is being destroyed.
|
// Use `try_insert` to avoid panic if the window is being destroyed.
|
||||||
commands
|
commands
|
||||||
.entity(trigger.target())
|
.entity(trigger.target().unwrap())
|
||||||
.try_insert(PendingCursor(Some(CursorSource::System(
|
.try_insert(PendingCursor(Some(CursorSource::System(
|
||||||
convert_system_cursor_icon(SystemCursorIcon::Default),
|
convert_system_cursor_icon(SystemCursorIcon::Default),
|
||||||
))));
|
))));
|
||||||
|
|||||||
@ -65,12 +65,12 @@ fn change_material(
|
|||||||
mut asset_materials: ResMut<Assets<StandardMaterial>>,
|
mut asset_materials: ResMut<Assets<StandardMaterial>>,
|
||||||
) {
|
) {
|
||||||
// Get the `ColorOverride` of the entity, if it does not have a color override, skip
|
// Get the `ColorOverride` of the entity, if it does not have a color override, skip
|
||||||
let Ok(color_override) = color_override.get(trigger.target()) else {
|
let Ok(color_override) = color_override.get(trigger.target().unwrap()) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Iterate over all children recursively
|
// Iterate over all children recursively
|
||||||
for descendants in children.iter_descendants(trigger.target()) {
|
for descendants in children.iter_descendants(trigger.target().unwrap()) {
|
||||||
// Get the material of the descendant
|
// Get the material of the descendant
|
||||||
if let Some(material) = mesh_materials
|
if let Some(material) = mesh_materials
|
||||||
.get(descendants)
|
.get(descendants)
|
||||||
|
|||||||
@ -571,6 +571,7 @@ Example | Description
|
|||||||
[UI Texture Atlas Slice](../examples/ui/ui_texture_atlas_slice.rs) | Illustrates how to use 9 Slicing for TextureAtlases in UI
|
[UI Texture Atlas Slice](../examples/ui/ui_texture_atlas_slice.rs) | Illustrates how to use 9 Slicing for TextureAtlases in UI
|
||||||
[UI Texture Slice](../examples/ui/ui_texture_slice.rs) | Illustrates how to use 9 Slicing in UI
|
[UI Texture Slice](../examples/ui/ui_texture_slice.rs) | Illustrates how to use 9 Slicing in UI
|
||||||
[UI Texture Slice Flipping and Tiling](../examples/ui/ui_texture_slice_flip_and_tile.rs) | Illustrates how to flip and tile images with 9 Slicing in UI
|
[UI Texture Slice Flipping and Tiling](../examples/ui/ui_texture_slice_flip_and_tile.rs) | Illustrates how to flip and tile images with 9 Slicing in UI
|
||||||
|
[UI Transform](../examples/ui/ui_transform.rs) | An example demonstrating how to translate, rotate and scale UI elements.
|
||||||
[UI Z-Index](../examples/ui/z_index.rs) | Demonstrates how to control the relative depth (z-position) of UI elements
|
[UI Z-Index](../examples/ui/z_index.rs) | Demonstrates how to control the relative depth (z-position) of UI elements
|
||||||
[Viewport Debug](../examples/ui/viewport_debug.rs) | An example for debugging viewport coordinates
|
[Viewport Debug](../examples/ui/viewport_debug.rs) | An example for debugging viewport coordinates
|
||||||
[Viewport Node](../examples/ui/viewport_node.rs) | Demonstrates how to create a viewport node with picking support
|
[Viewport Node](../examples/ui/viewport_node.rs) | Demonstrates how to create a viewport node with picking support
|
||||||
|
|||||||
@ -70,12 +70,12 @@ fn play_animation_when_ready(
|
|||||||
) {
|
) {
|
||||||
// The entity we spawned in `setup_mesh_and_animation` is the trigger's target.
|
// The entity we spawned in `setup_mesh_and_animation` is the trigger's target.
|
||||||
// Start by finding the AnimationToPlay component we added to that entity.
|
// Start by finding the AnimationToPlay component we added to that entity.
|
||||||
if let Ok(animation_to_play) = animations_to_play.get(trigger.target()) {
|
if let Ok(animation_to_play) = animations_to_play.get(trigger.target().unwrap()) {
|
||||||
// The SceneRoot component will have spawned the scene as a hierarchy
|
// The SceneRoot component will have spawned the scene as a hierarchy
|
||||||
// of entities parented to our entity. Since the asset contained a skinned
|
// of entities parented to our entity. Since the asset contained a skinned
|
||||||
// mesh and animations, it will also have spawned an animation player
|
// mesh and animations, it will also have spawned an animation player
|
||||||
// component. Search our entity's descendants to find the animation player.
|
// component. Search our entity's descendants to find the animation player.
|
||||||
for child in children.iter_descendants(trigger.target()) {
|
for child in children.iter_descendants(trigger.target().unwrap()) {
|
||||||
if let Ok(mut player) = players.get_mut(child) {
|
if let Ok(mut player) = players.get_mut(child) {
|
||||||
// Tell the animation player to start the animation and keep
|
// Tell the animation player to start the animation and keep
|
||||||
// repeating it.
|
// repeating it.
|
||||||
|
|||||||
@ -47,7 +47,10 @@ fn observe_on_step(
|
|||||||
transforms: Query<&GlobalTransform>,
|
transforms: Query<&GlobalTransform>,
|
||||||
mut seeded_rng: ResMut<SeededRng>,
|
mut seeded_rng: ResMut<SeededRng>,
|
||||||
) {
|
) {
|
||||||
let translation = transforms.get(trigger.target()).unwrap().translation();
|
let translation = transforms
|
||||||
|
.get(trigger.target().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.translation();
|
||||||
// Spawn a bunch of particles.
|
// Spawn a bunch of particles.
|
||||||
for _ in 0..14 {
|
for _ in 0..14 {
|
||||||
let horizontal = seeded_rng.0.r#gen::<Dir2>() * seeded_rng.0.gen_range(8.0..12.0);
|
let horizontal = seeded_rng.0.r#gen::<Dir2>() * seeded_rng.0.gen_range(8.0..12.0);
|
||||||
|
|||||||
@ -40,7 +40,7 @@ fn disable_entities_on_click(
|
|||||||
valid_query: Query<&DisableOnClick>,
|
valid_query: Query<&DisableOnClick>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
) {
|
) {
|
||||||
let clicked_entity = trigger.target();
|
let clicked_entity = trigger.target().unwrap();
|
||||||
// Windows and text are entities and can be clicked!
|
// Windows and text are entities and can be clicked!
|
||||||
// We definitely don't want to disable the window itself,
|
// We definitely don't want to disable the window itself,
|
||||||
// because that would cause the app to close!
|
// because that would cause the app to close!
|
||||||
|
|||||||
@ -78,14 +78,14 @@ fn attack_armor(entities: Query<Entity, With<Armor>>, mut commands: Commands) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn attack_hits(trigger: Trigger<Attack>, name: Query<&Name>) {
|
fn attack_hits(trigger: Trigger<Attack>, name: Query<&Name>) {
|
||||||
if let Ok(name) = name.get(trigger.target()) {
|
if let Ok(name) = name.get(trigger.target().unwrap()) {
|
||||||
info!("Attack hit {}", name);
|
info!("Attack hit {}", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A callback placed on [`Armor`], checking if it absorbed all the [`Attack`] damage.
|
/// A callback placed on [`Armor`], checking if it absorbed all the [`Attack`] damage.
|
||||||
fn block_attack(mut trigger: Trigger<Attack>, armor: Query<(&Armor, &Name)>) {
|
fn block_attack(mut trigger: Trigger<Attack>, armor: Query<(&Armor, &Name)>) {
|
||||||
let (armor, name) = armor.get(trigger.target()).unwrap();
|
let (armor, name) = armor.get(trigger.target().unwrap()).unwrap();
|
||||||
let attack = trigger.event_mut();
|
let attack = trigger.event_mut();
|
||||||
let damage = attack.damage.saturating_sub(**armor);
|
let damage = attack.damage.saturating_sub(**armor);
|
||||||
if damage > 0 {
|
if damage > 0 {
|
||||||
@ -110,14 +110,14 @@ fn take_damage(
|
|||||||
mut app_exit: EventWriter<AppExit>,
|
mut app_exit: EventWriter<AppExit>,
|
||||||
) {
|
) {
|
||||||
let attack = trigger.event();
|
let attack = trigger.event();
|
||||||
let (mut hp, name) = hp.get_mut(trigger.target()).unwrap();
|
let (mut hp, name) = hp.get_mut(trigger.target().unwrap()).unwrap();
|
||||||
**hp = hp.saturating_sub(attack.damage);
|
**hp = hp.saturating_sub(attack.damage);
|
||||||
|
|
||||||
if **hp > 0 {
|
if **hp > 0 {
|
||||||
info!("{} has {:.1} HP", name, hp.0);
|
info!("{} has {:.1} HP", name, hp.0);
|
||||||
} else {
|
} else {
|
||||||
warn!("💀 {} has died a gruesome death", name);
|
warn!("💀 {} has died a gruesome death", name);
|
||||||
commands.entity(trigger.target()).despawn();
|
commands.entity(trigger.target().unwrap()).despawn();
|
||||||
app_exit.write(AppExit::Success);
|
app_exit.write(AppExit::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -117,12 +117,16 @@ fn on_add_mine(
|
|||||||
query: Query<&Mine>,
|
query: Query<&Mine>,
|
||||||
mut index: ResMut<SpatialIndex>,
|
mut index: ResMut<SpatialIndex>,
|
||||||
) {
|
) {
|
||||||
let mine = query.get(trigger.target()).unwrap();
|
let mine = query.get(trigger.target().unwrap()).unwrap();
|
||||||
let tile = (
|
let tile = (
|
||||||
(mine.pos.x / CELL_SIZE).floor() as i32,
|
(mine.pos.x / CELL_SIZE).floor() as i32,
|
||||||
(mine.pos.y / CELL_SIZE).floor() as i32,
|
(mine.pos.y / CELL_SIZE).floor() as i32,
|
||||||
);
|
);
|
||||||
index.map.entry(tile).or_default().insert(trigger.target());
|
index
|
||||||
|
.map
|
||||||
|
.entry(tile)
|
||||||
|
.or_default()
|
||||||
|
.insert(trigger.target().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove despawned mines from our index
|
// Remove despawned mines from our index
|
||||||
@ -131,19 +135,19 @@ fn on_remove_mine(
|
|||||||
query: Query<&Mine>,
|
query: Query<&Mine>,
|
||||||
mut index: ResMut<SpatialIndex>,
|
mut index: ResMut<SpatialIndex>,
|
||||||
) {
|
) {
|
||||||
let mine = query.get(trigger.target()).unwrap();
|
let mine = query.get(trigger.target().unwrap()).unwrap();
|
||||||
let tile = (
|
let tile = (
|
||||||
(mine.pos.x / CELL_SIZE).floor() as i32,
|
(mine.pos.x / CELL_SIZE).floor() as i32,
|
||||||
(mine.pos.y / CELL_SIZE).floor() as i32,
|
(mine.pos.y / CELL_SIZE).floor() as i32,
|
||||||
);
|
);
|
||||||
index.map.entry(tile).and_modify(|set| {
|
index.map.entry(tile).and_modify(|set| {
|
||||||
set.remove(&trigger.target());
|
set.remove(&trigger.target().unwrap());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn explode_mine(trigger: Trigger<Explode>, query: Query<&Mine>, mut commands: Commands) {
|
fn explode_mine(trigger: Trigger<Explode>, query: Query<&Mine>, mut commands: Commands) {
|
||||||
// If a triggered event is targeting a specific entity you can access it with `.target()`
|
// If a triggered event is targeting a specific entity you can access it with `.target()`
|
||||||
let id = trigger.target();
|
let id = trigger.target().unwrap();
|
||||||
let Ok(mut entity) = commands.get_entity(id) else {
|
let Ok(mut entity) = commands.get_entity(id) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -50,7 +50,7 @@ fn remove_component(
|
|||||||
|
|
||||||
fn react_on_removal(trigger: Trigger<OnRemove, MyComponent>, mut query: Query<&mut Sprite>) {
|
fn react_on_removal(trigger: Trigger<OnRemove, MyComponent>, mut query: Query<&mut Sprite>) {
|
||||||
// The `OnRemove` trigger was automatically called on the `Entity` that had its `MyComponent` removed.
|
// The `OnRemove` trigger was automatically called on the `Entity` that had its `MyComponent` removed.
|
||||||
let entity = trigger.target();
|
let entity = trigger.target().unwrap();
|
||||||
if let Ok(mut sprite) = query.get_mut(entity) {
|
if let Ok(mut sprite) = query.get_mut(entity) {
|
||||||
sprite.color = Color::srgb(0.5, 1., 1.);
|
sprite.color = Color::srgb(0.5, 1., 1.);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -127,7 +127,10 @@ fn tick_timers(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn unwrap<B: Bundle>(trigger: Trigger<Unwrap>, world: &mut World) {
|
fn unwrap<B: Bundle>(trigger: Trigger<Unwrap>, world: &mut World) {
|
||||||
if let Ok(mut target) = world.get_entity_mut(trigger.target()) {
|
if let Some(mut target) = trigger
|
||||||
|
.target()
|
||||||
|
.and_then(|target| world.get_entity_mut(target).ok())
|
||||||
|
{
|
||||||
if let Some(DelayedComponent(bundle)) = target.take::<DelayedComponent<B>>() {
|
if let Some(DelayedComponent(bundle)) = target.take::<DelayedComponent<B>>() {
|
||||||
target.insert(bundle);
|
target.insert(bundle);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,13 +48,13 @@ fn setup_scene(
|
|||||||
.observe(on_click_spawn_cube)
|
.observe(on_click_spawn_cube)
|
||||||
.observe(
|
.observe(
|
||||||
|out: Trigger<Pointer<Out>>, mut texts: Query<&mut TextColor>| {
|
|out: Trigger<Pointer<Out>>, mut texts: Query<&mut TextColor>| {
|
||||||
let mut text_color = texts.get_mut(out.target()).unwrap();
|
let mut text_color = texts.get_mut(out.target().unwrap()).unwrap();
|
||||||
text_color.0 = Color::WHITE;
|
text_color.0 = Color::WHITE;
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.observe(
|
.observe(
|
||||||
|over: Trigger<Pointer<Over>>, mut texts: Query<&mut TextColor>| {
|
|over: Trigger<Pointer<Over>>, mut texts: Query<&mut TextColor>| {
|
||||||
let mut color = texts.get_mut(over.target()).unwrap();
|
let mut color = texts.get_mut(over.target().unwrap()).unwrap();
|
||||||
color.0 = bevy::color::palettes::tailwind::CYAN_400.into();
|
color.0 = bevy::color::palettes::tailwind::CYAN_400.into();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -102,7 +102,7 @@ fn on_click_spawn_cube(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_drag_rotate(drag: Trigger<Pointer<Drag>>, mut transforms: Query<&mut Transform>) {
|
fn on_drag_rotate(drag: Trigger<Pointer<Drag>>, mut transforms: Query<&mut Transform>) {
|
||||||
if let Ok(mut transform) = transforms.get_mut(drag.target()) {
|
if let Ok(mut transform) = transforms.get_mut(drag.target().unwrap()) {
|
||||||
transform.rotate_y(drag.delta.x * 0.02);
|
transform.rotate_y(drag.delta.x * 0.02);
|
||||||
transform.rotate_x(drag.delta.y * 0.02);
|
transform.rotate_x(drag.delta.y * 0.02);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -164,7 +164,7 @@ fn update_material_on<E>(
|
|||||||
// versions of this observer, each triggered by a different event and with a different hardcoded
|
// versions of this observer, each triggered by a different event and with a different hardcoded
|
||||||
// material. Instead, the event type is a generic, and the material is passed in.
|
// material. Instead, the event type is a generic, and the material is passed in.
|
||||||
move |trigger, mut query| {
|
move |trigger, mut query| {
|
||||||
if let Ok(mut material) = query.get_mut(trigger.target()) {
|
if let Ok(mut material) = query.get_mut(trigger.target().unwrap()) {
|
||||||
material.0 = new_material.clone();
|
material.0 = new_material.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,7 +191,7 @@ fn rotate(mut query: Query<&mut Transform, With<Shape>>, time: Res<Time>) {
|
|||||||
|
|
||||||
/// An observer to rotate an entity when it is dragged
|
/// An observer to rotate an entity when it is dragged
|
||||||
fn rotate_on_drag(drag: Trigger<Pointer<Drag>>, mut transforms: Query<&mut Transform>) {
|
fn rotate_on_drag(drag: Trigger<Pointer<Drag>>, mut transforms: Query<&mut Transform>) {
|
||||||
let mut transform = transforms.get_mut(drag.target()).unwrap();
|
let mut transform = transforms.get_mut(drag.target().unwrap()).unwrap();
|
||||||
transform.rotate_y(drag.delta.x * 0.02);
|
transform.rotate_y(drag.delta.x * 0.02);
|
||||||
transform.rotate_x(drag.delta.y * 0.02);
|
transform.rotate_x(drag.delta.y * 0.02);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,13 +27,13 @@ fn setup_scene(
|
|||||||
.observe(on_click_spawn_cube)
|
.observe(on_click_spawn_cube)
|
||||||
.observe(
|
.observe(
|
||||||
|out: Trigger<Pointer<Out>>, mut texts: Query<&mut TextColor>| {
|
|out: Trigger<Pointer<Out>>, mut texts: Query<&mut TextColor>| {
|
||||||
let mut text_color = texts.get_mut(out.target()).unwrap();
|
let mut text_color = texts.get_mut(out.target().unwrap()).unwrap();
|
||||||
text_color.0 = Color::WHITE;
|
text_color.0 = Color::WHITE;
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.observe(
|
.observe(
|
||||||
|over: Trigger<Pointer<Over>>, mut texts: Query<&mut TextColor>| {
|
|over: Trigger<Pointer<Over>>, mut texts: Query<&mut TextColor>| {
|
||||||
let mut color = texts.get_mut(over.target()).unwrap();
|
let mut color = texts.get_mut(over.target().unwrap()).unwrap();
|
||||||
color.0 = bevy::color::palettes::tailwind::CYAN_400.into();
|
color.0 = bevy::color::palettes::tailwind::CYAN_400.into();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -80,7 +80,7 @@ fn on_click_spawn_cube(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_drag_rotate(drag: Trigger<Pointer<Drag>>, mut transforms: Query<&mut Transform>) {
|
fn on_drag_rotate(drag: Trigger<Pointer<Drag>>, mut transforms: Query<&mut Transform>) {
|
||||||
if let Ok(mut transform) = transforms.get_mut(drag.target()) {
|
if let Ok(mut transform) = transforms.get_mut(drag.target().unwrap()) {
|
||||||
transform.rotate_y(drag.delta.x * 0.02);
|
transform.rotate_y(drag.delta.x * 0.02);
|
||||||
transform.rotate_x(drag.delta.y * 0.02);
|
transform.rotate_x(drag.delta.y * 0.02);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -152,7 +152,7 @@ fn setup_atlas(
|
|||||||
// An observer listener that changes the target entity's color.
|
// An observer listener that changes the target entity's color.
|
||||||
fn recolor_on<E: Debug + Clone + Reflect>(color: Color) -> impl Fn(Trigger<E>, Query<&mut Sprite>) {
|
fn recolor_on<E: Debug + Clone + Reflect>(color: Color) -> impl Fn(Trigger<E>, Query<&mut Sprite>) {
|
||||||
move |ev, mut sprites| {
|
move |ev, mut sprites| {
|
||||||
let Ok(mut sprite) = sprites.get_mut(ev.target()) else {
|
let Ok(mut sprite) = sprites.get_mut(ev.target().unwrap()) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
sprite.color = color;
|
sprite.color = color;
|
||||||
|
|||||||
@ -281,7 +281,7 @@ mod animation {
|
|||||||
animation: Res<Animation>,
|
animation: Res<Animation>,
|
||||||
mut players: Query<(Entity, &mut AnimationPlayer)>,
|
mut players: Query<(Entity, &mut AnimationPlayer)>,
|
||||||
) {
|
) {
|
||||||
for child in children.iter_descendants(trigger.target()) {
|
for child in children.iter_descendants(trigger.target().unwrap()) {
|
||||||
if let Ok((entity, mut player)) = players.get_mut(child) {
|
if let Ok((entity, mut player)) = players.get_mut(child) {
|
||||||
let mut transitions = AnimationTransitions::new();
|
let mut transitions = AnimationTransitions::new();
|
||||||
transitions
|
transitions
|
||||||
|
|||||||
@ -228,7 +228,13 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||||||
parent.spawn((
|
parent.spawn((
|
||||||
ImageNode::new(asset_server.load("branding/bevy_logo_light.png")),
|
ImageNode::new(asset_server.load("branding/bevy_logo_light.png")),
|
||||||
// Uses the transform to rotate the logo image by 45 degrees
|
// Uses the transform to rotate the logo image by 45 degrees
|
||||||
Transform::from_rotation(Quat::from_rotation_z(0.25 * PI)),
|
Node {
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
UiTransform {
|
||||||
|
rotation: Rot2::radians(0.25 * PI),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
BorderRadius::all(Val::Px(10.)),
|
BorderRadius::all(Val::Px(10.)),
|
||||||
Outline {
|
Outline {
|
||||||
width: Val::Px(2.),
|
width: Val::Px(2.),
|
||||||
|
|||||||
@ -70,7 +70,7 @@ fn universal_button_click_behavior(
|
|||||||
mut trigger: Trigger<Pointer<Click>>,
|
mut trigger: Trigger<Pointer<Click>>,
|
||||||
mut button_query: Query<(&mut BackgroundColor, &mut ResetTimer)>,
|
mut button_query: Query<(&mut BackgroundColor, &mut ResetTimer)>,
|
||||||
) {
|
) {
|
||||||
let button_entity = trigger.target();
|
let button_entity = trigger.target().unwrap();
|
||||||
if let Ok((mut color, mut reset_timer)) = button_query.get_mut(button_entity) {
|
if let Ok((mut color, mut reset_timer)) = button_query.get_mut(button_entity) {
|
||||||
// This would be a great place to play a little sound effect too!
|
// This would be a great place to play a little sound effect too!
|
||||||
color.0 = PRESSED_BUTTON.into();
|
color.0 = PRESSED_BUTTON.into();
|
||||||
|
|||||||
@ -26,20 +26,20 @@ fn setup(mut commands: Commands) {
|
|||||||
commands
|
commands
|
||||||
.spawn(Node {
|
.spawn(Node {
|
||||||
flex_direction: FlexDirection::Column,
|
flex_direction: FlexDirection::Column,
|
||||||
row_gap: Val::Px(30.),
|
row_gap: Val::Px(20.),
|
||||||
margin: UiRect::all(Val::Px(30.)),
|
margin: UiRect::all(Val::Px(20.)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.with_children(|commands| {
|
.with_children(|commands| {
|
||||||
for (b, stops) in [
|
for (b, stops) in [
|
||||||
(
|
(
|
||||||
5.,
|
4.,
|
||||||
vec![
|
vec![
|
||||||
ColorStop::new(Color::WHITE, Val::Percent(15.)),
|
ColorStop::new(Color::WHITE, Val::Percent(15.)),
|
||||||
ColorStop::new(Color::BLACK, Val::Percent(85.)),
|
ColorStop::new(Color::BLACK, Val::Percent(85.)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
(5., vec![RED.into(), BLUE.into(), LIME.into()]),
|
(4., vec![RED.into(), BLUE.into(), LIME.into()]),
|
||||||
(
|
(
|
||||||
0.,
|
0.,
|
||||||
vec![
|
vec![
|
||||||
@ -64,11 +64,11 @@ fn setup(mut commands: Commands) {
|
|||||||
commands
|
commands
|
||||||
.spawn(Node {
|
.spawn(Node {
|
||||||
flex_direction: FlexDirection::Column,
|
flex_direction: FlexDirection::Column,
|
||||||
row_gap: Val::Px(10.),
|
row_gap: Val::Px(5.),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.with_children(|commands| {
|
.with_children(|commands| {
|
||||||
for (w, h) in [(100., 100.), (50., 100.), (100., 50.)] {
|
for (w, h) in [(70., 70.), (35., 70.), (70., 35.)] {
|
||||||
commands
|
commands
|
||||||
.spawn(Node {
|
.spawn(Node {
|
||||||
column_gap: Val::Px(10.),
|
column_gap: Val::Px(10.),
|
||||||
@ -108,7 +108,7 @@ fn setup(mut commands: Commands) {
|
|||||||
aspect_ratio: Some(1.),
|
aspect_ratio: Some(1.),
|
||||||
height: Val::Percent(100.),
|
height: Val::Percent(100.),
|
||||||
border: UiRect::all(Val::Px(b)),
|
border: UiRect::all(Val::Px(b)),
|
||||||
margin: UiRect::left(Val::Px(30.)),
|
margin: UiRect::left(Val::Px(20.)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
BorderRadius::all(Val::Px(20.)),
|
BorderRadius::all(Val::Px(20.)),
|
||||||
@ -128,7 +128,7 @@ fn setup(mut commands: Commands) {
|
|||||||
aspect_ratio: Some(1.),
|
aspect_ratio: Some(1.),
|
||||||
height: Val::Percent(100.),
|
height: Val::Percent(100.),
|
||||||
border: UiRect::all(Val::Px(b)),
|
border: UiRect::all(Val::Px(b)),
|
||||||
margin: UiRect::left(Val::Px(30.)),
|
margin: UiRect::left(Val::Px(20.)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
BorderRadius::all(Val::Px(20.)),
|
BorderRadius::all(Val::Px(20.)),
|
||||||
@ -148,7 +148,7 @@ fn setup(mut commands: Commands) {
|
|||||||
aspect_ratio: Some(1.),
|
aspect_ratio: Some(1.),
|
||||||
height: Val::Percent(100.),
|
height: Val::Percent(100.),
|
||||||
border: UiRect::all(Val::Px(b)),
|
border: UiRect::all(Val::Px(b)),
|
||||||
margin: UiRect::left(Val::Px(30.)),
|
margin: UiRect::left(Val::Px(20.)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
BorderRadius::all(Val::Px(20.)),
|
BorderRadius::all(Val::Px(20.)),
|
||||||
|
|||||||
@ -4,7 +4,6 @@ use bevy::{input::common_conditions::input_just_pressed, prelude::*, ui::widget:
|
|||||||
use std::f32::consts::{FRAC_PI_2, PI, TAU};
|
use std::f32::consts::{FRAC_PI_2, PI, TAU};
|
||||||
|
|
||||||
const CONTAINER_SIZE: f32 = 150.0;
|
const CONTAINER_SIZE: f32 = 150.0;
|
||||||
const HALF_CONTAINER_SIZE: f32 = CONTAINER_SIZE / 2.0;
|
|
||||||
const LOOP_LENGTH: f32 = 4.0;
|
const LOOP_LENGTH: f32 = 4.0;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -41,16 +40,16 @@ struct AnimationState {
|
|||||||
struct Container(u8);
|
struct Container(u8);
|
||||||
|
|
||||||
trait UpdateTransform {
|
trait UpdateTransform {
|
||||||
fn update(&self, t: f32, transform: &mut Transform);
|
fn update(&self, t: f32, transform: &mut UiTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct Move;
|
struct Move;
|
||||||
|
|
||||||
impl UpdateTransform for Move {
|
impl UpdateTransform for Move {
|
||||||
fn update(&self, t: f32, transform: &mut Transform) {
|
fn update(&self, t: f32, transform: &mut UiTransform) {
|
||||||
transform.translation.x = ops::sin(t * TAU - FRAC_PI_2) * HALF_CONTAINER_SIZE;
|
transform.translation.x = Val::Percent(ops::sin(t * TAU - FRAC_PI_2) * 50.);
|
||||||
transform.translation.y = -ops::cos(t * TAU - FRAC_PI_2) * HALF_CONTAINER_SIZE;
|
transform.translation.y = Val::Percent(-ops::cos(t * TAU - FRAC_PI_2) * 50.);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +57,7 @@ impl UpdateTransform for Move {
|
|||||||
struct Scale;
|
struct Scale;
|
||||||
|
|
||||||
impl UpdateTransform for Scale {
|
impl UpdateTransform for Scale {
|
||||||
fn update(&self, t: f32, transform: &mut Transform) {
|
fn update(&self, t: f32, transform: &mut UiTransform) {
|
||||||
transform.scale.x = 1.0 + 0.5 * ops::cos(t * TAU).max(0.0);
|
transform.scale.x = 1.0 + 0.5 * ops::cos(t * TAU).max(0.0);
|
||||||
transform.scale.y = 1.0 + 0.5 * ops::cos(t * TAU + PI).max(0.0);
|
transform.scale.y = 1.0 + 0.5 * ops::cos(t * TAU + PI).max(0.0);
|
||||||
}
|
}
|
||||||
@ -68,9 +67,8 @@ impl UpdateTransform for Scale {
|
|||||||
struct Rotate;
|
struct Rotate;
|
||||||
|
|
||||||
impl UpdateTransform for Rotate {
|
impl UpdateTransform for Rotate {
|
||||||
fn update(&self, t: f32, transform: &mut Transform) {
|
fn update(&self, t: f32, transform: &mut UiTransform) {
|
||||||
transform.rotation =
|
transform.rotation = Rot2::radians(ops::cos(t * TAU) * 45.0);
|
||||||
Quat::from_axis_angle(Vec3::Z, (ops::cos(t * TAU) * 45.0).to_radians());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,10 +173,6 @@ fn spawn_container(
|
|||||||
update_transform: impl UpdateTransform + Component,
|
update_transform: impl UpdateTransform + Component,
|
||||||
spawn_children: impl FnOnce(&mut ChildSpawnerCommands),
|
spawn_children: impl FnOnce(&mut ChildSpawnerCommands),
|
||||||
) {
|
) {
|
||||||
let mut transform = Transform::default();
|
|
||||||
|
|
||||||
update_transform.update(0.0, &mut transform);
|
|
||||||
|
|
||||||
parent
|
parent
|
||||||
.spawn((
|
.spawn((
|
||||||
Node {
|
Node {
|
||||||
@ -198,11 +192,8 @@ fn spawn_container(
|
|||||||
Node {
|
Node {
|
||||||
align_items: AlignItems::Center,
|
align_items: AlignItems::Center,
|
||||||
justify_content: JustifyContent::Center,
|
justify_content: JustifyContent::Center,
|
||||||
top: Val::Px(transform.translation.x),
|
|
||||||
left: Val::Px(transform.translation.y),
|
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
transform,
|
|
||||||
update_transform,
|
update_transform,
|
||||||
))
|
))
|
||||||
.with_children(spawn_children);
|
.with_children(spawn_children);
|
||||||
@ -233,13 +224,10 @@ fn update_animation(
|
|||||||
|
|
||||||
fn update_transform<T: UpdateTransform + Component>(
|
fn update_transform<T: UpdateTransform + Component>(
|
||||||
animation: Res<AnimationState>,
|
animation: Res<AnimationState>,
|
||||||
mut containers: Query<(&mut Transform, &mut Node, &ComputedNode, &T)>,
|
mut containers: Query<(&mut UiTransform, &T)>,
|
||||||
) {
|
) {
|
||||||
for (mut transform, mut node, computed_node, update_transform) in &mut containers {
|
for (mut transform, update_transform) in &mut containers {
|
||||||
update_transform.update(animation.t, &mut transform);
|
update_transform.update(animation.t, &mut transform);
|
||||||
|
|
||||||
node.left = Val::Px(transform.translation.x * computed_node.inverse_scale_factor());
|
|
||||||
node.top = Val::Px(transform.translation.y * computed_node.inverse_scale_factor());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -78,7 +78,7 @@ fn relative_cursor_position_system(
|
|||||||
"unknown".to_string()
|
"unknown".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
text_color.0 = if relative_cursor_position.mouse_over() {
|
text_color.0 = if relative_cursor_position.cursor_over() {
|
||||||
Color::srgb(0.1, 0.9, 0.1)
|
Color::srgb(0.1, 0.9, 0.1)
|
||||||
} else {
|
} else {
|
||||||
Color::srgb(0.9, 0.1, 0.1)
|
Color::srgb(0.9, 0.1, 0.1)
|
||||||
|
|||||||
@ -93,7 +93,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||||||
mut commands: Commands
|
mut commands: Commands
|
||||||
| {
|
| {
|
||||||
if trigger.event().button == PointerButton::Primary {
|
if trigger.event().button == PointerButton::Primary {
|
||||||
commands.entity(trigger.target()).despawn();
|
commands.entity(trigger.target().unwrap()).despawn();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -142,7 +142,7 @@ fn setup(mut commands: Commands) {
|
|||||||
.observe(
|
.observe(
|
||||||
|mut trigger: Trigger<Pointer<Click>>,
|
|mut trigger: Trigger<Pointer<Click>>,
|
||||||
mut focus: ResMut<InputFocus>| {
|
mut focus: ResMut<InputFocus>| {
|
||||||
focus.0 = Some(trigger.target());
|
focus.0 = Some(trigger.target().unwrap());
|
||||||
trigger.propagate(false);
|
trigger.propagate(false);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
302
examples/ui/ui_transform.rs
Normal file
302
examples/ui/ui_transform.rs
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
//! An example demonstrating how to translate, rotate and scale UI elements.
|
||||||
|
use bevy::color::palettes::css::DARK_GRAY;
|
||||||
|
use bevy::color::palettes::css::RED;
|
||||||
|
use bevy::color::palettes::css::YELLOW;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use core::f32::consts::FRAC_PI_8;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.add_systems(Update, button_system)
|
||||||
|
.add_systems(Update, translation_system)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
const NORMAL_BUTTON: Color = Color::WHITE;
|
||||||
|
const HOVERED_BUTTON: Color = Color::Srgba(YELLOW);
|
||||||
|
const PRESSED_BUTTON: Color = Color::Srgba(RED);
|
||||||
|
|
||||||
|
/// A button that rotates the target node
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct RotateButton(pub Rot2);
|
||||||
|
|
||||||
|
/// A button that scales the target node
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct ScaleButton(pub f32);
|
||||||
|
|
||||||
|
/// Marker component so the systems know which entities to translate, rotate and scale
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct TargetNode;
|
||||||
|
|
||||||
|
/// Handles button interactions
|
||||||
|
fn button_system(
|
||||||
|
mut interaction_query: Query<
|
||||||
|
(
|
||||||
|
&Interaction,
|
||||||
|
&mut BackgroundColor,
|
||||||
|
Option<&RotateButton>,
|
||||||
|
Option<&ScaleButton>,
|
||||||
|
),
|
||||||
|
(Changed<Interaction>, With<Button>),
|
||||||
|
>,
|
||||||
|
mut rotator_query: Query<&mut UiTransform, With<TargetNode>>,
|
||||||
|
) {
|
||||||
|
for (interaction, mut color, maybe_rotate, maybe_scale) in &mut interaction_query {
|
||||||
|
match *interaction {
|
||||||
|
Interaction::Pressed => {
|
||||||
|
*color = PRESSED_BUTTON.into();
|
||||||
|
if let Some(step) = maybe_rotate {
|
||||||
|
for mut transform in rotator_query.iter_mut() {
|
||||||
|
transform.rotation *= step.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(step) = maybe_scale {
|
||||||
|
for mut transform in rotator_query.iter_mut() {
|
||||||
|
transform.scale += step.0;
|
||||||
|
transform.scale =
|
||||||
|
transform.scale.clamp(Vec2::splat(0.25), Vec2::splat(3.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Interaction::Hovered => {
|
||||||
|
*color = HOVERED_BUTTON.into();
|
||||||
|
}
|
||||||
|
Interaction::None => {
|
||||||
|
*color = NORMAL_BUTTON.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// move the rotating panel when the arrow keys are pressed
|
||||||
|
fn translation_system(
|
||||||
|
time: Res<Time>,
|
||||||
|
input: Res<ButtonInput<KeyCode>>,
|
||||||
|
mut translation_query: Query<&mut UiTransform, With<TargetNode>>,
|
||||||
|
) {
|
||||||
|
let controls = [
|
||||||
|
(KeyCode::ArrowLeft, -Vec2::X),
|
||||||
|
(KeyCode::ArrowRight, Vec2::X),
|
||||||
|
(KeyCode::ArrowUp, -Vec2::Y),
|
||||||
|
(KeyCode::ArrowDown, Vec2::Y),
|
||||||
|
];
|
||||||
|
for &(key_code, direction) in &controls {
|
||||||
|
if input.pressed(key_code) {
|
||||||
|
for mut transform in translation_query.iter_mut() {
|
||||||
|
let d = direction * 50.0 * time.delta_secs();
|
||||||
|
let (Val::Px(x), Val::Px(y)) = (transform.translation.x, transform.translation.y)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let x = (x + d.x).clamp(-150., 150.);
|
||||||
|
let y = (y + d.y).clamp(-150., 150.);
|
||||||
|
|
||||||
|
transform.translation = Val2::px(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
// UI camera
|
||||||
|
commands.spawn(Camera2d);
|
||||||
|
|
||||||
|
// Root node filling the whole screen
|
||||||
|
commands.spawn((
|
||||||
|
Node {
|
||||||
|
width: Val::Percent(100.),
|
||||||
|
height: Val::Percent(100.),
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::BLACK),
|
||||||
|
children![(
|
||||||
|
Node {
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::SpaceEvenly,
|
||||||
|
column_gap: Val::Px(25.0),
|
||||||
|
row_gap: Val::Px(25.0),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::BLACK),
|
||||||
|
children![
|
||||||
|
(
|
||||||
|
Node {
|
||||||
|
flex_direction: FlexDirection::Column,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
row_gap: Val::Px(10.0),
|
||||||
|
column_gap: Val::Px(10.0),
|
||||||
|
padding: UiRect::all(Val::Px(10.0)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::BLACK),
|
||||||
|
GlobalZIndex(1),
|
||||||
|
children![
|
||||||
|
(
|
||||||
|
Button,
|
||||||
|
Node {
|
||||||
|
height: Val::Px(50.0),
|
||||||
|
width: Val::Px(50.0),
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::WHITE),
|
||||||
|
RotateButton(Rot2::radians(-FRAC_PI_8)),
|
||||||
|
children![(Text::new("<--"), TextColor(Color::BLACK),)]
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Button,
|
||||||
|
Node {
|
||||||
|
height: Val::Px(50.0),
|
||||||
|
width: Val::Px(50.0),
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::WHITE),
|
||||||
|
ScaleButton(-0.25),
|
||||||
|
children![(Text::new("-"), TextColor(Color::BLACK),)]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
// Target node with its own set of buttons
|
||||||
|
(
|
||||||
|
Node {
|
||||||
|
flex_direction: FlexDirection::Column,
|
||||||
|
justify_content: JustifyContent::SpaceBetween,
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
width: Val::Px(300.0),
|
||||||
|
height: Val::Px(300.0),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(DARK_GRAY.into()),
|
||||||
|
TargetNode,
|
||||||
|
children![
|
||||||
|
(
|
||||||
|
Button,
|
||||||
|
Node {
|
||||||
|
width: Val::Px(80.0),
|
||||||
|
height: Val::Px(80.0),
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::WHITE),
|
||||||
|
children![(Text::new("Top"), TextColor(Color::BLACK))]
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Node {
|
||||||
|
align_self: AlignSelf::Stretch,
|
||||||
|
justify_content: JustifyContent::SpaceBetween,
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
children![
|
||||||
|
(
|
||||||
|
Button,
|
||||||
|
Node {
|
||||||
|
width: Val::Px(80.0),
|
||||||
|
height: Val::Px(80.0),
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::WHITE),
|
||||||
|
UiTransform::from_rotation(Rot2::radians(
|
||||||
|
-std::f32::consts::FRAC_PI_2
|
||||||
|
)),
|
||||||
|
children![(Text::new("Left"), TextColor(Color::BLACK),)]
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Node {
|
||||||
|
width: Val::Px(100.),
|
||||||
|
height: Val::Px(100.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
ImageNode {
|
||||||
|
image: asset_server.load("branding/icon.png"),
|
||||||
|
image_mode: NodeImageMode::Stretch,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Button,
|
||||||
|
Node {
|
||||||
|
width: Val::Px(80.0),
|
||||||
|
height: Val::Px(80.0),
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
UiTransform::from_rotation(Rot2::radians(
|
||||||
|
core::f32::consts::FRAC_PI_2
|
||||||
|
)),
|
||||||
|
BackgroundColor(Color::WHITE),
|
||||||
|
children![(Text::new("Right"), TextColor(Color::BLACK))]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Button,
|
||||||
|
Node {
|
||||||
|
width: Val::Px(80.0),
|
||||||
|
height: Val::Px(80.0),
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::WHITE),
|
||||||
|
UiTransform::from_rotation(Rot2::radians(std::f32::consts::PI)),
|
||||||
|
children![(Text::new("Bottom"), TextColor(Color::BLACK),)]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
// Right column of controls
|
||||||
|
(
|
||||||
|
Node {
|
||||||
|
flex_direction: FlexDirection::Column,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
row_gap: Val::Px(10.0),
|
||||||
|
column_gap: Val::Px(10.0),
|
||||||
|
padding: UiRect::all(Val::Px(10.0)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::BLACK),
|
||||||
|
GlobalZIndex(1),
|
||||||
|
children![
|
||||||
|
(
|
||||||
|
Button,
|
||||||
|
Node {
|
||||||
|
height: Val::Px(50.0),
|
||||||
|
width: Val::Px(50.0),
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::WHITE),
|
||||||
|
RotateButton(Rot2::radians(FRAC_PI_8)),
|
||||||
|
children![(Text::new("-->"), TextColor(Color::BLACK),)]
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Button,
|
||||||
|
Node {
|
||||||
|
height: Val::Px(50.0),
|
||||||
|
width: Val::Px(50.0),
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::WHITE),
|
||||||
|
ScaleButton(0.25),
|
||||||
|
children![(Text::new("+"), TextColor(Color::BLACK),)]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)],
|
||||||
|
));
|
||||||
|
}
|
||||||
@ -91,7 +91,7 @@ fn test(
|
|||||||
|
|
||||||
fn on_drag_viewport(drag: Trigger<Pointer<Drag>>, mut node_query: Query<&mut Node>) {
|
fn on_drag_viewport(drag: Trigger<Pointer<Drag>>, mut node_query: Query<&mut Node>) {
|
||||||
if matches!(drag.button, PointerButton::Secondary) {
|
if matches!(drag.button, PointerButton::Secondary) {
|
||||||
let mut node = node_query.get_mut(drag.target()).unwrap();
|
let mut node = node_query.get_mut(drag.target().unwrap()).unwrap();
|
||||||
|
|
||||||
if let (Val::Px(top), Val::Px(left)) = (node.top, node.left) {
|
if let (Val::Px(top), Val::Px(left)) = (node.top, node.left) {
|
||||||
node.left = Val::Px(left + drag.delta.x);
|
node.left = Val::Px(left + drag.delta.x);
|
||||||
@ -102,7 +102,7 @@ fn on_drag_viewport(drag: Trigger<Pointer<Drag>>, mut node_query: Query<&mut Nod
|
|||||||
|
|
||||||
fn on_drag_cuboid(drag: Trigger<Pointer<Drag>>, mut transform_query: Query<&mut Transform>) {
|
fn on_drag_cuboid(drag: Trigger<Pointer<Drag>>, mut transform_query: Query<&mut Transform>) {
|
||||||
if matches!(drag.button, PointerButton::Primary) {
|
if matches!(drag.button, PointerButton::Primary) {
|
||||||
let mut transform = transform_query.get_mut(drag.target()).unwrap();
|
let mut transform = transform_query.get_mut(drag.target().unwrap()).unwrap();
|
||||||
transform.rotate_y(drag.delta.x * 0.02);
|
transform.rotate_y(drag.delta.x * 0.02);
|
||||||
transform.rotate_x(drag.delta.y * 0.02);
|
transform.rotate_x(drag.delta.y * 0.02);
|
||||||
}
|
}
|
||||||
|
|||||||
12
release-content/migration-guides/observer_triggers.md
Normal file
12
release-content/migration-guides/observer_triggers.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
title: Observer Triggers
|
||||||
|
pull_requests: [19440]
|
||||||
|
---
|
||||||
|
|
||||||
|
Observers may be triggered on particular entities or globally.
|
||||||
|
Previously, a global trigger would claim to trigger on a particular `Entity`, `Entity::PLACEHOLDER`.
|
||||||
|
For correctness and transparency, triggers have been changed to `Option<Entity>`.
|
||||||
|
|
||||||
|
`Trigger::target` now returns `Option<Entity>` and `ObserverTrigger::target` is now of type `Option<Entity>`.
|
||||||
|
If you were checking for `Entity::PLACEHOLDER`, migrate to handling the `None` case.
|
||||||
|
If you were not checking for `Entity::PLACEHOLDER`, migrate to unwrapping, as `Entity::PLACEHOLDER` would have caused a panic before, at a later point.
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
title: RelativeCursorPosition is object-centered
|
||||||
|
pull_requests: [16615]
|
||||||
|
---
|
||||||
|
|
||||||
|
`RelativeCursorPosition`'s coordinates are now object-centered with (0,0) at the the center of the node and the corners at (±0.5, ±0.5). Its `normalized_visible_node_rect` field has been removed and replaced with a new `cursor_over: bool` field which is set to true when the cursor is hovering a visible section of the UI node.
|
||||||
32
release-content/migration-guides/specialized_ui_transform.md
Normal file
32
release-content/migration-guides/specialized_ui_transform.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
title: Specialized UI transform
|
||||||
|
pull_requests: [16615]
|
||||||
|
---
|
||||||
|
|
||||||
|
Bevy UI now uses specialized 2D UI transform components `UiTransform` and `UiGlobalTransform` in place of `Transform` and `GlobalTransform`.
|
||||||
|
|
||||||
|
UiTransform is a 2d-only equivalent of Transform with a responsive translation in `Val`s. `UiGlobalTransform` newtypes `Affine2` and is updated in `ui_layout_system
|
||||||
|
|
||||||
|
`Node` now requires `UiTransform` instead of `Transform`. `UiTransform` requires `UiGlobalTransform`.
|
||||||
|
|
||||||
|
The `UiTransform` equivalent of the `Transform`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
Transform {
|
||||||
|
translation: Vec3 { x, y, z },
|
||||||
|
rotation:Quat::from_rotation_z(radians),
|
||||||
|
scale,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
is
|
||||||
|
|
||||||
|
```rust
|
||||||
|
UiTransform {
|
||||||
|
translation: Val2::px(x, y),
|
||||||
|
rotation: Rot2::from_rotation(radians),
|
||||||
|
scale: scale.xy(),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In previous versions of Bevy `ui_layout_system` would overwrite UI node's `Transform::translation` each frame. `UiTransform`s aren't overwritten and there is no longer any need for systems that cache and rewrite the transform for translated UI elements.
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
title: Unified system state flag
|
||||||
|
pull_requests: [19506]
|
||||||
|
---
|
||||||
|
|
||||||
|
Now the system have a unified `SystemStateFlags` to represent its different states.
|
||||||
|
|
||||||
|
If your code previously looked like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl System for MyCustomSystem {
|
||||||
|
// ...
|
||||||
|
|
||||||
|
fn is_send(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_exclusive(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_deferred(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ....
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You should migrate it to:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl System for MyCustomSystem{
|
||||||
|
// ...
|
||||||
|
|
||||||
|
fn flags(&self) -> SystemStateFlags {
|
||||||
|
// non-send , exclusive , no deferred
|
||||||
|
SystemStateFlags::NON_SEND | SystemStateFlags::EXCLUSIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: Specialized UI Transform
|
||||||
|
authors: ["@Ickshonpe"]
|
||||||
|
pull_requests: [16615]
|
||||||
|
---
|
||||||
|
|
||||||
|
In Bevy UI `Transform` and `GlobalTransform` have been replaced by `UiTransform` and `UiGlobalTransform`. `UiTransform` is a specialized 2D UI transform which supports responsive translations.
|
||||||
Loading…
Reference in New Issue
Block a user