diff --git a/Cargo.toml b/Cargo.toml index 897e751449..f773ad40ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,7 +116,6 @@ default = [ "bevy_mesh_picking_backend", "bevy_pbr", "bevy_picking", - "bevy_remote", "bevy_render", "bevy_scene", "bevy_sprite", @@ -141,13 +140,22 @@ default = [ ] # Provides an implementation for picking meshes -bevy_mesh_picking_backend = ["bevy_picking"] +bevy_mesh_picking_backend = [ + "bevy_picking", + "bevy_internal/bevy_mesh_picking_backend", +] # Provides an implementation for picking sprites -bevy_sprite_picking_backend = ["bevy_picking"] +bevy_sprite_picking_backend = [ + "bevy_picking", + "bevy_internal/bevy_sprite_picking_backend", +] -# Provides an implementation for picking ui -bevy_ui_picking_backend = ["bevy_picking"] +# Provides an implementation for picking UI +bevy_ui_picking_backend = [ + "bevy_picking", + "bevy_internal/bevy_ui_picking_backend", +] # Force dynamic linking, which improves iterative compile times dynamic_linking = ["dep:bevy_dylib", "bevy_internal/dynamic_linking"] @@ -3647,6 +3655,7 @@ wasm = true name = "client" path = "examples/remote/client.rs" doc-scrape-examples = true +required-features = ["bevy_remote"] [package.metadata.example.client] name = "client" @@ -3658,6 +3667,7 @@ wasm = false name = "server" path = "examples/remote/server.rs" doc-scrape-examples = true +required-features = ["bevy_remote"] [package.metadata.example.server] name = "server" diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 25fa79256d..6375af6d5e 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -14,7 +14,9 @@ bevy_app = { path = "../crates/bevy_app" } bevy_ecs = { path = "../crates/bevy_ecs", features = ["multi_threaded"] } bevy_hierarchy = { path = "../crates/bevy_hierarchy" } bevy_math = { path = "../crates/bevy_math" } -bevy_picking = { path = "../crates/bevy_picking", features = ["bevy_mesh"] } +bevy_picking = { path = "../crates/bevy_picking", features = [ + "bevy_mesh_picking_backend", +] } bevy_reflect = { path = "../crates/bevy_reflect", features = ["functions"] } bevy_render = { path = "../crates/bevy_render" } bevy_tasks = { path = "../crates/bevy_tasks" } diff --git a/crates/bevy_animation/Cargo.toml b/crates/bevy_animation/Cargo.toml index 6fb9fb9f59..a1d019ad1a 100644 --- a/crates/bevy_animation/Cargo.toml +++ b/crates/bevy_animation/Cargo.toml @@ -10,7 +10,6 @@ keywords = ["bevy"] [dependencies] # bevy -bevy_animation_derive = { path = "derive", version = "0.15.0-dev" } bevy_app = { path = "../bevy_app", version = "0.15.0-dev" } bevy_asset = { path = "../bevy_asset", version = "0.15.0-dev" } bevy_color = { path = "../bevy_color", version = "0.15.0-dev" } diff --git a/crates/bevy_animation/derive/Cargo.toml b/crates/bevy_animation/derive/Cargo.toml deleted file mode 100644 index ad44a82b00..0000000000 --- a/crates/bevy_animation/derive/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "bevy_animation_derive" -version = "0.15.0-dev" -edition = "2021" -description = "Derive implementations for bevy_animation" -homepage = "https://bevyengine.org" -repository = "https://github.com/bevyengine/bevy" -license = "MIT OR Apache-2.0" -keywords = ["bevy"] - -[lib] -proc-macro = true - -[dependencies] -bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.15.0-dev" } -proc-macro2 = "1.0" -quote = "1.0" -syn = { version = "2.0", features = ["full"] } - -[lints] -workspace = true - -[package.metadata.docs.rs] -rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] -all-features = true diff --git a/crates/bevy_animation/derive/src/lib.rs b/crates/bevy_animation/derive/src/lib.rs deleted file mode 100644 index 48bce1163e..0000000000 --- a/crates/bevy_animation/derive/src/lib.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Derive macros for `bevy_animation`. - -extern crate proc_macro; - -use bevy_macro_utils::BevyManifest; -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, DeriveInput}; - -/// Used to derive `AnimationEvent` for a type. -#[proc_macro_derive(AnimationEvent)] -pub fn derive_animation_event(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - let name = ast.ident; - let manifest = BevyManifest::default(); - let bevy_animation_path = manifest.get_path("bevy_animation"); - let bevy_ecs_path = manifest.get_path("bevy_ecs"); - let animation_event_path = quote! { #bevy_animation_path::animation_event }; - let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); - // TODO: This could derive Event as well. - quote! { - impl #impl_generics #animation_event_path::AnimationEvent for #name #ty_generics #where_clause { - fn trigger(&self, _time: f32, _weight: f32, entity: #bevy_ecs_path::entity::Entity, world: &mut #bevy_ecs_path::world::World) { - world.entity_mut(entity).trigger(Clone::clone(self)); - } - } - } - .into() -} diff --git a/crates/bevy_animation/src/animation_event.rs b/crates/bevy_animation/src/animation_event.rs deleted file mode 100644 index a2cf2da785..0000000000 --- a/crates/bevy_animation/src/animation_event.rs +++ /dev/null @@ -1,281 +0,0 @@ -//! Traits and types for triggering events from animations. - -use core::{any::Any, fmt::Debug}; - -use bevy_ecs::prelude::*; -use bevy_reflect::{ - prelude::*, utility::NonGenericTypeInfoCell, ApplyError, DynamicTupleStruct, FromType, - GetTypeRegistration, ReflectFromPtr, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, - TupleStructFieldIter, TupleStructInfo, TypeInfo, TypeRegistration, Typed, UnnamedField, -}; - -pub use bevy_animation_derive::AnimationEvent; - -pub(crate) fn trigger_animation_event( - entity: Entity, - time: f32, - weight: f32, - event: Box, -) -> impl Command { - move |world: &mut World| { - event.trigger(time, weight, entity, world); - } -} - -/// An event that can be used with animations. -/// It can be derived to trigger as an observer event, -/// if you need more complex behavior, consider -/// a manual implementation. -/// -/// # Example -/// -/// ```rust -/// # use bevy_animation::prelude::*; -/// # use bevy_ecs::prelude::*; -/// # use bevy_reflect::prelude::*; -/// # use bevy_asset::prelude::*; -/// # -/// #[derive(Event, AnimationEvent, Reflect, Clone)] -/// struct Say(String); -/// -/// fn on_say(trigger: Trigger) { -/// println!("{}", trigger.event().0); -/// } -/// -/// fn setup_animation( -/// mut commands: Commands, -/// mut animations: ResMut>, -/// mut graphs: ResMut>, -/// ) { -/// // Create a new animation and add an event at 1.0s. -/// let mut animation = AnimationClip::default(); -/// animation.add_event(1.0, Say("Hello".into())); -/// -/// // Create an animation graph. -/// let (graph, animation_index) = AnimationGraph::from_clip(animations.add(animation)); -/// -/// // Start playing the animation. -/// let mut player = AnimationPlayer::default(); -/// player.play(animation_index).repeat(); -/// -/// commands.spawn((AnimationGraphHandle(graphs.add(graph)), player)); -/// } -/// # -/// # bevy_ecs::system::assert_is_system(setup_animation); -/// ``` -#[reflect_trait] -pub trait AnimationEvent: CloneableAnimationEvent + Reflect + Send + Sync { - /// Trigger the event, targeting `entity`. - fn trigger(&self, time: f32, weight: f32, entity: Entity, world: &mut World); -} - -/// This trait exist so that manual implementors of [`AnimationEvent`] -/// do not have to implement `clone_value`. -#[diagnostic::on_unimplemented( - message = "`{Self}` does not implement `Clone`", - note = "consider annotating `{Self}` with `#[derive(Clone)]`" -)] -pub trait CloneableAnimationEvent { - /// Clone this value into a new `Box` - fn clone_value(&self) -> Box; -} - -impl CloneableAnimationEvent for T { - fn clone_value(&self) -> Box { - Box::new(self.clone()) - } -} - -/// The data that will be used to trigger an animation event. -#[derive(TypePath)] -pub(crate) struct AnimationEventData(pub(crate) Box); - -impl AnimationEventData { - pub(crate) fn new(event: impl AnimationEvent) -> Self { - Self(Box::new(event)) - } -} - -impl Debug for AnimationEventData { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("AnimationEventData(")?; - PartialReflect::debug(self.0.as_ref(), f)?; - f.write_str(")")?; - Ok(()) - } -} - -impl Clone for AnimationEventData { - fn clone(&self) -> Self { - Self(CloneableAnimationEvent::clone_value(self.0.as_ref())) - } -} - -// We have to implement `GetTypeRegistration` manually because of the embedded -// `Box`, which can't be automatically derived yet. -impl GetTypeRegistration for AnimationEventData { - fn get_type_registration() -> TypeRegistration { - let mut registration = TypeRegistration::of::(); - registration.insert::(FromType::::from_type()); - registration - } -} - -// We have to implement `Typed` manually because of the embedded -// `Box`, which can't be automatically derived yet. -impl Typed for AnimationEventData { - fn type_info() -> &'static TypeInfo { - static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| { - TypeInfo::TupleStruct(TupleStructInfo::new::(&[UnnamedField::new::<()>(0)])) - }) - } -} - -// We have to implement `TupleStruct` manually because of the embedded -// `Box`, which can't be automatically derived yet. -impl TupleStruct for AnimationEventData { - fn field(&self, index: usize) -> Option<&dyn PartialReflect> { - match index { - 0 => Some(self.0.as_partial_reflect()), - _ => None, - } - } - - fn field_mut(&mut self, index: usize) -> Option<&mut dyn PartialReflect> { - match index { - 0 => Some(self.0.as_partial_reflect_mut()), - _ => None, - } - } - - fn field_len(&self) -> usize { - 1 - } - - fn iter_fields(&self) -> TupleStructFieldIter { - TupleStructFieldIter::new(self) - } - - fn clone_dynamic(&self) -> DynamicTupleStruct { - DynamicTupleStruct::from_iter([PartialReflect::clone_value(&*self.0)]) - } -} - -// We have to implement `PartialReflect` manually because of the embedded -// `Box`, which can't be automatically derived yet. -impl PartialReflect for AnimationEventData { - #[inline] - fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { - Some(::type_info()) - } - - #[inline] - fn into_partial_reflect(self: Box) -> Box { - self - } - - #[inline] - fn as_partial_reflect(&self) -> &dyn PartialReflect { - self - } - - #[inline] - fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { - self - } - - fn try_into_reflect(self: Box) -> Result, Box> { - Ok(self) - } - - #[inline] - fn try_as_reflect(&self) -> Option<&dyn Reflect> { - Some(self) - } - - #[inline] - fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { - Some(self) - } - - fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { - if let ReflectRef::TupleStruct(struct_value) = value.reflect_ref() { - for (i, value) in struct_value.iter_fields().enumerate() { - if let Some(v) = self.field_mut(i) { - v.try_apply(value)?; - } - } - } else { - return Err(ApplyError::MismatchedKinds { - from_kind: value.reflect_kind(), - to_kind: ReflectKind::TupleStruct, - }); - } - Ok(()) - } - - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::TupleStruct(self) - } - - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::TupleStruct(self) - } - - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::TupleStruct(self) - } - - fn clone_value(&self) -> Box { - Box::new(Clone::clone(self)) - } -} - -// We have to implement `Reflect` manually because of the embedded -// `Box`, which can't be automatically derived yet. -impl Reflect for AnimationEventData { - #[inline] - fn into_any(self: Box) -> Box { - self - } - - #[inline] - fn as_any(&self) -> &dyn Any { - self - } - - #[inline] - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - #[inline] - fn into_reflect(self: Box) -> Box { - self - } - - #[inline] - fn as_reflect(&self) -> &dyn Reflect { - self - } - - #[inline] - fn as_reflect_mut(&mut self) -> &mut dyn Reflect { - self - } - - #[inline] - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) - } -} - -// We have to implement `FromReflect` manually because of the embedded -// `Box`, which can't be automatically derived yet. -impl FromReflect for AnimationEventData { - fn from_reflect(reflect: &dyn PartialReflect) -> Option { - Some(reflect.try_downcast_ref::()?.clone()) - } -} diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 8d55cb7ea5..9fe1d89f25 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -11,13 +11,11 @@ extern crate alloc; pub mod animatable; pub mod animation_curves; -pub mod animation_event; pub mod gltf_curves; pub mod graph; pub mod transition; mod util; -use animation_event::{trigger_animation_event, AnimationEvent, AnimationEventData}; use core::{ any::{Any, TypeId}, cell::RefCell, @@ -30,7 +28,7 @@ use prelude::AnimationCurveEvaluator; use crate::graph::{AnimationGraphHandle, ThreadedAnimationGraphs}; -use bevy_app::{App, Plugin, PostUpdate}; +use bevy_app::{Animation, App, Plugin, PostUpdate}; use bevy_asset::{Asset, AssetApp, Assets}; use bevy_core::Name; use bevy_ecs::{ @@ -64,12 +62,8 @@ use uuid::Uuid; pub mod prelude { #[doc(hidden)] pub use crate::{ - animatable::*, - animation_curves::*, - animation_event::{AnimationEvent, ReflectAnimationEvent}, - graph::*, - transition::*, - AnimationClip, AnimationPlayer, AnimationPlugin, VariableCurve, + animatable::*, animation_curves::*, graph::*, transition::*, AnimationClip, + AnimationPlayer, AnimationPlugin, VariableCurve, }; } @@ -78,6 +72,7 @@ use crate::{ graph::{AnimationGraph, AnimationGraphAssetLoader, AnimationNodeIndex}, transition::{advance_transitions, expire_completed_transitions, AnimationTransitions}, }; +use alloc::sync::Arc; /// The [UUID namespace] of animation targets (e.g. bones). /// @@ -289,7 +284,35 @@ pub struct AnimationClip { #[derive(Reflect, Debug, Clone)] struct TimedAnimationEvent { time: f32, - event: AnimationEventData, + event: AnimationEvent, +} + +#[derive(Reflect, Debug, Clone)] +struct AnimationEvent { + #[reflect(ignore)] + trigger: AnimationEventFn, +} + +impl AnimationEvent { + fn trigger(&self, commands: &mut Commands, entity: Entity, time: f32, weight: f32) { + (self.trigger.0)(commands, entity, time, weight); + } +} + +#[derive(Reflect, Clone)] +#[reflect(opaque)] +struct AnimationEventFn(Arc); + +impl Default for AnimationEventFn { + fn default() -> Self { + Self(Arc::new(|_commands, _entity, _time, _weight| {})) + } +} + +impl Debug for AnimationEventFn { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("AnimationEventFn").finish() + } } #[derive(Reflect, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] @@ -472,9 +495,24 @@ impl AnimationClip { .push(variable_curve); } - /// Add an [`AnimationEvent`] to an [`AnimationTarget`] named by an [`AnimationTargetId`]. + /// Add a untargeted [`Event`] to this [`AnimationClip`]. /// - /// The `event` will trigger on the entity matching the target once the `time` (in seconds) + /// The `event` will be cloned and triggered on the [`AnimationPlayer`] entity once the `time` (in seconds) + /// is reached in the animation. + /// + /// See also [`add_event_to_target`](Self::add_event_to_target). + pub fn add_event(&mut self, time: f32, event: impl Event + Clone) { + self.add_event_fn( + time, + move |commands: &mut Commands, entity: Entity, _time: f32, _weight: f32| { + commands.entity(entity).trigger(event.clone()); + }, + ); + } + + /// Add an [`Event`] to an [`AnimationTarget`] named by an [`AnimationTargetId`]. + /// + /// The `event` will be cloned and triggered on the entity matching the target once the `time` (in seconds) /// is reached in the animation. /// /// Use [`add_event`](Self::add_event) instead if you don't have a specific target. @@ -482,26 +520,69 @@ impl AnimationClip { &mut self, target_id: AnimationTargetId, time: f32, - event: impl AnimationEvent, + event: impl Event + Clone, ) { - self.add_event_to_target_inner(AnimationEventTarget::Node(target_id), time, event); + self.add_event_fn_to_target( + target_id, + time, + move |commands: &mut Commands, entity: Entity, _time: f32, _weight: f32| { + commands.entity(entity).trigger(event.clone()); + }, + ); } - /// Add a untargeted [`AnimationEvent`] to this [`AnimationClip`]. + /// Add a untargeted event function to this [`AnimationClip`]. /// - /// The `event` will trigger on the [`AnimationPlayer`] entity once the `time` (in seconds) + /// The `func` will trigger on the [`AnimationPlayer`] entity once the `time` (in seconds) /// is reached in the animation. /// + /// For a simpler [`Event`]-based alternative, see [`AnimationClip::add_event`]. /// See also [`add_event_to_target`](Self::add_event_to_target). - pub fn add_event(&mut self, time: f32, event: impl AnimationEvent) { - self.add_event_to_target_inner(AnimationEventTarget::Root, time, event); + /// + /// ``` + /// # use bevy_animation::AnimationClip; + /// # let mut clip = AnimationClip::default(); + /// clip.add_event_fn(1.0, |commands, entity, time, weight| { + /// println!("Animation Event Triggered {entity:#?} at time {time} with weight {weight}"); + /// }) + /// ``` + pub fn add_event_fn( + &mut self, + time: f32, + func: impl Fn(&mut Commands, Entity, f32, f32) + Send + Sync + 'static, + ) { + self.add_event_internal(AnimationEventTarget::Root, time, func); } - fn add_event_to_target_inner( + /// Add an event function to an [`AnimationTarget`] named by an [`AnimationTargetId`]. + /// + /// The `func` will trigger on the entity matching the target once the `time` (in seconds) + /// is reached in the animation. + /// + /// For a simpler [`Event`]-based alternative, see [`AnimationClip::add_event_to_target`]. + /// Use [`add_event`](Self::add_event) instead if you don't have a specific target. + /// + /// ``` + /// # use bevy_animation::{AnimationClip, AnimationTargetId}; + /// # let mut clip = AnimationClip::default(); + /// clip.add_event_fn_to_target(AnimationTargetId::from_iter(["Arm", "Hand"]), 1.0, |commands, entity, time, weight| { + /// println!("Animation Event Triggered {entity:#?} at time {time} with weight {weight}"); + /// }) + /// ``` + pub fn add_event_fn_to_target( + &mut self, + target_id: AnimationTargetId, + time: f32, + func: impl Fn(&mut Commands, Entity, f32, f32) + Send + Sync + 'static, + ) { + self.add_event_internal(AnimationEventTarget::Node(target_id), time, func); + } + + fn add_event_internal( &mut self, target: AnimationEventTarget, time: f32, - event: impl AnimationEvent, + trigger_fn: impl Fn(&mut Commands, Entity, f32, f32) + Send + Sync + 'static, ) { self.duration = self.duration.max(time); let triggers = self.events.entry(target).or_default(); @@ -510,7 +591,9 @@ impl AnimationClip { index, TimedAnimationEvent { time, - event: AnimationEventData::new(event), + event: AnimationEvent { + trigger: AnimationEventFn(Arc::new(trigger_fn)), + }, }, ), } @@ -988,12 +1071,7 @@ fn trigger_untargeted_animation_events( }; for TimedAnimationEvent { time, event } in triggered_events.iter() { - commands.queue(trigger_animation_event( - entity, - *time, - active_animation.weight, - event.clone().0, - )); + event.trigger(&mut commands, entity, *time, active_animation.weight); } } } @@ -1195,12 +1273,12 @@ pub fn animate_targets( for TimedAnimationEvent { time, event } in triggered_events.iter() { - commands.queue(trigger_animation_event( + event.trigger( + &mut commands, entity, *time, active_animation.weight, - event.clone().0, - )); + ); } }); } @@ -1252,10 +1330,6 @@ pub fn animate_targets( }); } -/// Animation system set -#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] -pub struct Animation; - /// Adds animation support to an app #[derive(Default)] pub struct AnimationPlugin; @@ -1573,12 +1647,6 @@ mod tests { #[derive(Event, Reflect, Clone)] struct A; - impl AnimationEvent for A { - fn trigger(&self, _time: f32, _weight: f32, target: Entity, world: &mut World) { - world.entity_mut(target).trigger(self.clone()); - } - } - #[track_caller] fn assert_triggered_events_with( active_animation: &ActiveAnimation, diff --git a/crates/bevy_app/src/main_schedule.rs b/crates/bevy_app/src/main_schedule.rs index f2b5494437..834205cf53 100644 --- a/crates/bevy_app/src/main_schedule.rs +++ b/crates/bevy_app/src/main_schedule.rs @@ -181,6 +181,10 @@ pub struct PostUpdate; #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] pub struct Last; +/// Animation system set. This exists in [`PostUpdate`]. +#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] +pub struct Animation; + /// Defines the schedules to be run for the [`Main`] schedule, including /// their order. #[derive(Resource, Debug)] diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 3934a2221c..30117f7127 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -18,6 +18,7 @@ bevy_debug_stepping = [] serialize = ["dep:serde"] track_change_detection = [] reflect_functions = ["bevy_reflect", "bevy_reflect/functions"] +detailed_trace = [] [dependencies] bevy_ptr = { path = "../bevy_ptr", version = "0.15.0-dev" } diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 7c7df1b90a..b915f8b5ca 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -1029,8 +1029,8 @@ impl Components { /// registration will be used. pub(crate) unsafe fn register_required_components( &mut self, - required: ComponentId, requiree: ComponentId, + required: ComponentId, constructor: fn() -> R, ) -> Result<(), RequiredComponentsError> { // SAFETY: The caller ensures that the `requiree` is valid. @@ -1065,6 +1065,10 @@ impl Components { // Propagate the new required components up the chain to all components that require the requiree. if let Some(required_by) = self.get_required_by(requiree).cloned() { + // `required` is now required by anything that `requiree` was required by. + self.get_required_by_mut(required) + .unwrap() + .extend(required_by.iter().copied()); for &required_by_id in required_by.iter() { // SAFETY: The component is in the list of required components, so it must exist already. let required_components = unsafe { @@ -1072,20 +1076,24 @@ impl Components { .debug_checked_unwrap() }; - // Register the original required component for the requiree. - // The inheritance depth is `1` since this is a component required by the original requiree. - required_components.register_by_id(required, constructor, 1); + // Register the original required component in the "parent" of the requiree. + // The inheritance depth is 1 deeper than the `requiree` wrt `required_by_id`. + let depth = required_components.0.get(&requiree).expect("requiree is required by required_by_id, so its required_components must include requiree").inheritance_depth; + required_components.register_by_id(required, constructor, depth + 1); for (component_id, component) in inherited_requirements.iter() { // Register the required component. - // The inheritance depth is increased by `1` since this is a component required by the original required component. + // The inheritance depth of inherited components is whatever the requiree's + // depth is relative to `required_by_id`, plus the inheritance depth of the + // inherited component relative to the requiree, plus 1 to account for the + // requiree in between. // SAFETY: Component ID and constructor match the ones on the original requiree. // The original requiree is responsible for making sure the registration is safe. unsafe { required_components.register_dynamic( *component_id, component.constructor.clone(), - component.inheritance_depth + 1, + component.inheritance_depth + depth + 1, ); }; } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index f18bde71f7..3a2e24d904 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -2345,6 +2345,94 @@ mod tests { assert!(world.entity(id).get::().is_some()); } + #[test] + fn runtime_required_components_propagate_up_even_more() { + #[derive(Component)] + struct A; + + #[derive(Component, Default)] + struct B; + + #[derive(Component, Default)] + struct C; + + #[derive(Component, Default)] + struct D; + + let mut world = World::new(); + + world.register_required_components::(); + world.register_required_components::(); + world.register_required_components::(); + + let id = world.spawn(A).id(); + + assert!(world.entity(id).get::().is_some()); + } + + #[test] + fn runtime_required_components_deep_require_does_not_override_shallow_require() { + #[derive(Component)] + struct A; + #[derive(Component, Default)] + struct B; + #[derive(Component, Default)] + struct C; + #[derive(Component)] + struct Counter(i32); + #[derive(Component, Default)] + struct D; + + let mut world = World::new(); + + world.register_required_components::(); + world.register_required_components::(); + world.register_required_components::(); + world.register_required_components_with::(|| Counter(2)); + // This should replace the require constructor in A since it is + // shallower. + world.register_required_components_with::(|| Counter(1)); + + let id = world.spawn(A).id(); + + // The "shallower" of the two components is used. + assert_eq!(world.entity(id).get::().unwrap().0, 1); + } + + #[test] + fn runtime_required_components_deep_require_does_not_override_shallow_require_deep_subtree_after_shallow( + ) { + #[derive(Component)] + struct A; + #[derive(Component, Default)] + struct B; + #[derive(Component, Default)] + struct C; + #[derive(Component, Default)] + struct D; + #[derive(Component, Default)] + struct E; + #[derive(Component)] + struct Counter(i32); + #[derive(Component, Default)] + struct F; + + let mut world = World::new(); + + world.register_required_components::(); + world.register_required_components::(); + world.register_required_components::(); + world.register_required_components::(); + world.register_required_components_with::(|| Counter(1)); + world.register_required_components_with::(|| Counter(2)); + world.register_required_components::(); + + let id = world.spawn(A).id(); + + // The "shallower" of the two components is used. + assert_eq!(world.entity(id).get::().unwrap().0, 1); + } + #[test] fn runtime_required_components_existing_archetype() { #[derive(Component)] diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index de3796190f..77f40555c2 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -272,7 +272,8 @@ use smallvec::SmallVec; /// [`ReadOnly`]: Self::ReadOnly #[diagnostic::on_unimplemented( message = "`{Self}` is not valid to request as data in a `Query`", - label = "invalid `Query` data" + label = "invalid `Query` data", + note = "if `{Self}` is a component type, try using `&{Self}` or `&mut {Self}`" )] pub unsafe trait QueryData: WorldQuery { /// The read-only variant of this [`QueryData`], which satisfies the [`ReadOnlyQueryData`] trait. diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index b6a30b14ae..2913833ad5 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -649,7 +649,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// Returns an iterator over the query items generated from an [`Entity`] list. /// /// Items are returned in the order of the list of entities, and may not be unique if the input - /// doesnn't guarantee uniqueness. Entities that don't match the query are skipped. + /// doesn't guarantee uniqueness. Entities that don't match the query are skipped. /// /// # Examples /// diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index eae555a0d5..63a3025eeb 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -515,7 +515,7 @@ impl World { // SAFETY: We just created the `required` and `requiree` components. unsafe { self.components - .register_required_components::(required, requiree, constructor) + .register_required_components::(requiree, required, constructor) } } diff --git a/crates/bevy_hierarchy/src/events.rs b/crates/bevy_hierarchy/src/events.rs index c397488c08..5a667fef77 100644 --- a/crates/bevy_hierarchy/src/events.rs +++ b/crates/bevy_hierarchy/src/events.rs @@ -1,4 +1,5 @@ use bevy_ecs::{event::Event, prelude::Entity}; +#[cfg(feature = "reflect")] use bevy_reflect::Reflect; /// An [`Event`] that is fired whenever there is a change in the world's hierarchy. diff --git a/crates/bevy_image/src/image.rs b/crates/bevy_image/src/image.rs index cfe3a768d6..63ea5c8f1a 100644 --- a/crates/bevy_image/src/image.rs +++ b/crates/bevy_image/src/image.rs @@ -944,19 +944,19 @@ impl Image { let pixel_size = self.texture_descriptor.format.pixel_size(); let pixel_offset = match self.texture_descriptor.dimension { TextureDimension::D3 => { - if coords.x > width || coords.y > height || coords.z > depth { + if coords.x >= width || coords.y >= height || coords.z >= depth { return None; } coords.z * height * width + coords.y * width + coords.x } TextureDimension::D2 => { - if coords.x > width || coords.y > height { + if coords.x >= width || coords.y >= height { return None; } coords.y * width + coords.x } TextureDimension::D1 => { - if coords.x > width { + if coords.x >= width { return None; } coords.x @@ -1573,4 +1573,28 @@ mod test { assert_eq!(UVec2::ONE, image.size()); assert_eq!(Vec2::ONE, image.size_f32()); } + + #[test] + fn on_edge_pixel_is_invalid() { + let image = Image::new_fill( + Extent3d { + width: 5, + height: 10, + depth_or_array_layers: 1, + }, + TextureDimension::D2, + &[0, 0, 0, 255], + TextureFormat::Rgba8Unorm, + RenderAssetUsages::MAIN_WORLD, + ); + assert!(matches!(image.get_color_at(4, 9), Ok(Color::BLACK))); + assert!(matches!( + image.get_color_at(0, 10), + Err(TextureAccessError::OutOfBounds { x: 0, y: 10, z: 0 }) + )); + assert!(matches!( + image.get_color_at(5, 10), + Err(TextureAccessError::OutOfBounds { x: 5, y: 10, z: 0 }) + )); + } } diff --git a/crates/bevy_input/src/button_input.rs b/crates/bevy_input/src/button_input.rs index d329470d8b..c781fafe49 100644 --- a/crates/bevy_input/src/button_input.rs +++ b/crates/bevy_input/src/button_input.rs @@ -210,7 +210,17 @@ where /// Returns `true` if any item in `inputs` has just been released. pub fn any_just_released(&self, inputs: impl IntoIterator) -> bool { - inputs.into_iter().any(|it| self.just_released(it)) + inputs.into_iter().any(|input| self.just_released(input)) + } + + /// Returns `true` if all items in `inputs` have just been released. + pub fn all_just_released(&self, inputs: impl IntoIterator) -> bool { + inputs.into_iter().all(|input| self.just_released(input)) + } + + /// Returns `true` if all items in `inputs` have been just pressed. + pub fn all_just_pressed(&self, inputs: impl IntoIterator) -> bool { + inputs.into_iter().all(|input| self.just_pressed(input)) } /// Clears the `just_released` state of the `input` and returns `true` if the `input` has just been released. diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index a356e525b4..5493103a61 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -307,7 +307,7 @@ pub enum ButtonSettingsError { }, } -/// Stores a connected gamepad's state and any metadata such as the device name. +/// Stores a connected gamepad's metadata such as the name and its [`GamepadButton`] and [`GamepadAxis`]. /// /// An entity with this component is spawned automatically after [`GamepadConnectionEvent`] /// and updated by [`gamepad_event_processing_system`]. @@ -325,11 +325,11 @@ pub enum ButtonSettingsError { /// for (name, gamepad) in &gamepads { /// println!("{name}"); /// -/// if gamepad.digital.just_pressed(GamepadButton::North) { -/// println!("{name} just pressed North") +/// if gamepad.just_pressed(GamepadButton::North) { +/// println!("{} just pressed North", name) /// } /// -/// if let Some(left_stick_x) = gamepad.analog.get(GamepadAxis::LeftStickX) { +/// if let Some(left_stick_x) = gamepad.get(GamepadAxis::LeftStickX) { /// println!("left stick X: {}", left_stick_x) /// } /// } @@ -340,46 +340,175 @@ pub enum ButtonSettingsError { #[require(GamepadSettings)] pub struct Gamepad { /// The USB vendor ID as assigned by the USB-IF, if available. - pub vendor_id: Option, + pub(crate) vendor_id: Option, /// The USB product ID as assigned by the [vendor], if available. /// /// [vendor]: Self::vendor_id - pub product_id: Option, + pub(crate) product_id: Option, /// [`ButtonInput`] of [`GamepadButton`] representing their digital state - pub digital: ButtonInput, + pub(crate) digital: ButtonInput, /// [`Axis`] of [`GamepadButton`] representing their analog state. - pub analog: Axis, + pub(crate) analog: Axis, } impl Gamepad { + /// Returns the USB vendor ID as assigned by the USB-IF, if available. + pub fn vendor_id(&self) -> Option { + self.vendor_id + } + + /// Returns the USB product ID as assigned by the [vendor], if available. + /// + /// [vendor]: Self::vendor_id + pub fn product_id(&self) -> Option { + self.product_id + } + + /// Returns the analog data of the provided [`GamepadAxis`] or [`GamepadButton`]. + /// + /// This will be clamped between [[`Axis::MIN`],[`Axis::MAX`]]. + pub fn get(&self, input: impl Into) -> Option { + self.analog.get(input.into()) + } + + /// Returns the unclamped analog data of the provided [`GamepadAxis`] or [`GamepadButton`]. + /// + /// This value may be outside the [`Axis::MIN`] and [`Axis::MAX`] range. + pub fn get_unclamped(&self, input: impl Into) -> Option { + self.analog.get_unclamped(input.into()) + } + /// Returns the left stick as a [`Vec2`] pub fn left_stick(&self) -> Vec2 { Vec2 { - x: self.analog.get(GamepadAxis::LeftStickX).unwrap_or(0.0), - y: self.analog.get(GamepadAxis::LeftStickY).unwrap_or(0.0), + x: self.get(GamepadAxis::LeftStickX).unwrap_or(0.0), + y: self.get(GamepadAxis::LeftStickY).unwrap_or(0.0), } } /// Returns the right stick as a [`Vec2`] pub fn right_stick(&self) -> Vec2 { Vec2 { - x: self.analog.get(GamepadAxis::RightStickX).unwrap_or(0.0), - y: self.analog.get(GamepadAxis::RightStickY).unwrap_or(0.0), + x: self.get(GamepadAxis::RightStickX).unwrap_or(0.0), + y: self.get(GamepadAxis::RightStickY).unwrap_or(0.0), } } /// Returns the directional pad as a [`Vec2`] pub fn dpad(&self) -> Vec2 { Vec2 { - x: self.analog.get(GamepadButton::DPadRight).unwrap_or(0.0) - - self.analog.get(GamepadButton::DPadLeft).unwrap_or(0.0), - y: self.analog.get(GamepadButton::DPadUp).unwrap_or(0.0) - - self.analog.get(GamepadButton::DPadDown).unwrap_or(0.0), + x: self.get(GamepadButton::DPadRight).unwrap_or(0.0) + - self.get(GamepadButton::DPadLeft).unwrap_or(0.0), + y: self.get(GamepadButton::DPadUp).unwrap_or(0.0) + - self.get(GamepadButton::DPadDown).unwrap_or(0.0), } } + + /// Returns `true` if the [`GamepadButton`] has been pressed. + pub fn pressed(&self, button_type: GamepadButton) -> bool { + self.digital.pressed(button_type) + } + + /// Returns `true` if any item in the [`GamepadButton`] iterator has been pressed. + pub fn any_pressed(&self, button_inputs: impl IntoIterator) -> bool { + self.digital.any_pressed(button_inputs) + } + + /// Returns `true` if all items in the [`GamepadButton`] iterator have been pressed. + pub fn all_pressed(&self, button_inputs: impl IntoIterator) -> bool { + self.digital.all_pressed(button_inputs) + } + + /// Returns `true` if the [`GamepadButton`] has been pressed during the current frame. + /// + /// Note: This function does not imply information regarding the current state of [`ButtonInput::pressed`] or [`ButtonInput::just_released`]. + pub fn just_pressed(&self, button_type: GamepadButton) -> bool { + self.digital.just_pressed(button_type) + } + + /// Returns `true` if any item in the [`GamepadButton`] iterator has been pressed during the current frame. + pub fn any_just_pressed(&self, button_inputs: impl IntoIterator) -> bool { + self.digital.any_just_pressed(button_inputs) + } + + /// Returns `true` if all items in the [`GamepadButton`] iterator have been just pressed. + pub fn all_just_pressed(&self, button_inputs: impl IntoIterator) -> bool { + self.digital.all_just_pressed(button_inputs) + } + + /// Returns `true` if the [`GamepadButton`] has been released during the current frame. + /// + /// Note: This function does not imply information regarding the current state of [`ButtonInput::pressed`] or [`ButtonInput::just_pressed`]. + pub fn just_released(&self, button_type: GamepadButton) -> bool { + self.digital.just_released(button_type) + } + + /// Returns `true` if any item in the [`GamepadButton`] iterator has just been released. + pub fn any_just_released( + &self, + button_inputs: impl IntoIterator, + ) -> bool { + self.digital.any_just_released(button_inputs) + } + + /// Returns `true` if all items in the [`GamepadButton`] iterator have just been released. + pub fn all_just_released( + &self, + button_inputs: impl IntoIterator, + ) -> bool { + self.digital.all_just_released(button_inputs) + } + + /// Returns an iterator over all digital [button]s that are pressed. + /// + /// [button]: GamepadButton + pub fn get_pressed(&self) -> impl Iterator { + self.digital.get_pressed() + } + + /// Returns an iterator over all digital [button]s that were just pressed. + /// + /// [button]: GamepadButton + pub fn get_just_pressed(&self) -> impl Iterator { + self.digital.get_just_pressed() + } + + /// Returns an iterator over all digital [button]s that were just released. + /// + /// [button]: GamepadButton + pub fn get_just_released(&self) -> impl Iterator { + self.digital.get_just_released() + } + + /// Returns an iterator over all analog [axes]. + /// + /// [axes]: GamepadInput + pub fn get_analog_axes(&self) -> impl Iterator { + self.analog.all_axes() + } + + /// [`ButtonInput`] of [`GamepadButton`] representing their digital state + pub fn digital(&self) -> &ButtonInput { + &self.digital + } + + /// Mutable [`ButtonInput`] of [`GamepadButton`] representing their digital state. Useful for mocking inputs. + pub fn digital_mut(&mut self) -> &mut ButtonInput { + &mut self.digital + } + + /// [`Axis`] of [`GamepadButton`] representing their analog state. + pub fn analog(&self) -> &Axis { + &self.analog + } + + /// Mutable [`Axis`] of [`GamepadButton`] representing their analog state. Useful for mocking inputs. + pub fn analog_mut(&mut self) -> &mut Axis { + &mut self.analog + } } impl Default for Gamepad { @@ -1307,7 +1436,7 @@ pub fn gamepad_event_processing_system( }; let Some(filtered_value) = gamepad_settings .get_axis_settings(axis) - .filter(value, gamepad_axis.analog.get(axis)) + .filter(value, gamepad_axis.get(axis)) else { continue; }; @@ -1328,7 +1457,7 @@ pub fn gamepad_event_processing_system( }; let Some(filtered_value) = settings .get_button_axis_settings(button) - .filter(value, gamepad_buttons.analog.get(button)) + .filter(value, gamepad_buttons.get(button)) else { continue; }; @@ -1337,7 +1466,7 @@ pub fn gamepad_event_processing_system( if button_settings.is_released(filtered_value) { // Check if button was previously pressed - if gamepad_buttons.digital.pressed(button) { + if gamepad_buttons.pressed(button) { processed_digital_events.send(GamepadButtonStateChangedEvent::new( gamepad, button, @@ -1349,7 +1478,7 @@ pub fn gamepad_event_processing_system( gamepad_buttons.digital.release(button); } else if button_settings.is_pressed(filtered_value) { // Check if button was previously not pressed - if !gamepad_buttons.digital.pressed(button) { + if !gamepad_buttons.pressed(button) { processed_digital_events.send(GamepadButtonStateChangedEvent::new( gamepad, button, @@ -2403,8 +2532,13 @@ mod tests { assert_eq!(event.button, GamepadButton::DPadDown); assert_eq!(event.state, ButtonState::Pressed); } - let gamepad = ctx.app.world_mut().get::(entity).unwrap(); - assert!(gamepad.digital.pressed(GamepadButton::DPadDown)); + assert!(ctx + .app + .world_mut() + .query::<&Gamepad>() + .get(ctx.app.world(), entity) + .unwrap() + .pressed(GamepadButton::DPadDown)); ctx.app .world_mut() @@ -2419,8 +2553,13 @@ mod tests { .len(), 0 ); - let gamepad = ctx.app.world_mut().get::(entity).unwrap(); - assert!(gamepad.digital.pressed(GamepadButton::DPadDown)); + assert!(ctx + .app + .world_mut() + .query::<&Gamepad>() + .get(ctx.app.world(), entity) + .unwrap() + .pressed(GamepadButton::DPadDown)); } #[test] @@ -2439,13 +2578,23 @@ mod tests { ctx.update(); // Check it is flagged for this frame - let gamepad = ctx.app.world_mut().get::(entity).unwrap(); - assert!(gamepad.digital.just_pressed(GamepadButton::DPadDown)); + assert!(ctx + .app + .world_mut() + .query::<&Gamepad>() + .get(ctx.app.world(), entity) + .unwrap() + .just_pressed(GamepadButton::DPadDown)); ctx.update(); //Check it clears next frame - let gamepad = ctx.app.world_mut().get::(entity).unwrap(); - assert!(!gamepad.digital.just_pressed(GamepadButton::DPadDown)); + assert!(!ctx + .app + .world_mut() + .query::<&Gamepad>() + .get(ctx.app.world(), entity) + .unwrap() + .just_pressed(GamepadButton::DPadDown)); } #[test] fn gamepad_buttons_released() { @@ -2488,8 +2637,13 @@ mod tests { assert_eq!(event.button, GamepadButton::DPadDown); assert_eq!(event.state, ButtonState::Released); } - let gamepad = ctx.app.world_mut().get::(entity).unwrap(); - assert!(!gamepad.digital.pressed(GamepadButton::DPadDown)); + assert!(!ctx + .app + .world_mut() + .query::<&Gamepad>() + .get(ctx.app.world(), entity) + .unwrap() + .pressed(GamepadButton::DPadDown)); ctx.app .world_mut() .resource_mut::>() @@ -2528,13 +2682,23 @@ mod tests { ctx.update(); // Check it is flagged for this frame - let gamepad = ctx.app.world_mut().get::(entity).unwrap(); - assert!(gamepad.digital.just_released(GamepadButton::DPadDown)); + assert!(ctx + .app + .world_mut() + .query::<&Gamepad>() + .get(ctx.app.world(), entity) + .unwrap() + .just_released(GamepadButton::DPadDown)); ctx.update(); - // Check it clears next frame - let gamepad = ctx.app.world_mut().get::(entity).unwrap(); - assert!(!gamepad.digital.just_released(GamepadButton::DPadDown)); + //Check it clears next frame + assert!(!ctx + .app + .world_mut() + .query::<&Gamepad>() + .get(ctx.app.world(), entity) + .unwrap() + .just_released(GamepadButton::DPadDown)); } #[test] diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 9e6cdebacc..fc1d9a3d5d 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -24,7 +24,7 @@ trace = [ trace_chrome = ["bevy_log/tracing-chrome"] trace_tracy = ["bevy_render?/tracing-tracy", "bevy_log/tracing-tracy"] trace_tracy_memory = ["bevy_log/trace_tracy_memory"] -detailed_trace = ["bevy_utils/detailed_trace"] +detailed_trace = ["bevy_ecs/detailed_trace", "bevy_render?/detailed_trace"] sysinfo_plugin = ["bevy_diagnostic/sysinfo_plugin"] @@ -218,13 +218,23 @@ bevy_dev_tools = ["dep:bevy_dev_tools"] bevy_remote = ["dep:bevy_remote"] # Provides picking functionality -bevy_picking = [ - "dep:bevy_picking", - "bevy_picking/bevy_mesh", - "bevy_ui?/bevy_picking", - "bevy_sprite?/bevy_picking", +bevy_picking = ["dep:bevy_picking"] + +# Provides a mesh picking backend +bevy_mesh_picking_backend = [ + "bevy_picking", + "bevy_picking/bevy_mesh_picking_backend", ] +# Provides a sprite picking backend +bevy_sprite_picking_backend = [ + "bevy_picking", + "bevy_sprite/bevy_sprite_picking_backend", +] + +# Provides a UI picking backend +bevy_ui_picking_backend = ["bevy_picking", "bevy_ui/bevy_ui_picking_backend"] + # Enable support for the ios_simulator by downgrading some rendering capabilities ios_simulator = ["bevy_pbr?/ios_simulator", "bevy_render?/ios_simulator"] diff --git a/crates/bevy_math/src/aspect_ratio.rs b/crates/bevy_math/src/aspect_ratio.rs index a318ff0296..1a07de91d3 100644 --- a/crates/bevy_math/src/aspect_ratio.rs +++ b/crates/bevy_math/src/aspect_ratio.rs @@ -45,31 +45,31 @@ impl AspectRatio { /// Returns the aspect ratio as a f32 value. #[inline] - pub fn ratio(&self) -> f32 { + pub const fn ratio(&self) -> f32 { self.0 } /// Returns the inverse of this aspect ratio (height/width). #[inline] - pub fn inverse(&self) -> Self { + pub const fn inverse(&self) -> Self { Self(1.0 / self.0) } /// Returns true if the aspect ratio represents a landscape orientation. #[inline] - pub fn is_landscape(&self) -> bool { + pub const fn is_landscape(&self) -> bool { self.0 > 1.0 } /// Returns true if the aspect ratio represents a portrait orientation. #[inline] - pub fn is_portrait(&self) -> bool { + pub const fn is_portrait(&self) -> bool { self.0 < 1.0 } /// Returns true if the aspect ratio is exactly square. #[inline] - pub fn is_square(&self) -> bool { + pub const fn is_square(&self) -> bool { self.0 == 1.0 } } diff --git a/crates/bevy_math/src/rotation2d.rs b/crates/bevy_math/src/rotation2d.rs index 4ad267d83d..2b73d0ce33 100644 --- a/crates/bevy_math/src/rotation2d.rs +++ b/crates/bevy_math/src/rotation2d.rs @@ -350,7 +350,7 @@ impl Rot2 { #[inline] #[must_use] #[doc(alias = "conjugate")] - pub fn inverse(self) -> Self { + pub const fn inverse(self) -> Self { Self { cos: self.cos, sin: -self.sin, diff --git a/crates/bevy_picking/Cargo.toml b/crates/bevy_picking/Cargo.toml index d4731e001b..3deba7d21b 100644 --- a/crates/bevy_picking/Cargo.toml +++ b/crates/bevy_picking/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" [features] # Provides a mesh picking backend -bevy_mesh = ["dep:bevy_mesh", "dep:crossbeam-channel"] +bevy_mesh_picking_backend = ["dep:bevy_mesh", "dep:crossbeam-channel"] [dependencies] bevy_app = { path = "../bevy_app", version = "0.15.0-dev" } diff --git a/crates/bevy_picking/src/lib.rs b/crates/bevy_picking/src/lib.rs index 945385e5f6..6b0b95f11b 100644 --- a/crates/bevy_picking/src/lib.rs +++ b/crates/bevy_picking/src/lib.rs @@ -156,7 +156,7 @@ pub mod backend; pub mod events; pub mod focus; pub mod input; -#[cfg(feature = "bevy_mesh")] +#[cfg(feature = "bevy_mesh_picking_backend")] pub mod mesh_picking; pub mod pointer; @@ -168,7 +168,7 @@ use bevy_reflect::prelude::*; /// /// This includes the most common types in this crate, re-exported for your convenience. pub mod prelude { - #[cfg(feature = "bevy_mesh")] + #[cfg(feature = "bevy_mesh_picking_backend")] #[doc(hidden)] pub use crate::mesh_picking::{ ray_cast::{MeshRayCast, RayCastBackfaces, RayCastSettings, RayCastVisibility}, diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 0ab710527b..8c1ec9b58d 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -30,6 +30,7 @@ ci_limits = [] webgl = ["wgpu/webgl"] webgpu = ["wgpu/webgpu"] ios_simulator = [] +detailed_trace = [] [dependencies] # bevy diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index c220470e48..2923afbad6 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -76,7 +76,8 @@ use bevy_window::{PrimaryWindow, RawHandleWrapperHolder}; use extract_resource::ExtractResourcePlugin; use globals::GlobalsPlugin; use render_asset::RenderAssetBytesPerFrame; -use renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue}; +use renderer::{RenderDevice, RenderQueue}; +use settings::RenderResources; use sync_world::{ despawn_temporary_render_entities, entity_sync_system, SyncToRenderWorld, SyncWorldPlugin, }; @@ -95,7 +96,7 @@ use crate::{ use alloc::sync::Arc; use bevy_app::{App, AppLabel, Plugin, SubApp}; use bevy_asset::{load_internal_asset, AssetApp, AssetServer, Handle}; -use bevy_ecs::{prelude::*, schedule::ScheduleLabel, system::SystemState}; +use bevy_ecs::{prelude::*, schedule::ScheduleLabel}; use bevy_utils::tracing::debug; use core::ops::{Deref, DerefMut}; use std::sync::Mutex; @@ -240,19 +241,7 @@ pub mod graph { } #[derive(Resource)] -struct FutureRendererResources( - Arc< - Mutex< - Option<( - RenderDevice, - RenderQueue, - RenderAdapterInfo, - RenderAdapter, - RenderInstance, - )>, - >, - >, -); +struct FutureRenderResources(Arc>>); /// A label for the rendering sub-app. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] @@ -271,31 +260,27 @@ impl Plugin for RenderPlugin { .init_asset_loader::(); match &self.render_creation { - RenderCreation::Manual(device, queue, adapter_info, adapter, instance) => { - let future_renderer_resources_wrapper = Arc::new(Mutex::new(Some(( - device.clone(), - queue.clone(), - adapter_info.clone(), - adapter.clone(), - instance.clone(), - )))); - app.insert_resource(FutureRendererResources( - future_renderer_resources_wrapper.clone(), + RenderCreation::Manual(resources) => { + let future_render_resources_wrapper = Arc::new(Mutex::new(Some(resources.clone()))); + app.insert_resource(FutureRenderResources( + future_render_resources_wrapper.clone(), )); // SAFETY: Plugins should be set up on the main thread. unsafe { initialize_render_app(app) }; } RenderCreation::Automatic(render_creation) => { if let Some(backends) = render_creation.backends { - let future_renderer_resources_wrapper = Arc::new(Mutex::new(None)); - app.insert_resource(FutureRendererResources( - future_renderer_resources_wrapper.clone(), + let future_render_resources_wrapper = Arc::new(Mutex::new(None)); + app.insert_resource(FutureRenderResources( + future_render_resources_wrapper.clone(), )); - let mut system_state: SystemState< - Query<&RawHandleWrapperHolder, With>, - > = SystemState::new(app.world_mut()); - let primary_window = system_state.get(app.world()).get_single().ok().cloned(); + let primary_window = app + .world_mut() + .query_filtered::<&RawHandleWrapperHolder, With>() + .get_single(app.world()) + .ok() + .cloned(); let settings = render_creation.clone(); let async_renderer = async move { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { @@ -305,13 +290,13 @@ impl Plugin for RenderPlugin { gles_minor_version: settings.gles3_minor_version, }); - // SAFETY: Plugins should be set up on the main thread. - let surface = primary_window.and_then(|wrapper| unsafe { + let surface = primary_window.and_then(|wrapper| { let maybe_handle = wrapper.0.lock().expect( "Couldn't get the window handle in time for renderer initialization", ); if let Some(wrapper) = maybe_handle.as_ref() { - let handle = wrapper.get_handle(); + // SAFETY: Plugins should be set up on the main thread. + let handle = unsafe { wrapper.get_handle() }; Some( instance .create_surface(handle) @@ -337,9 +322,9 @@ impl Plugin for RenderPlugin { .await; debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); debug!("Configured wgpu adapter Features: {:#?}", device.features()); - let mut future_renderer_resources_inner = - future_renderer_resources_wrapper.lock().unwrap(); - *future_renderer_resources_inner = Some(( + let mut future_render_resources_inner = + future_render_resources_wrapper.lock().unwrap(); + *future_render_resources_inner = Some(RenderResources( device, queue, adapter_info, @@ -391,7 +376,7 @@ impl Plugin for RenderPlugin { fn ready(&self, app: &App) -> bool { app.world() - .get_resource::() + .get_resource::() .and_then(|frr| frr.0.try_lock().map(|locked| locked.is_some()).ok()) .unwrap_or(true) } @@ -404,11 +389,11 @@ impl Plugin for RenderPlugin { "color_operations.wgsl", Shader::from_wgsl ); - if let Some(future_renderer_resources) = - app.world_mut().remove_resource::() + if let Some(future_render_resources) = + app.world_mut().remove_resource::() { - let (device, queue, adapter_info, render_adapter, instance) = - future_renderer_resources.0.lock().unwrap().take().unwrap(); + let RenderResources(device, queue, adapter_info, render_adapter, instance) = + future_render_resources.0.lock().unwrap().take().unwrap(); app.insert_resource(device.clone()) .insert_resource(queue.clone()) diff --git a/crates/bevy_render/src/mesh/allocator.rs b/crates/bevy_render/src/mesh/allocator.rs index 3493f7dcf1..506d3adeff 100644 --- a/crates/bevy_render/src/mesh/allocator.rs +++ b/crates/bevy_render/src/mesh/allocator.rs @@ -955,12 +955,18 @@ impl ElementLayout { /// Creates an [`ElementLayout`] for mesh data of the given class (vertex or /// index) with the given byte size. fn new(class: ElementClass, size: u64) -> ElementLayout { + const { + assert!(4 == COPY_BUFFER_ALIGNMENT); + } + // this is equivalent to `4 / gcd(4,size)` but lets us not implement gcd. + // ping @atlv if above assert ever fails (likely never) + let elements_per_slot = [1, 4, 2, 4][size as usize & 3]; ElementLayout { class, size, // Make sure that slot boundaries begin and end on // `COPY_BUFFER_ALIGNMENT`-byte (4-byte) boundaries. - elements_per_slot: (COPY_BUFFER_ALIGNMENT / gcd(size, COPY_BUFFER_ALIGNMENT)) as u32, + elements_per_slot, } } @@ -1000,18 +1006,6 @@ impl GeneralSlab { } } -/// Returns the greatest common divisor of the two numbers. -/// -/// -fn gcd(mut a: u64, mut b: u64) -> u64 { - while b != 0 { - let t = b; - b = a % b; - a = t; - } - a -} - /// Returns a string describing the given buffer usages. fn buffer_usages_to_str(buffer_usages: BufferUsages) -> &'static str { if buffer_usages.contains(BufferUsages::VERTEX) { diff --git a/crates/bevy_render/src/render_resource/bind_group.rs b/crates/bevy_render/src/render_resource/bind_group.rs index 2753616aa0..d851389fa7 100644 --- a/crates/bevy_render/src/render_resource/bind_group.rs +++ b/crates/bevy_render/src/render_resource/bind_group.rs @@ -172,7 +172,7 @@ impl Deref for BindGroup { /// * The field's [`Handle`](bevy_asset::Handle) will be used to look up the matching [`Buffer`] GPU resource, which /// will be bound as a storage buffer in shaders. If the `storage` attribute is used, the field is expected a raw /// buffer, and the buffer will be bound as a storage buffer in shaders. -/// * It supports and optional `read_only` parameter. Defaults to false if not present. +/// * It supports an optional `read_only` parameter. Defaults to false if not present. /// /// | Arguments | Values | Default | /// |------------------------|-------------------------------------------------------------------------|----------------------| diff --git a/crates/bevy_render/src/settings.rs b/crates/bevy_render/src/settings.rs index bf1db17ff1..fe8933656a 100644 --- a/crates/bevy_render/src/settings.rs +++ b/crates/bevy_render/src/settings.rs @@ -124,16 +124,19 @@ impl Default for WgpuSettings { } } +#[derive(Clone)] +pub struct RenderResources( + pub RenderDevice, + pub RenderQueue, + pub RenderAdapterInfo, + pub RenderAdapter, + pub RenderInstance, +); + /// An enum describing how the renderer will initialize resources. This is used when creating the [`RenderPlugin`](crate::RenderPlugin). pub enum RenderCreation { /// Allows renderer resource initialization to happen outside of the rendering plugin. - Manual( - RenderDevice, - RenderQueue, - RenderAdapterInfo, - RenderAdapter, - RenderInstance, - ), + Manual(RenderResources), /// Lets the rendering plugin create resources itself. Automatic(WgpuSettings), } @@ -147,7 +150,13 @@ impl RenderCreation { adapter: RenderAdapter, instance: RenderInstance, ) -> Self { - Self::Manual(device, queue, adapter_info, adapter, instance) + RenderResources(device, queue, adapter_info, adapter, instance).into() + } +} + +impl From for RenderCreation { + fn from(value: RenderResources) -> Self { + Self::Manual(value) } } diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index 6cee2885d0..69a8050ed7 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -117,7 +117,7 @@ impl Plugin for SyncWorldPlugin { /// /// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin /// [`SyncComponentPlugin`]: crate::sync_component::SyncComponentPlugin -#[derive(Component, Clone, Debug, Default, Reflect)] +#[derive(Component, Copy, Clone, Debug, Default, Reflect)] #[reflect[Component]] #[component(storage = "SparseSet")] pub struct SyncToRenderWorld; @@ -165,8 +165,7 @@ pub type MainEntityHashMap = hashbrown::HashMap; pub type MainEntityHashSet = hashbrown::HashSet; /// Marker component that indicates that its entity needs to be despawned at the end of the frame. -#[derive(Component, Clone, Debug, Default, Reflect)] -#[component(storage = "SparseSet")] +#[derive(Component, Copy, Clone, Debug, Default, Reflect)] pub struct TemporaryRenderEntity; /// A record enum to what entities with [`SyncToRenderWorld`] have been added or removed. diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 572a1db7b8..22af7ffb44 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -186,6 +186,35 @@ impl Msaa { #[derive(Component)] pub struct ExtractedView { + /// Typically a right-handed projection matrix, one of either: + /// + /// Perspective (infinite reverse z) + /// ```text + /// f = 1 / tan(fov_y_radians / 2) + /// + /// ⎡ f / aspect 0 0 0 ⎤ + /// ⎢ 0 f 0 0 ⎥ + /// ⎢ 0 0 0 -1 ⎥ + /// ⎣ 0 0 near 0 ⎦ + /// ``` + /// + /// Orthographic + /// ```text + /// w = right - left + /// h = top - bottom + /// d = near - far + /// cw = -right - left + /// ch = -top - bottom + /// + /// ⎡ 2 / w 0 0 0 ⎤ + /// ⎢ 0 2 / h 0 0 ⎥ + /// ⎢ 0 0 1 / d 0 ⎥ + /// ⎣ cw / w ch / h near / d 1 ⎦ + /// ``` + /// + /// `clip_from_view[3][3] == 1.0` is the standard way to check if a projection is orthographic + /// + /// Custom projections are also possible however. pub clip_from_view: Mat4, pub world_from_view: GlobalTransform, // The view-projection matrix. When provided it is used instead of deriving it from @@ -423,12 +452,44 @@ pub struct ViewUniform { pub world_from_clip: Mat4, pub world_from_view: Mat4, pub view_from_world: Mat4, + /// Typically a right-handed projection matrix, one of either: + /// + /// Perspective (infinite reverse z) + /// ```text + /// f = 1 / tan(fov_y_radians / 2) + /// + /// ⎡ f / aspect 0 0 0 ⎤ + /// ⎢ 0 f 0 0 ⎥ + /// ⎢ 0 0 0 -1 ⎥ + /// ⎣ 0 0 near 0 ⎦ + /// ``` + /// + /// Orthographic + /// ```text + /// w = right - left + /// h = top - bottom + /// d = near - far + /// cw = -right - left + /// ch = -top - bottom + /// + /// ⎡ 2 / w 0 0 0 ⎤ + /// ⎢ 0 2 / h 0 0 ⎥ + /// ⎢ 0 0 1 / d 0 ⎥ + /// ⎣ cw / w ch / h near / d 1 ⎦ + /// ``` + /// + /// `clip_from_view[3][3] == 1.0` is the standard way to check if a projection is orthographic + /// + /// Custom projections are also possible however. pub clip_from_view: Mat4, pub view_from_clip: Mat4, pub world_position: Vec3, pub exposure: f32, // viewport(x_origin, y_origin, width, height) pub viewport: Vec4, + /// 6 world-space half spaces (normal: vec3, distance: f32) ordered left, right, top, bottom, near, far. + /// The normal vectors point towards the interior of the frustum. + /// A half space contains `p` if `normal.dot(p) + distance > 0.` pub frustum: [Vec4; 6], pub color_grading: ColorGradingUniform, pub mip_bias: f32, diff --git a/crates/bevy_render/src/view/view.wgsl b/crates/bevy_render/src/view/view.wgsl index c67f382c23..ed08599758 100644 --- a/crates/bevy_render/src/view/view.wgsl +++ b/crates/bevy_render/src/view/view.wgsl @@ -19,12 +19,44 @@ struct View { world_from_clip: mat4x4, world_from_view: mat4x4, view_from_world: mat4x4, + // Typically a right-handed projection matrix, one of either: + // + // Perspective (infinite reverse z) + // ``` + // f = 1 / tan(fov_y_radians / 2) + // + // ⎡ f / aspect 0 0 0 ⎤ + // ⎢ 0 f 0 0 ⎥ + // ⎢ 0 0 0 -1 ⎥ + // ⎣ 0 0 near 0 ⎦ + // ``` + // + // Orthographic + // ``` + // w = right - left + // h = top - bottom + // d = near - far + // cw = -right - left + // ch = -top - bottom + // + // ⎡ 2 / w 0 0 0 ⎤ + // ⎢ 0 2 / h 0 0 ⎥ + // ⎢ 0 0 1 / d 0 ⎥ + // ⎣ cw / w ch / h near / d 1 ⎦ + // ``` + // + // `clip_from_view[3][3] == 1.0` is the standard way to check if a projection is orthographic + // + // Custom projections are also possible however. clip_from_view: mat4x4, view_from_clip: mat4x4, world_position: vec3, exposure: f32, // viewport(x_origin, y_origin, width, height) viewport: vec4, + // 6 world-space half spaces (normal: vec3, distance: f32) ordered left, right, top, bottom, near, far. + // The normal vectors point towards the interior of the frustum. + // A half space contains `p` if `normal.dot(p) + distance > 0.` frustum: array, 6>, color_grading: ColorGrading, mip_bias: f32, diff --git a/crates/bevy_render/src/view/visibility/range.rs b/crates/bevy_render/src/view/visibility/range.rs index c476c9e9c6..9ea69db7f9 100644 --- a/crates/bevy_render/src/view/visibility/range.rs +++ b/crates/bevy_render/src/view/visibility/range.rs @@ -12,6 +12,7 @@ use bevy_ecs::{ entity::{Entity, EntityHashMap}, query::{Changed, With}, reflect::ReflectComponent, + removal_detection::RemovedComponents, schedule::IntoSystemConfigs as _, system::{Query, Res, ResMut, Resource}, }; @@ -111,7 +112,7 @@ impl Plugin for VisibilityRangePlugin { /// that the `end_margin` of a higher LOD is always identical to the /// `start_margin` of the next lower LOD; this is important for the crossfade /// effect to function properly. -#[derive(Component, Clone, PartialEq, Reflect)] +#[derive(Component, Clone, PartialEq, Default, Reflect)] #[reflect(Component, PartialEq, Hash)] pub struct VisibilityRange { /// The range of distances, in world units, between which this entity will @@ -131,6 +132,20 @@ pub struct VisibilityRange { /// /// `end_margin.start` must be greater than or equal to `start_margin.end`. pub end_margin: Range, + + /// If set to true, Bevy will use the center of the axis-aligned bounding + /// box ([`Aabb`]) as the position of the mesh for the purposes of + /// visibility range computation. + /// + /// Otherwise, if this field is set to false, Bevy will use the origin of + /// the mesh as the mesh's position. + /// + /// Usually you will want to leave this set to false, because different LODs + /// may have different AABBs, and smooth crossfades between LOD levels + /// require that all LODs of a mesh be at *precisely* the same position. If + /// you aren't using crossfading, however, and your meshes aren't centered + /// around their origins, then this flag may be useful. + pub use_aabb: bool, } impl Eq for VisibilityRange {} @@ -160,6 +175,7 @@ impl VisibilityRange { Self { start_margin: start..start, end_margin: end..end, + use_aabb: false, } } @@ -390,14 +406,17 @@ pub fn check_visibility_ranges( for (entity, entity_transform, maybe_model_aabb, visibility_range) in entity_query.iter_mut() { let mut visibility = 0; for (view_index, &(_, view_position)) in views.iter().enumerate() { - let model_pos = if let Some(model_aabb) = maybe_model_aabb { - let world_from_local = entity_transform.affine(); - world_from_local.transform_point3a(model_aabb.center) - } else { - entity_transform.translation_vec3a() + // If instructed to use the AABB and the model has one, use its + // center as the model position. Otherwise, use the model's + // translation. + let model_position = match (visibility_range.use_aabb, maybe_model_aabb) { + (true, Some(model_aabb)) => entity_transform + .affine() + .transform_point3a(model_aabb.center), + _ => entity_transform.translation_vec3a(), }; - if visibility_range.is_visible_at_all((view_position - model_pos).length()) { + if visibility_range.is_visible_at_all((view_position - model_position).length()) { visibility |= 1 << view_index; } } @@ -416,8 +435,9 @@ pub fn extract_visibility_ranges( mut render_visibility_ranges: ResMut, visibility_ranges_query: Extract>, changed_ranges_query: Extract>>, + mut removed_visibility_ranges: Extract>, ) { - if changed_ranges_query.is_empty() { + if changed_ranges_query.is_empty() && removed_visibility_ranges.read().next().is_none() { return; } diff --git a/crates/bevy_sprite/Cargo.toml b/crates/bevy_sprite/Cargo.toml index 0709eead67..9bbf629ec5 100644 --- a/crates/bevy_sprite/Cargo.toml +++ b/crates/bevy_sprite/Cargo.toml @@ -9,7 +9,6 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [features] -default = ["bevy_sprite_picking_backend"] bevy_sprite_picking_backend = ["bevy_picking", "bevy_window"] serialize = ["dep:serde"] webgl = [] diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 8ea962eec2..c6023b2c30 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -63,8 +63,20 @@ use bevy_render::{ }; /// Adds support for 2D sprite rendering. -#[derive(Default)] -pub struct SpritePlugin; +pub struct SpritePlugin { + /// Whether to add the sprite picking backend to the app. + #[cfg(feature = "bevy_sprite_picking_backend")] + pub add_picking: bool, +} + +impl Default for SpritePlugin { + fn default() -> Self { + Self { + #[cfg(feature = "bevy_sprite_picking_backend")] + add_picking: true, + } + } +} pub const SPRITE_SHADER_HANDLE: Handle = Handle::weak_from_u128(2763343953151597127); pub const SPRITE_VIEW_BINDINGS_SHADER_HANDLE: Handle = @@ -135,7 +147,9 @@ impl Plugin for SpritePlugin { ); #[cfg(feature = "bevy_sprite_picking_backend")] - app.add_plugins(picking_backend::SpritePickingPlugin); + if self.add_picking { + app.add_plugins(picking_backend::SpritePickingPlugin); + } if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 6b85675c94..cd5fba2ca4 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -71,7 +71,7 @@ pub mod prelude { }; } -use bevy_app::prelude::*; +use bevy_app::{prelude::*, Animation}; use bevy_asset::AssetApp; #[cfg(feature = "default_font")] use bevy_asset::{load_internal_binary_asset, Handle}; @@ -138,7 +138,8 @@ impl Plugin for TextPlugin { calculate_bounds_text2d.in_set(VisibilitySystems::CalculateBounds), ) .chain() - .in_set(Update2dText), + .in_set(Update2dText) + .after(Animation), ) .add_systems(Last, trim_cosmic_cache); diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs index 13e464451b..0effb361aa 100644 --- a/crates/bevy_text/src/text.rs +++ b/crates/bevy_text/src/text.rs @@ -289,6 +289,28 @@ pub struct TextFont { } impl TextFont { + /// Returns a new [`TextFont`] with the specified font face handle. + pub fn from_font(font: Handle) -> Self { + Self::default().with_font(font) + } + + /// Returns a new [`TextFont`] with the specified font size. + pub fn from_font_size(font_size: f32) -> Self { + Self::default().with_font_size(font_size) + } + + /// Returns this [`TextFont`] with the specified font face handle. + pub fn with_font(mut self, font: Handle) -> Self { + self.font = font; + self + } + + /// Returns this [`TextFont`] with the specified font size. + pub const fn with_font_size(mut self, font_size: f32) -> Self { + self.font_size = font_size; + self + } + /// Returns this [`TextFont`] with the specified [`FontSmoothing`]. pub const fn with_font_smoothing(mut self, font_smoothing: FontSmoothing) -> Self { self.font_smoothing = font_smoothing; diff --git a/crates/bevy_ui/Cargo.toml b/crates/bevy_ui/Cargo.toml index d3f456d63f..1517543e16 100644 --- a/crates/bevy_ui/Cargo.toml +++ b/crates/bevy_ui/Cargo.toml @@ -25,7 +25,6 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [ "bevy", ] } bevy_render = { path = "../bevy_render", version = "0.15.0-dev" } -bevy_animation = { path = "../bevy_animation", version = "0.15.0-dev" } bevy_sprite = { path = "../bevy_sprite", version = "0.15.0-dev" } bevy_text = { path = "../bevy_text", version = "0.15.0-dev" } bevy_picking = { path = "../bevy_picking", version = "0.15.0-dev", optional = true } @@ -47,7 +46,6 @@ smallvec = "1.11" accesskit = "0.17" [features] -default = ["bevy_ui_picking_backend"] serialize = ["serde", "smallvec/serde", "bevy_math/serialize"] bevy_ui_picking_backend = ["bevy_picking"] diff --git a/crates/bevy_ui/src/accessibility.rs b/crates/bevy_ui/src/accessibility.rs index d67babc411..95686530ce 100644 --- a/crates/bevy_ui/src/accessibility.rs +++ b/crates/bevy_ui/src/accessibility.rs @@ -133,14 +133,14 @@ fn label_changed( if let Some(mut accessible) = accessible { accessible.set_role(Role::Label); if let Some(label) = label { - accessible.set_label(label); + accessible.set_value(label); } else { - accessible.clear_label(); + accessible.clear_value(); } } else { let mut node = Node::new(Role::Label); if let Some(label) = label { - node.set_label(label); + node.set_value(label); } commands .entity(entity) diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 763c6e00f0..a2eb37ee31 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -1,6 +1,5 @@ use crate::{ - CalculatedClip, ComputedNode, DefaultUiCamera, ResolvedBorderRadius, TargetCamera, UiScale, - UiStack, + CalculatedClip, ComputedNode, DefaultUiCamera, ResolvedBorderRadius, TargetCamera, UiStack, }; use bevy_ecs::{ change_detection::DetectChangesMut, @@ -158,7 +157,6 @@ pub fn ui_focus_system( windows: Query<&Window>, mouse_button_input: Res>, touches_input: Res, - ui_scale: Res, ui_stack: Res, mut node_query: Query, ) { @@ -201,19 +199,16 @@ pub fn ui_focus_system( }; let viewport_position = camera - .logical_viewport_rect() - .map(|rect| rect.min) + .physical_viewport_rect() + .map(|rect| rect.min.as_vec2()) .unwrap_or_default(); windows .get(window_ref.entity()) .ok() - .and_then(Window::cursor_position) + .and_then(Window::physical_cursor_position) .or_else(|| touches_input.first_pressed_position()) .map(|cursor_position| (entity, cursor_position - viewport_position)) }) - // The cursor position returned by `Window` only takes into account the window scale factor and not `UiScale`. - // To convert the cursor position to logical UI viewport coordinates we have to divide it by `UiScale`. - .map(|(entity, cursor_position)| (entity, cursor_position / ui_scale.0)) .collect(); // prepare an iterator that contains all the nodes that have the cursor in their rect, diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index aaa3f47050..ca077a9e56 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -1,7 +1,7 @@ use crate::{ experimental::{UiChildren, UiRootNodes}, BorderRadius, ComputedNode, ContentSize, DefaultUiCamera, Display, Node, Outline, OverflowAxis, - ScrollPosition, TargetCamera, UiScale, + ScrollPosition, TargetCamera, UiScale, Val, }; use bevy_ecs::{ change_detection::{DetectChanges, DetectChangesMut}, @@ -343,31 +343,33 @@ with UI components as a child of an entity without UI components, your UI layout maybe_scroll_position, )) = node_transform_query.get_mut(entity) { - let Ok((layout, unrounded_unscaled_size)) = ui_surface.get_layout(entity) else { + let Ok((layout, unrounded_size)) = ui_surface.get_layout(entity) else { return; }; - let layout_size = - inverse_target_scale_factor * Vec2::new(layout.size.width, layout.size.height); - let unrounded_size = inverse_target_scale_factor * unrounded_unscaled_size; - let layout_location = - inverse_target_scale_factor * Vec2::new(layout.location.x, layout.location.y); + let layout_size = Vec2::new(layout.size.width, layout.size.height); + + 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 let node_center = layout_location - parent_scroll_position + 0.5 * (layout_size - parent_size); // only trigger change detection when the new values are different - if node.size != layout_size || node.unrounded_size != unrounded_size { + if node.size != layout_size + || node.unrounded_size != unrounded_size + || node.inverse_scale_factor != inverse_target_scale_factor + { node.size = layout_size; node.unrounded_size = unrounded_size; + node.inverse_scale_factor = inverse_target_scale_factor; } let taffy_rect_to_border_rect = |rect: taffy::Rect| BorderRect { - left: rect.left * inverse_target_scale_factor, - right: rect.right * inverse_target_scale_factor, - top: rect.top * inverse_target_scale_factor, - bottom: rect.bottom * inverse_target_scale_factor, + left: rect.left, + right: rect.right, + top: rect.top, + bottom: rect.bottom, }; node.bypass_change_detection().border = taffy_rect_to_border_rect(layout.border); @@ -377,28 +379,35 @@ with UI components as a child of an entity without UI components, your UI layout if let Some(border_radius) = maybe_border_radius { // We don't trigger change detection for changes to border radius - node.bypass_change_detection().border_radius = - border_radius.resolve(node.size, viewport_size); + node.bypass_change_detection().border_radius = border_radius.resolve( + node.size, + viewport_size, + inverse_target_scale_factor.recip(), + ); } if let Some(outline) = maybe_outline { // don't trigger change detection when only outlines are changed let node = node.bypass_change_detection(); node.outline_width = if style.display != Display::None { - outline - .width - .resolve(node.size().x, viewport_size) - .unwrap_or(0.) - .max(0.) + match outline.width { + Val::Px(w) => Val::Px(w / inverse_target_scale_factor), + width => width, + } + .resolve(node.size().x, viewport_size) + .unwrap_or(0.) + .max(0.) } else { 0. }; - node.outline_offset = outline - .offset - .resolve(node.size().x, viewport_size) - .unwrap_or(0.) - .max(0.); + node.outline_offset = match outline.offset { + Val::Px(offset) => Val::Px(offset / inverse_target_scale_factor), + offset => offset, + } + .resolve(node.size().x, viewport_size) + .unwrap_or(0.) + .max(0.); } if transform.translation.truncate() != node_center { @@ -422,8 +431,7 @@ with UI components as a child of an entity without UI components, your UI layout }) .unwrap_or_default(); - let content_size = Vec2::new(layout.content_size.width, layout.content_size.height) - * inverse_target_scale_factor; + let content_size = Vec2::new(layout.content_size.width, layout.content_size.height); let max_possible_offset = (content_size - layout_size).max(Vec2::ZERO); let clamped_scroll_position = scroll_position.clamp(Vec2::ZERO, max_possible_offset); @@ -1131,7 +1139,7 @@ mod tests { .sum(); let parent_width = world.get::(parent).unwrap().size.x; assert!((width_sum - parent_width).abs() < 0.001); - assert!((width_sum - 320.).abs() <= 1.); + assert!((width_sum - 320. * s).abs() <= 1.); s += r; } } diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 9b8945f7b9..eb2ede9fb0 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -66,7 +66,7 @@ pub mod prelude { }; } -use bevy_app::prelude::*; +use bevy_app::{prelude::*, Animation}; use bevy_ecs::prelude::*; use bevy_input::InputSystem; use bevy_render::{ @@ -85,12 +85,17 @@ pub struct UiPlugin { /// If set to false, the UI's rendering systems won't be added to the `RenderApp` and no UI elements will be drawn. /// The layout and interaction components will still be updated as normal. pub enable_rendering: bool, + /// Whether to add the UI picking backend to the app. + #[cfg(feature = "bevy_ui_picking_backend")] + pub add_picking: bool, } impl Default for UiPlugin { fn default() -> Self { Self { enable_rendering: true, + #[cfg(feature = "bevy_ui_picking_backend")] + add_picking: true, } } } @@ -171,9 +176,7 @@ impl Plugin for UiPlugin { PostUpdate, ( CameraUpdateSystem, - UiSystem::Prepare - .before(UiSystem::Stack) - .after(bevy_animation::Animation), + UiSystem::Prepare.before(UiSystem::Stack).after(Animation), UiSystem::Layout, UiSystem::PostLayout, ) @@ -219,7 +222,9 @@ impl Plugin for UiPlugin { build_text_interop(app); #[cfg(feature = "bevy_ui_picking_backend")] - app.add_plugins(picking_backend::UiPickingPlugin); + if self.add_picking { + app.add_plugins(picking_backend::UiPickingPlugin); + } if !self.enable_rendering { return; diff --git a/crates/bevy_ui/src/picking_backend.rs b/crates/bevy_ui/src/picking_backend.rs index 93c14e35b4..fb43cd08c4 100644 --- a/crates/bevy_ui/src/picking_backend.rs +++ b/crates/bevy_ui/src/picking_backend.rs @@ -65,7 +65,6 @@ pub fn ui_picking( camera_query: Query<(Entity, &Camera, Has)>, default_ui_camera: DefaultUiCamera, primary_window: Query>, - ui_scale: Res, ui_stack: Res, node_query: Query, mut output: EventWriter, @@ -95,15 +94,15 @@ pub fn ui_picking( let Ok((_, camera_data, _)) = camera_query.get(camera) else { continue; }; - let mut pointer_pos = pointer_location.position; - if let Some(viewport) = camera_data.logical_viewport_rect() { - pointer_pos -= viewport.min; + let mut pointer_pos = + pointer_location.position * camera_data.target_scaling_factor().unwrap_or(1.); + if let Some(viewport) = camera_data.physical_viewport_rect() { + pointer_pos -= viewport.min.as_vec2(); } - let scaled_pointer_pos = pointer_pos / **ui_scale; pointer_pos_by_camera .entry(camera) .or_default() - .insert(pointer_id, scaled_pointer_pos); + .insert(pointer_id, pointer_pos); } } diff --git a/crates/bevy_ui/src/render/box_shadow.rs b/crates/bevy_ui/src/render/box_shadow.rs index fc19dde8bf..51536ad981 100644 --- a/crates/bevy_ui/src/render/box_shadow.rs +++ b/crates/bevy_ui/src/render/box_shadow.rs @@ -1,8 +1,10 @@ +//! Box shadows rendering + use core::{hash::Hash, ops::Range}; use crate::{ BoxShadow, CalculatedClip, ComputedNode, DefaultUiCamera, RenderUiSystem, ResolvedBorderRadius, - TargetCamera, TransparentUi, UiBoxShadowSamples, UiScale, Val, + TargetCamera, TransparentUi, UiBoxShadowSamples, Val, }; use bevy_app::prelude::*; use bevy_asset::*; @@ -133,14 +135,14 @@ impl FromWorld for BoxShadowPipeline { } #[derive(Clone, Copy, Hash, PartialEq, Eq)] -pub struct UiTextureSlicePipelineKey { +pub struct BoxShadowPipelineKey { pub hdr: bool, /// Number of samples, a higher value results in better quality shadows. pub samples: u32, } impl SpecializedRenderPipeline for BoxShadowPipeline { - type Key = UiTextureSlicePipelineKey; + type Key = BoxShadowPipelineKey; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { let vertex_layout = VertexBufferLayout::from_vertex_formats( @@ -235,7 +237,6 @@ pub fn extract_shadows( mut commands: Commands, mut extracted_box_shadows: ResMut, default_ui_camera: Extract, - ui_scale: Extract>, camera_query: Extract>, box_shadow_query: Extract< Query<( @@ -266,37 +267,36 @@ pub fn extract_shadows( continue; } - let ui_logical_viewport_size = camera_query + let ui_physical_viewport_size = camera_query .get(camera_entity) .ok() - .and_then(|(_, c)| c.logical_viewport_size()) - .unwrap_or(Vec2::ZERO) - // The logical window resolution returned by `Window` only takes into account the window scale factor and not `UiScale`, - // so we have to divide by `UiScale` to get the size of the UI viewport. - / ui_scale.0; + .and_then(|(_, c)| { + c.physical_viewport_size() + .map(|size| Vec2::new(size.x as f32, size.y as f32)) + }) + .unwrap_or(Vec2::ZERO); - let resolve_val = |val, base| match val { + let scale_factor = uinode.inverse_scale_factor.recip(); + + let resolve_val = |val, base, scale_factor| match val { Val::Auto => 0., - Val::Px(px) => px, + Val::Px(px) => px * scale_factor, Val::Percent(percent) => percent / 100. * base, - Val::Vw(percent) => percent / 100. * ui_logical_viewport_size.x, - Val::Vh(percent) => percent / 100. * ui_logical_viewport_size.y, - Val::VMin(percent) => percent / 100. * ui_logical_viewport_size.min_element(), - Val::VMax(percent) => percent / 100. * ui_logical_viewport_size.max_element(), + Val::Vw(percent) => percent / 100. * ui_physical_viewport_size.x, + Val::Vh(percent) => percent / 100. * ui_physical_viewport_size.y, + Val::VMin(percent) => percent / 100. * ui_physical_viewport_size.min_element(), + Val::VMax(percent) => percent / 100. * ui_physical_viewport_size.max_element(), }; - let spread_x = resolve_val(box_shadow.spread_radius, uinode.size().x); - let spread_ratio_x = (spread_x + uinode.size().x) / uinode.size().x; + let spread_x = resolve_val(box_shadow.spread_radius, uinode.size().x, scale_factor); + let spread_ratio = (spread_x + uinode.size().x) / uinode.size().x; - let spread = vec2( - spread_x, - (spread_ratio_x * uinode.size().y) - uinode.size().y, - ); + let spread = vec2(spread_x, uinode.size().y * spread_ratio - uinode.size().y); - let blur_radius = resolve_val(box_shadow.blur_radius, uinode.size().x); + let blur_radius = resolve_val(box_shadow.blur_radius, uinode.size().x, scale_factor); let offset = vec2( - resolve_val(box_shadow.x_offset, uinode.size().x), - resolve_val(box_shadow.y_offset, uinode.size().y), + resolve_val(box_shadow.x_offset, uinode.size().x, scale_factor), + resolve_val(box_shadow.y_offset, uinode.size().y, scale_factor), ); let shadow_size = uinode.size() + spread; @@ -305,10 +305,10 @@ pub fn extract_shadows( } let radius = ResolvedBorderRadius { - top_left: uinode.border_radius.top_left * spread_ratio_x, - top_right: uinode.border_radius.top_right * spread_ratio_x, - bottom_left: uinode.border_radius.bottom_left * spread_ratio_x, - bottom_right: uinode.border_radius.bottom_right * spread_ratio_x, + top_left: uinode.border_radius.top_left * spread_ratio, + top_right: uinode.border_radius.top_right * spread_ratio, + bottom_left: uinode.border_radius.bottom_left * spread_ratio, + bottom_right: uinode.border_radius.bottom_right * spread_ratio, }; extracted_box_shadows.box_shadows.insert( @@ -333,8 +333,8 @@ pub fn extract_shadows( } pub fn queue_shadows( - extracted_ui_slicers: ResMut, - ui_slicer_pipeline: Res, + extracted_box_shadows: ResMut, + box_shadow_pipeline: Res, mut pipelines: ResMut>, mut transparent_render_phases: ResMut>, mut views: Query<(Entity, &ExtractedView, Option<&UiBoxShadowSamples>)>, @@ -342,7 +342,7 @@ pub fn queue_shadows( draw_functions: Res>, ) { let draw_function = draw_functions.read().id::(); - for (entity, extracted_shadow) in extracted_ui_slicers.box_shadows.iter() { + for (entity, extracted_shadow) in extracted_box_shadows.box_shadows.iter() { let Ok((view_entity, view, shadow_samples)) = views.get_mut(extracted_shadow.camera_entity) else { continue; @@ -354,8 +354,8 @@ pub fn queue_shadows( let pipeline = pipelines.specialize( &pipeline_cache, - &ui_slicer_pipeline, - UiTextureSlicePipelineKey { + &box_shadow_pipeline, + BoxShadowPipelineKey { hdr: view.hdr, samples: shadow_samples.copied().unwrap_or_default().0, }, @@ -371,7 +371,6 @@ pub fn queue_shadows( ), batch_range: 0..0, extra_index: PhaseItemExtraIndex::NONE, - inverse_scale_factor: 1., }); } } @@ -384,7 +383,7 @@ pub fn prepare_shadows( mut ui_meta: ResMut, mut extracted_shadows: ResMut, view_uniforms: Res, - texture_slicer_pipeline: Res, + box_shadow_pipeline: Res, mut phases: ResMut>, mut previous_len: Local, ) { @@ -394,8 +393,8 @@ pub fn prepare_shadows( ui_meta.vertices.clear(); ui_meta.indices.clear(); ui_meta.view_bind_group = Some(render_device.create_bind_group( - "ui_texture_slice_view_bind_group", - &texture_slicer_pipeline.view_layout, + "box_shadow_view_bind_group", + &box_shadow_pipeline.view_layout, &BindGroupEntries::single(view_binding), )); diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index b8bdb40d5e..8badc68771 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -8,7 +8,6 @@ use crate::widget::ImageNode; use crate::{ experimental::UiChildren, BackgroundColor, BorderColor, CalculatedClip, ComputedNode, DefaultUiCamera, Outline, ResolvedBorderRadius, TargetCamera, UiAntiAlias, UiBoxShadowSamples, - UiScale, }; use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, AssetEvent, AssetId, Assets, Handle}; @@ -19,7 +18,7 @@ use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; use bevy_ecs::entity::{EntityHashMap, EntityHashSet}; use bevy_ecs::prelude::*; use bevy_image::Image; -use bevy_math::{FloatOrd, Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4Swizzles}; +use bevy_math::{FloatOrd, Mat4, Rect, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4Swizzles}; use bevy_render::render_phase::ViewSortedRenderPhases; use bevy_render::sync_world::MainEntity; use bevy_render::texture::TRANSPARENT_IMAGE_HANDLE; @@ -528,16 +527,10 @@ const UI_CAMERA_TRANSFORM_OFFSET: f32 = -0.1; #[derive(Component)] pub struct DefaultCameraView(pub Entity); -#[derive(Component)] -pub struct ExtractedAA { - pub scale_factor: f32, -} - /// Extracts all UI elements associated with a camera into the render world. pub fn extract_default_ui_camera_view( mut commands: Commands, mut transparent_render_phases: ResMut>, - ui_scale: Extract>, query: Extract< Query< ( @@ -553,41 +546,26 @@ pub fn extract_default_ui_camera_view( ) { live_entities.clear(); - let scale = ui_scale.0.recip(); for (entity, camera, ui_anti_alias, shadow_samples) in &query { // ignore inactive cameras if !camera.is_active { commands .get_entity(entity) .expect("Camera entity wasn't synced.") - .remove::<(DefaultCameraView, ExtractedAA, UiBoxShadowSamples)>(); + .remove::<(DefaultCameraView, UiAntiAlias, UiBoxShadowSamples)>(); continue; } - if let ( - Some(logical_size), - Some(URect { - min: physical_origin, - .. - }), - Some(physical_size), - Some(scale_factor), - ) = ( - camera.logical_viewport_size(), - camera.physical_viewport_rect(), - camera.physical_viewport_size(), - camera.target_scaling_factor(), - ) { + if let Some(physical_viewport_rect) = camera.physical_viewport_rect() { // use a projection matrix with the origin in the top left instead of the bottom left that comes with OrthographicProjection let projection_matrix = Mat4::orthographic_rh( 0.0, - logical_size.x * scale, - logical_size.y * scale, + physical_viewport_rect.width() as f32, + physical_viewport_rect.height() as f32, 0.0, 0.0, UI_CAMERA_FAR, ); - let default_camera_view = commands .spawn(( ExtractedView { @@ -599,12 +577,10 @@ pub fn extract_default_ui_camera_view( ), clip_from_world: None, hdr: camera.hdr, - viewport: UVec4::new( - physical_origin.x, - physical_origin.y, - physical_size.x, - physical_size.y, - ), + viewport: UVec4::from(( + physical_viewport_rect.min, + physical_viewport_rect.size(), + )), color_grading: Default::default(), }, TemporaryRenderEntity, @@ -614,10 +590,8 @@ pub fn extract_default_ui_camera_view( .get_entity(entity) .expect("Camera entity wasn't synced."); entity_commands.insert(DefaultCameraView(default_camera_view)); - if ui_anti_alias != Some(&UiAntiAlias::Off) { - entity_commands.insert(ExtractedAA { - scale_factor: (scale_factor * ui_scale.0), - }); + if let Some(ui_anti_alias) = ui_anti_alias { + entity_commands.insert(*ui_anti_alias); } if let Some(shadow_samples) = shadow_samples { entity_commands.insert(*shadow_samples); @@ -635,10 +609,8 @@ pub fn extract_default_ui_camera_view( pub fn extract_text_sections( mut commands: Commands, mut extracted_uinodes: ResMut, - camera_query: Extract>, default_ui_camera: Extract, texture_atlases: Extract>>, - ui_scale: Extract>, uinode_query: Extract< Query<( Entity, @@ -678,32 +650,18 @@ pub fn extract_text_sections( continue; } - let scale_factor = camera_query - .get(camera_entity) - .ok() - .and_then(Camera::target_scaling_factor) - .unwrap_or(1.0) - * ui_scale.0; - let inverse_scale_factor = scale_factor.recip(); - let Ok(&render_camera_entity) = mapping.get(camera_entity) else { continue; }; - // Align the text to the nearest physical pixel: + + // Align the text to the nearest pixel: // * Translate by minus the text node's half-size // (The transform translates to the center of the node but the text coordinates are relative to the node's top left corner) - // * Multiply the logical coordinates by the scale factor to get its position in physical coordinates - // * Round the physical position to the nearest physical pixel - // * Multiply by the rounded physical position by the inverse scale factor to return to logical coordinates - - let logical_top_left = -0.5 * uinode.size(); + // * Round the position to the nearest physical pixel let mut transform = global_transform.affine() - * bevy_math::Affine3A::from_translation(logical_top_left.extend(0.)); - - transform.translation *= scale_factor; + * bevy_math::Affine3A::from_translation((-0.5 * uinode.size()).extend(0.)); transform.translation = transform.translation.round(); - transform.translation *= inverse_scale_factor; let mut color = LinearRgba::WHITE; let mut current_span = usize::MAX; @@ -730,15 +688,14 @@ pub fn extract_text_sections( .unwrap_or_default(); current_span = *span_index; } - let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap(); - - let mut rect = atlas.textures[atlas_info.location.glyph_index].as_rect(); - rect.min *= inverse_scale_factor; - rect.max *= inverse_scale_factor; + let rect = texture_atlases + .get(&atlas_info.texture_atlas) + .unwrap() + .textures[atlas_info.location.glyph_index] + .as_rect(); extracted_uinodes.glyphs.push(ExtractedGlyph { - transform: transform - * Mat4::from_translation(position.extend(0.) * inverse_scale_factor), + transform: transform * Mat4::from_translation(position.extend(0.)), rect, }); @@ -762,7 +719,7 @@ pub fn extract_text_sections( camera_entity: render_camera_entity.id(), rect, item: ExtractedUiItem::Glyphs { - atlas_scaling: Vec2::splat(inverse_scale_factor), + atlas_scaling: Vec2::ONE, range: start..end, }, main_entity: entity.into(), @@ -795,7 +752,6 @@ struct UiVertex { pub size: [f32; 2], /// Position relative to the center of the UI node. pub point: [f32; 2], - pub inverse_scale_factor: f32, } #[derive(Resource)] @@ -846,13 +802,13 @@ pub fn queue_uinodes( ui_pipeline: Res, mut pipelines: ResMut>, mut transparent_render_phases: ResMut>, - mut views: Query<(Entity, &ExtractedView, Option<&ExtractedAA>)>, + mut views: Query<(Entity, &ExtractedView, Option<&UiAntiAlias>)>, pipeline_cache: Res, draw_functions: Res>, ) { let draw_function = draw_functions.read().id::(); for (entity, extracted_uinode) in extracted_uinodes.uinodes.iter() { - let Ok((view_entity, view, extracted_aa)) = views.get_mut(extracted_uinode.camera_entity) + let Ok((view_entity, view, ui_anti_alias)) = views.get_mut(extracted_uinode.camera_entity) else { continue; }; @@ -866,7 +822,7 @@ pub fn queue_uinodes( &ui_pipeline, UiPipelineKey { hdr: view.hdr, - anti_alias: extracted_aa.is_some(), + anti_alias: matches!(ui_anti_alias, None | Some(UiAntiAlias::On)), }, ); transparent_phase.add(TransparentUi { @@ -880,7 +836,6 @@ pub fn queue_uinodes( // batch_range will be calculated in prepare_uinodes batch_range: 0..0, extra_index: PhaseItemExtraIndex::NONE, - inverse_scale_factor: extracted_aa.map(|aa| aa.scale_factor).unwrap_or(1.), }); } } @@ -1151,7 +1106,6 @@ pub fn prepare_uinodes( border: [border.left, border.top, border.right, border.bottom], size: rect_size.xy().into(), point: points[i].into(), - inverse_scale_factor: item.inverse_scale_factor, }); } @@ -1255,7 +1209,6 @@ pub fn prepare_uinodes( border: [0.0; 4], size: size.into(), point: [0.0; 2], - inverse_scale_factor: item.inverse_scale_factor, }); } diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index caa3e804dc..dd465515c5 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -74,8 +74,6 @@ impl SpecializedRenderPipeline for UiPipeline { VertexFormat::Float32x2, // position relative to the center VertexFormat::Float32x2, - // inverse scale factor - VertexFormat::Float32, ], ); let shader_defs = if key.anti_alias { diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index cbae1204db..29b2328c2f 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -97,7 +97,6 @@ pub struct TransparentUi { pub draw_function: DrawFunctionId, pub batch_range: Range, pub extra_index: PhaseItemExtraIndex, - pub inverse_scale_factor: f32, } impl PhaseItem for TransparentUi { diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 940bdbfed6..342fd4d380 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -22,7 +22,6 @@ struct VertexOutput { // Position relative to the center of the rectangle. @location(6) point: vec2, - @location(7) @interpolate(flat) scale_factor: f32, @builtin(position) position: vec4, }; @@ -40,7 +39,6 @@ fn vertex( @location(5) border: vec4, @location(6) size: vec2, @location(7) point: vec2, - @location(8) scale_factor: f32, ) -> VertexOutput { var out: VertexOutput; out.uv = vertex_uv; @@ -51,7 +49,6 @@ fn vertex( out.size = size; out.border = border; out.point = point; - out.scale_factor = scale_factor; return out; } @@ -118,9 +115,9 @@ fn sd_inset_rounded_box(point: vec2, size: vec2, radius: vec4, in } // get alpha for antialiasing for sdf -fn antialias(distance: f32, scale_factor: f32) -> f32 { +fn antialias(distance: f32) -> f32 { // Using the fwidth(distance) was causing artifacts, so just use the distance. - return clamp(0.0, 1.0, (0.5 - scale_factor * distance)); + return clamp(0.0, 1.0, (0.5 - distance)); } fn draw(in: VertexOutput, texture_color: vec4) -> vec4 { @@ -151,7 +148,7 @@ fn draw(in: VertexOutput, texture_color: vec4) -> vec4 { // This select statement ensures we only perform anti-aliasing where a non-zero width border // is present, otherwise an outline about the external boundary would be drawn even without // a border. - let t = select(1.0 - step(0.0, border_distance), antialias(border_distance, in.scale_factor), external_distance < internal_distance); + let t = select(1.0 - step(0.0, border_distance), antialias(border_distance), external_distance < internal_distance); #else let t = 1.0 - step(0.0, border_distance); #endif @@ -167,7 +164,7 @@ fn draw_background(in: VertexOutput, texture_color: vec4) -> vec4 { let internal_distance = sd_inset_rounded_box(in.point, in.size, in.radius, in.border); #ifdef ANTI_ALIAS - let t = antialias(internal_distance, in.scale_factor); + let t = antialias(internal_distance); #else let t = 1.0 - step(0.0, internal_distance); #endif diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index c0bd6a0673..23be50063c 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -655,7 +655,6 @@ pub fn queue_ui_material_nodes( ), batch_range: 0..0, extra_index: PhaseItemExtraIndex::NONE, - inverse_scale_factor: 1., }); } } diff --git a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs index 423a57bd1c..dc19fd830b 100644 --- a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs @@ -237,6 +237,7 @@ pub struct ExtractedUiTextureSlice { pub image_scale_mode: SpriteImageMode, pub flip_x: bool, pub flip_y: bool, + pub inverse_scale_factor: f32, pub main_entity: MainEntity, } @@ -331,6 +332,7 @@ pub fn extract_ui_texture_slices( atlas_rect, flip_x: image.flip_x, flip_y: image.flip_y, + inverse_scale_factor: uinode.inverse_scale_factor, main_entity: entity.into(), }, ); @@ -372,7 +374,6 @@ pub fn queue_ui_slices( ), batch_range: 0..0, extra_index: PhaseItemExtraIndex::NONE, - inverse_scale_factor: 1., }); } } @@ -609,7 +610,7 @@ pub fn prepare_ui_slices( let [slices, border, repeat] = compute_texture_slices( image_size, - uinode_rect.size(), + uinode_rect.size() * texture_slices.inverse_scale_factor, &texture_slices.image_scale_mode, ); diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 05fb3ca4d3..4a867b90ff 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -22,7 +22,7 @@ pub struct ComputedNode { /// The order of the node in the UI layout. /// Nodes with a higher stack index are drawn on top of and receive interactions before nodes with lower stack indices. pub(crate) stack_index: u32, - /// The size of the node as width and height in logical pixels + /// The size of the node as width and height in physical pixels /// /// automatically calculated by [`super::layout::ui_layout_system`] pub(crate) size: Vec2, @@ -37,29 +37,34 @@ pub struct ComputedNode { /// /// Automatically calculated by [`super::layout::ui_layout_system`]. pub(crate) outline_offset: f32, - /// The unrounded size of the node as width and height in logical pixels. + /// The unrounded size of the node as width and height in physical pixels. /// /// Automatically calculated by [`super::layout::ui_layout_system`]. pub(crate) unrounded_size: Vec2, - /// Resolved border values in logical pixels + /// Resolved border values in physical pixels /// Border updates bypass change detection. /// /// Automatically calculated by [`super::layout::ui_layout_system`]. pub(crate) border: BorderRect, - /// Resolved border radius values in logical pixels. + /// Resolved border radius values in physical pixels. /// Border radius updates bypass change detection. /// /// Automatically calculated by [`super::layout::ui_layout_system`]. pub(crate) border_radius: ResolvedBorderRadius, - /// Resolved padding values in logical pixels + /// Resolved padding values in physical pixels /// Padding updates bypass change detection. /// /// Automatically calculated by [`super::layout::ui_layout_system`]. pub(crate) padding: BorderRect, + /// Inverse scale factor for this Node. + /// Multiply physical coordinates by the inverse scale factor to give logical coordinates. + /// + /// Automatically calculated by [`super::layout::ui_layout_system`]. + pub(crate) inverse_scale_factor: f32, } impl ComputedNode { - /// The calculated node size as width and height in logical pixels. + /// The calculated node size as width and height in physical pixels. /// /// Automatically calculated by [`super::layout::ui_layout_system`]. #[inline] @@ -82,7 +87,7 @@ impl ComputedNode { self.stack_index } - /// The calculated node size as width and height in logical pixels before rounding. + /// The calculated node size as width and height in physical pixels before rounding. /// /// Automatically calculated by [`super::layout::ui_layout_system`]. #[inline] @@ -90,7 +95,7 @@ impl ComputedNode { self.unrounded_size } - /// Returns the thickness of the UI node's outline in logical pixels. + /// Returns the thickness of the UI node's outline in physical pixels. /// If this value is negative or `0.` then no outline will be rendered. /// /// Automatically calculated by [`super::layout::ui_layout_system`]. @@ -99,7 +104,7 @@ impl ComputedNode { self.outline_width } - /// Returns the amount of space between the outline and the edge of the node in logical pixels. + /// Returns the amount of space between the outline and the edge of the node in physical pixels. /// /// Automatically calculated by [`super::layout::ui_layout_system`]. #[inline] @@ -139,7 +144,7 @@ impl ComputedNode { } } - /// Returns the thickness of the node's border on each edge in logical pixels. + /// Returns the thickness of the node's border on each edge in physical pixels. /// /// Automatically calculated by [`super::layout::ui_layout_system`]. #[inline] @@ -147,7 +152,7 @@ impl ComputedNode { self.border } - /// Returns the border radius for each of the node's corners in logical pixels. + /// Returns the border radius for each of the node's corners in physical pixels. /// /// Automatically calculated by [`super::layout::ui_layout_system`]. #[inline] @@ -155,7 +160,7 @@ impl ComputedNode { self.border_radius } - /// Returns the inner border radius for each of the node's corners in logical pixels. + /// Returns the inner border radius for each of the node's corners in physical pixels. pub fn inner_radius(&self) -> ResolvedBorderRadius { fn clamp_corner(r: f32, size: Vec2, offset: Vec2) -> f32 { let s = 0.5 * size + offset; @@ -177,7 +182,7 @@ impl ComputedNode { } } - /// Returns the thickness of the node's padding on each edge in logical pixels. + /// Returns the thickness of the node's padding on each edge in physical pixels. /// /// Automatically calculated by [`super::layout::ui_layout_system`]. #[inline] @@ -185,7 +190,7 @@ impl ComputedNode { self.padding } - /// Returns the combined inset on each edge including both padding and border thickness in logical pixels. + /// Returns the combined inset on each edge including both padding and border thickness in physical pixels. #[inline] pub const fn content_inset(&self) -> BorderRect { BorderRect { @@ -195,6 +200,13 @@ impl ComputedNode { bottom: self.border.bottom + self.padding.bottom, } } + + /// Returns the inverse of the scale factor for this node. + /// To convert from physical coordinates to logical coordinates multiply by this value. + #[inline] + pub const fn inverse_scale_factor(&self) -> f32 { + self.inverse_scale_factor + } } impl ComputedNode { @@ -207,6 +219,7 @@ impl ComputedNode { border_radius: ResolvedBorderRadius::ZERO, border: BorderRect::ZERO, padding: BorderRect::ZERO, + inverse_scale_factor: 1., }; } @@ -2330,10 +2343,15 @@ impl BorderRadius { } /// Compute the logical border radius for a single corner from the given values - pub fn resolve_single_corner(radius: Val, node_size: Vec2, viewport_size: Vec2) -> f32 { + pub fn resolve_single_corner( + radius: Val, + node_size: Vec2, + viewport_size: Vec2, + scale_factor: f32, + ) -> f32 { match radius { Val::Auto => 0., - Val::Px(px) => px, + Val::Px(px) => px * scale_factor, Val::Percent(percent) => node_size.min_element() * percent / 100., Val::Vw(percent) => viewport_size.x * percent / 100., Val::Vh(percent) => viewport_size.y * percent / 100., @@ -2343,19 +2361,44 @@ impl BorderRadius { .clamp(0., 0.5 * node_size.min_element()) } - pub fn resolve(&self, node_size: Vec2, viewport_size: Vec2) -> ResolvedBorderRadius { + pub fn resolve( + &self, + node_size: Vec2, + viewport_size: Vec2, + scale_factor: f32, + ) -> ResolvedBorderRadius { ResolvedBorderRadius { - top_left: Self::resolve_single_corner(self.top_left, node_size, viewport_size), - top_right: Self::resolve_single_corner(self.top_right, node_size, viewport_size), - bottom_left: Self::resolve_single_corner(self.bottom_left, node_size, viewport_size), - bottom_right: Self::resolve_single_corner(self.bottom_right, node_size, viewport_size), + top_left: Self::resolve_single_corner( + self.top_left, + node_size, + viewport_size, + scale_factor, + ), + top_right: Self::resolve_single_corner( + self.top_right, + node_size, + viewport_size, + scale_factor, + ), + bottom_left: Self::resolve_single_corner( + self.bottom_left, + node_size, + viewport_size, + scale_factor, + ), + bottom_right: Self::resolve_single_corner( + self.bottom_right, + node_size, + viewport_size, + scale_factor, + ), } } } /// Represents the resolved border radius values for a UI node. /// -/// The values are in logical pixels. +/// The values are in physical pixels. #[derive(Copy, Clone, Debug, Default, PartialEq, Reflect)] pub struct ResolvedBorderRadius { pub top_left: f32, diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index 428f0a6c30..049c797ea1 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -115,7 +115,8 @@ fn update_clipping( clip_rect.max.x -= clip_inset.right; clip_rect.max.y -= clip_inset.bottom; - clip_rect = clip_rect.inflate(node.overflow_clip_margin.margin.max(0.)); + clip_rect = clip_rect + .inflate(node.overflow_clip_margin.margin.max(0.) / computed_node.inverse_scale_factor); if node.overflow.x == OverflowAxis::Visible { clip_rect.min.x = -f32::INFINITY; diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index 43df6aaeeb..55fb343737 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -279,10 +279,15 @@ pub fn update_image_content_size_system( continue; } - if let Some(size) = match &image.texture_atlas { - Some(atlas) => atlas.texture_rect(&atlases).map(|t| t.size()), - None => textures.get(&image.image).map(Image::size), - } { + if let Some(size) = + image + .rect + .map(|rect| rect.size().as_uvec2()) + .or_else(|| match &image.texture_atlas { + Some(atlas) => atlas.texture_rect(&atlases).map(|t| t.size()), + None => textures.get(&image.image).map(Image::size), + }) + { // Update only if size or scale factor has changed to avoid needless layout calculations if size != image_size.size || combined_scale_factor != *previous_combined_scale_factor diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 1007a4cc78..9d13270aaa 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -350,10 +350,7 @@ fn queue_text( TextBounds::UNBOUNDED } else { // `scale_factor` is already multiplied by `UiScale` - TextBounds::new( - node.unrounded_size.x * scale_factor, - node.unrounded_size.y * scale_factor, - ) + TextBounds::new(node.unrounded_size.x, node.unrounded_size.y) }; let text_layout_info = text_layout_info.into_inner(); @@ -398,12 +395,7 @@ fn queue_text( #[allow(clippy::too_many_arguments)] pub fn text_system( mut textures: ResMut>, - mut scale_factors_buffer: Local>, - mut last_scale_factors: Local>, fonts: Res>, - camera_query: Query<(Entity, &Camera)>, - default_ui_camera: DefaultUiCamera, - ui_scale: Res, mut texture_atlases: ResMut>, mut font_atlas_sets: ResMut, mut text_pipeline: ResMut, @@ -414,40 +406,13 @@ pub fn text_system( &mut TextLayoutInfo, &mut TextNodeFlags, &mut ComputedTextBlock, - Option<&TargetCamera>, )>, mut text_reader: TextUiReader, mut font_system: ResMut, mut swash_cache: ResMut, ) { - scale_factors_buffer.clear(); - - for (entity, node, block, text_layout_info, text_flags, mut computed, maybe_camera) in - &mut text_query - { - let Some(camera_entity) = maybe_camera - .map(TargetCamera::entity) - .or(default_ui_camera.get()) - else { - continue; - }; - let scale_factor = match scale_factors_buffer.entry(camera_entity) { - Entry::Occupied(entry) => *entry.get(), - Entry::Vacant(entry) => *entry.insert( - camera_query - .get(camera_entity) - .ok() - .and_then(|(_, c)| c.target_scaling_factor()) - .unwrap_or(1.0) - * ui_scale.0, - ), - }; - let inverse_scale_factor = scale_factor.recip(); - - if last_scale_factors.get(&camera_entity) != Some(&scale_factor) - || node.is_changed() - || text_flags.needs_recompute - { + for (entity, node, block, text_layout_info, text_flags, mut computed) in &mut text_query { + if node.is_changed() || text_flags.needs_recompute { queue_text( entity, &fonts, @@ -455,8 +420,8 @@ pub fn text_system( &mut font_atlas_sets, &mut texture_atlases, &mut textures, - scale_factor, - inverse_scale_factor, + node.inverse_scale_factor.recip(), + node.inverse_scale_factor, block, node, text_flags, @@ -468,5 +433,4 @@ pub fn text_system( ); } } - core::mem::swap(&mut *last_scale_factors, &mut *scale_factors_buffer); } diff --git a/crates/bevy_utils/Cargo.toml b/crates/bevy_utils/Cargo.toml index c1555d8ba9..6eb9a32020 100644 --- a/crates/bevy_utils/Cargo.toml +++ b/crates/bevy_utils/Cargo.toml @@ -18,7 +18,6 @@ std = [ "ahash/runtime-rng", ] alloc = ["hashbrown/default"] -detailed_trace = [] serde = ["hashbrown/serde"] [dependencies] diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 1865f62cf9..694c341465 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -411,7 +411,7 @@ pub fn error(result: Result<(), E>) { #[macro_export] macro_rules! detailed_trace { ($($tts:tt)*) => { - if cfg!(detailed_trace) { + if cfg!(feature = "detailed_trace") { $crate::tracing::trace!($($tts)*); } } diff --git a/deny.toml b/deny.toml index 1d72763947..dda02ee289 100644 --- a/deny.toml +++ b/deny.toml @@ -25,6 +25,7 @@ allow = [ exceptions = [ { name = "unicode-ident", allow = [ "Unicode-DFS-2016", + "Unicode-3.0", ] }, { name = "symphonia", allow = [ "MPL-2.0", diff --git a/docs/cargo_features.md b/docs/cargo_features.md index e9ad2e10c1..1cc83b9e11 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -25,7 +25,6 @@ The default feature set enables most of the expected features of a game engine, |bevy_mesh_picking_backend|Provides an implementation for picking meshes| |bevy_pbr|Adds PBR rendering| |bevy_picking|Provides picking functionality| -|bevy_remote|Enable the Bevy Remote Protocol| |bevy_render|Provides rendering functionality| |bevy_scene|Provides scene functionality| |bevy_sprite|Provides sprite functionality| @@ -33,7 +32,7 @@ The default feature set enables most of the expected features of a game engine, |bevy_state|Enable built in global state machines| |bevy_text|Provides text functionality| |bevy_ui|A custom ECS-driven UI framework| -|bevy_ui_picking_backend|Provides an implementation for picking ui| +|bevy_ui_picking_backend|Provides an implementation for picking UI| |bevy_window|Windowing layer| |bevy_winit|winit window and input backend| |custom_cursor|Enable winit custom cursor support| @@ -62,6 +61,7 @@ The default feature set enables most of the expected features of a game engine, |bevy_ci_testing|Enable systems that allow for automated testing on CI| |bevy_debug_stepping|Enable stepping-based debugging of Bevy systems| |bevy_dev_tools|Provides a collection of developer tools| +|bevy_remote|Enable the Bevy Remote Protocol| |bmp|BMP image format support| |dds|DDS compressed texture support| |debug_glam_assert|Enable assertions in debug builds to check the validity of parameters passed to glam| diff --git a/examples/3d/visibility_range.rs b/examples/3d/visibility_range.rs index 64c368655f..6fdbdf13a9 100644 --- a/examples/3d/visibility_range.rs +++ b/examples/3d/visibility_range.rs @@ -26,10 +26,12 @@ const MIN_ZOOM_DISTANCE: f32 = 0.5; static NORMAL_VISIBILITY_RANGE_HIGH_POLY: VisibilityRange = VisibilityRange { start_margin: 0.0..0.0, end_margin: 3.0..4.0, + use_aabb: false, }; static NORMAL_VISIBILITY_RANGE_LOW_POLY: VisibilityRange = VisibilityRange { start_margin: 3.0..4.0, end_margin: 8.0..9.0, + use_aabb: false, }; // A visibility model that we use to always show a model (until the camera is so @@ -37,12 +39,14 @@ static NORMAL_VISIBILITY_RANGE_LOW_POLY: VisibilityRange = VisibilityRange { static SINGLE_MODEL_VISIBILITY_RANGE: VisibilityRange = VisibilityRange { start_margin: 0.0..0.0, end_margin: 8.0..9.0, + use_aabb: false, }; // A visibility range that we use to completely hide a model. static INVISIBLE_VISIBILITY_RANGE: VisibilityRange = VisibilityRange { start_margin: 0.0..0.0, end_margin: 0.0..0.0, + use_aabb: false, }; // Allows us to identify the main model. diff --git a/examples/animation/animated_fox.rs b/examples/animation/animated_fox.rs index db17bbecc0..c4b59d5fb5 100644 --- a/examples/animation/animated_fox.rs +++ b/examples/animation/animated_fox.rs @@ -34,8 +34,7 @@ struct Animations { graph: Handle, } -#[derive(Event, AnimationEvent, Reflect, Clone)] -#[reflect(AnimationEvent)] +#[derive(Event, Reflect, Clone)] struct OnStep; fn observe_on_step( diff --git a/examples/animation/animation_events.rs b/examples/animation/animation_events.rs index 60f1d8a7db..177fa2f47f 100644 --- a/examples/animation/animation_events.rs +++ b/examples/animation/animation_events.rs @@ -11,39 +11,27 @@ fn main() { .add_plugins(DefaultPlugins) .add_event::() .add_systems(Startup, setup) - .add_systems(PreUpdate, (animate_text_opacity, edit_message)) + .add_systems(Update, animate_text_opacity) + .add_observer(edit_message) .run(); } #[derive(Component)] struct MessageText; -#[derive(Event, Reflect, Clone)] -#[reflect(AnimationEvent)] +#[derive(Event, Clone)] struct MessageEvent { value: String, color: Color, } -// AnimationEvent can also be derived, but doing so will -// trigger it as an observer event which is triggered in PostUpdate. -// We need to set the message text before that so it is -// updated before rendering without a one frame delay. -impl AnimationEvent for MessageEvent { - fn trigger(&self, _time: f32, _weight: f32, _entity: Entity, world: &mut World) { - world.send_event(self.clone()); - } -} - fn edit_message( - mut event_reader: EventReader, + trigger: Trigger, text: Single<(&mut Text2d, &mut TextColor), With>, ) { let (mut text, mut color) = text.into_inner(); - for event in event_reader.read() { - text.0 = event.value.clone(); - color.0 = event.color; - } + text.0 = trigger.event().value.clone(); + color.0 = trigger.event().color; } fn setup( diff --git a/examples/input/gamepad_input.rs b/examples/input/gamepad_input.rs index d81e6fd836..4f3dfba4ae 100644 --- a/examples/input/gamepad_input.rs +++ b/examples/input/gamepad_input.rs @@ -11,18 +11,18 @@ fn main() { fn gamepad_system(gamepads: Query<(Entity, &Gamepad)>) { for (entity, gamepad) in &gamepads { - if gamepad.digital.just_pressed(GamepadButton::South) { + if gamepad.just_pressed(GamepadButton::South) { info!("{:?} just pressed South", entity); - } else if gamepad.digital.just_released(GamepadButton::South) { + } else if gamepad.just_released(GamepadButton::South) { info!("{:?} just released South", entity); } - let right_trigger = gamepad.analog.get(GamepadButton::RightTrigger2).unwrap(); + let right_trigger = gamepad.get(GamepadButton::RightTrigger2).unwrap(); if right_trigger.abs() > 0.01 { info!("{:?} RightTrigger2 value is {}", entity, right_trigger); } - let left_stick_x = gamepad.analog.get(GamepadAxis::LeftStickX).unwrap(); + let left_stick_x = gamepad.get(GamepadAxis::LeftStickX).unwrap(); if left_stick_x.abs() > 0.01 { info!("{:?} LeftStickX value is {}", entity, left_stick_x); } diff --git a/examples/input/gamepad_rumble.rs b/examples/input/gamepad_rumble.rs index c3262daf52..cdc5a9c85e 100644 --- a/examples/input/gamepad_rumble.rs +++ b/examples/input/gamepad_rumble.rs @@ -19,7 +19,7 @@ fn gamepad_system( mut rumble_requests: EventWriter, ) { for (entity, gamepad) in &gamepads { - if gamepad.digital.just_pressed(GamepadButton::North) { + if gamepad.just_pressed(GamepadButton::North) { info!( "North face button: strong (low-frequency) with low intensity for rumble for 5 seconds. Press multiple times to increase intensity." ); @@ -30,7 +30,7 @@ fn gamepad_system( }); } - if gamepad.digital.just_pressed(GamepadButton::East) { + if gamepad.just_pressed(GamepadButton::East) { info!("East face button: maximum rumble on both motors for 5 seconds"); rumble_requests.send(GamepadRumbleRequest::Add { gamepad: entity, @@ -39,7 +39,7 @@ fn gamepad_system( }); } - if gamepad.digital.just_pressed(GamepadButton::South) { + if gamepad.just_pressed(GamepadButton::South) { info!("South face button: low-intensity rumble on the weak motor for 0.5 seconds"); rumble_requests.send(GamepadRumbleRequest::Add { gamepad: entity, @@ -48,7 +48,7 @@ fn gamepad_system( }); } - if gamepad.digital.just_pressed(GamepadButton::West) { + if gamepad.just_pressed(GamepadButton::West) { info!("West face button: custom rumble intensity for 5 second"); rumble_requests.send(GamepadRumbleRequest::Add { gamepad: entity, @@ -62,7 +62,7 @@ fn gamepad_system( }); } - if gamepad.digital.just_pressed(GamepadButton::Start) { + if gamepad.just_pressed(GamepadButton::Start) { info!("Start button: Interrupt the current rumble"); rumble_requests.send(GamepadRumbleRequest::Stop { gamepad: entity }); } diff --git a/examples/tools/gamepad_viewer.rs b/examples/tools/gamepad_viewer.rs index 35d4fd8ab3..32549893c6 100644 --- a/examples/tools/gamepad_viewer.rs +++ b/examples/tools/gamepad_viewer.rs @@ -395,10 +395,10 @@ fn update_buttons( ) { for gamepad in &gamepads { for (mut handle, react_to) in query.iter_mut() { - if gamepad.digital.just_pressed(**react_to) { + if gamepad.just_pressed(**react_to) { *handle = materials.active.clone(); } - if gamepad.digital.just_released(**react_to) { + if gamepad.just_released(**react_to) { *handle = materials.normal.clone(); } } diff --git a/examples/ui/overflow_debug.rs b/examples/ui/overflow_debug.rs index 13f2c362a2..cfa1fb4348 100644 --- a/examples/ui/overflow_debug.rs +++ b/examples/ui/overflow_debug.rs @@ -233,13 +233,13 @@ fn update_animation( fn update_transform( animation: Res, - mut containers: Query<(&mut Transform, &mut Node, &T)>, + mut containers: Query<(&mut Transform, &mut Node, &ComputedNode, &T)>, ) { - for (mut transform, mut node, update_transform) in &mut containers { + for (mut transform, mut node, computed_node, update_transform) in &mut containers { update_transform.update(animation.t, &mut transform); - node.left = Val::Px(transform.translation.x); - node.top = Val::Px(transform.translation.y); + node.left = Val::Px(transform.translation.x * computed_node.inverse_scale_factor()); + node.top = Val::Px(transform.translation.y * computed_node.inverse_scale_factor()); } } diff --git a/examples/ui/ui_texture_slice_flip_and_tile.rs b/examples/ui/ui_texture_slice_flip_and_tile.rs index ccf9e7d174..7b8153965a 100644 --- a/examples/ui/ui_texture_slice_flip_and_tile.rs +++ b/examples/ui/ui_texture_slice_flip_and_tile.rs @@ -51,26 +51,24 @@ fn setup(mut commands: Commands, asset_server: Res) { ..default() }) .with_children(|parent| { - for ([width, height], flip_x, flip_y) in [ - ([160., 160.], false, false), - ([320., 160.], false, true), - ([320., 160.], true, false), - ([160., 160.], true, true), - ] { - parent.spawn(( - ImageNode { - image: image.clone(), - flip_x, - flip_y, - image_mode: NodeImageMode::Sliced(slicer.clone()), - ..default() - }, - Node { - width: Val::Px(width), - height: Val::Px(height), - ..default() - }, - )); + for [columns, rows] in [[3., 3.], [4., 4.], [5., 4.], [4., 5.], [5., 5.]] { + for (flip_x, flip_y) in [(false, false), (false, true), (true, false), (true, true)] + { + parent.spawn(( + ImageNode { + image: image.clone(), + flip_x, + flip_y, + image_mode: NodeImageMode::Sliced(slicer.clone()), + ..default() + }, + Node { + width: Val::Px(16. * columns), + height: Val::Px(16. * rows), + ..default() + }, + )); + } } }); } diff --git a/tools/publish.sh b/tools/publish.sh index 20eb0c13c4..6576430791 100644 --- a/tools/publish.sh +++ b/tools/publish.sh @@ -34,7 +34,6 @@ crates=( bevy_core_pipeline bevy_input bevy_gilrs - bevy_animation/derive bevy_animation bevy_pbr bevy_gltf