mirror of
https://github.com/bevyengine/bevy
synced 2024-11-23 13:13:49 +00:00
Merge remote-tracking branch 'upstream/main' into brp-watch-id-unwatch
This commit is contained in:
commit
35cebdd939
66 changed files with 974 additions and 830 deletions
20
Cargo.toml
20
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"
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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
|
|
@ -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()
|
||||
}
|
|
@ -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<dyn AnimationEvent>,
|
||||
) -> 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<Say>) {
|
||||
/// println!("{}", trigger.event().0);
|
||||
/// }
|
||||
///
|
||||
/// fn setup_animation(
|
||||
/// mut commands: Commands,
|
||||
/// mut animations: ResMut<Assets<AnimationClip>>,
|
||||
/// mut graphs: ResMut<Assets<AnimationGraph>>,
|
||||
/// ) {
|
||||
/// // 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<dyn AnimationEvent>`
|
||||
fn clone_value(&self) -> Box<dyn AnimationEvent>;
|
||||
}
|
||||
|
||||
impl<T: AnimationEvent + Clone> CloneableAnimationEvent for T {
|
||||
fn clone_value(&self) -> Box<dyn AnimationEvent> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// The data that will be used to trigger an animation event.
|
||||
#[derive(TypePath)]
|
||||
pub(crate) struct AnimationEventData(pub(crate) Box<dyn AnimationEvent>);
|
||||
|
||||
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<dyn AnimationEvent>`, which can't be automatically derived yet.
|
||||
impl GetTypeRegistration for AnimationEventData {
|
||||
fn get_type_registration() -> TypeRegistration {
|
||||
let mut registration = TypeRegistration::of::<Self>();
|
||||
registration.insert::<ReflectFromPtr>(FromType::<Self>::from_type());
|
||||
registration
|
||||
}
|
||||
}
|
||||
|
||||
// We have to implement `Typed` manually because of the embedded
|
||||
// `Box<dyn AnimationEvent>`, 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::<Self>(&[UnnamedField::new::<()>(0)]))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// We have to implement `TupleStruct` manually because of the embedded
|
||||
// `Box<dyn AnimationEvent>`, 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<dyn AnimationEvent>`, which can't be automatically derived yet.
|
||||
impl PartialReflect for AnimationEventData {
|
||||
#[inline]
|
||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
||||
Some(<Self as Typed>::type_info())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_partial_reflect(self: Box<Self>) -> Box<dyn PartialReflect> {
|
||||
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<Self>) -> Result<Box<dyn Reflect>, Box<dyn PartialReflect>> {
|
||||
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<Self>) -> ReflectOwned {
|
||||
ReflectOwned::TupleStruct(self)
|
||||
}
|
||||
|
||||
fn clone_value(&self) -> Box<dyn PartialReflect> {
|
||||
Box::new(Clone::clone(self))
|
||||
}
|
||||
}
|
||||
|
||||
// We have to implement `Reflect` manually because of the embedded
|
||||
// `Box<dyn AnimationEvent>`, which can't be automatically derived yet.
|
||||
impl Reflect for AnimationEventData {
|
||||
#[inline]
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
||||
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<Self>) -> Box<dyn Reflect> {
|
||||
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<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
|
||||
*self = value.take()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// We have to implement `FromReflect` manually because of the embedded
|
||||
// `Box<dyn AnimationEvent>`, which can't be automatically derived yet.
|
||||
impl FromReflect for AnimationEventData {
|
||||
fn from_reflect(reflect: &dyn PartialReflect) -> Option<Self> {
|
||||
Some(reflect.try_downcast_ref::<AnimationEventData>()?.clone())
|
||||
}
|
||||
}
|
|
@ -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<dyn Fn(&mut Commands, Entity, f32, f32) + Send + Sync>);
|
||||
|
||||
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,
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -1029,8 +1029,8 @@ impl Components {
|
|||
/// registration will be used.
|
||||
pub(crate) unsafe fn register_required_components<R: Component>(
|
||||
&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,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2345,6 +2345,94 @@ mod tests {
|
|||
assert!(world.entity(id).get::<C>().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::<A, B>();
|
||||
world.register_required_components::<B, C>();
|
||||
world.register_required_components::<C, D>();
|
||||
|
||||
let id = world.spawn(A).id();
|
||||
|
||||
assert!(world.entity(id).get::<D>().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::<A, B>();
|
||||
world.register_required_components::<B, C>();
|
||||
world.register_required_components::<C, D>();
|
||||
world.register_required_components_with::<D, Counter>(|| Counter(2));
|
||||
// This should replace the require constructor in A since it is
|
||||
// shallower.
|
||||
world.register_required_components_with::<C, Counter>(|| Counter(1));
|
||||
|
||||
let id = world.spawn(A).id();
|
||||
|
||||
// The "shallower" of the two components is used.
|
||||
assert_eq!(world.entity(id).get::<Counter>().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::<A, B>();
|
||||
world.register_required_components::<B, C>();
|
||||
world.register_required_components::<C, D>();
|
||||
world.register_required_components::<D, E>();
|
||||
world.register_required_components_with::<E, Counter>(|| Counter(1));
|
||||
world.register_required_components_with::<F, Counter>(|| Counter(2));
|
||||
world.register_required_components::<E, F>();
|
||||
|
||||
let id = world.spawn(A).id();
|
||||
|
||||
// The "shallower" of the two components is used.
|
||||
assert_eq!(world.entity(id).get::<Counter>().unwrap().0, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_required_components_existing_archetype() {
|
||||
#[derive(Component)]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
///
|
||||
|
|
|
@ -515,7 +515,7 @@ impl World {
|
|||
// SAFETY: We just created the `required` and `requiree` components.
|
||||
unsafe {
|
||||
self.components
|
||||
.register_required_components::<R>(required, requiree, constructor)
|
||||
.register_required_components::<R>(requiree, required, constructor)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 })
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Item = T>) -> 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<Item = T>) -> 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<Item = T>) -> 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.
|
||||
|
|
|
@ -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<u16>,
|
||||
pub(crate) vendor_id: Option<u16>,
|
||||
|
||||
/// The USB product ID as assigned by the [vendor], if available.
|
||||
///
|
||||
/// [vendor]: Self::vendor_id
|
||||
pub product_id: Option<u16>,
|
||||
pub(crate) product_id: Option<u16>,
|
||||
|
||||
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state
|
||||
pub digital: ButtonInput<GamepadButton>,
|
||||
pub(crate) digital: ButtonInput<GamepadButton>,
|
||||
|
||||
/// [`Axis`] of [`GamepadButton`] representing their analog state.
|
||||
pub analog: Axis<GamepadInput>,
|
||||
pub(crate) analog: Axis<GamepadInput>,
|
||||
}
|
||||
|
||||
impl Gamepad {
|
||||
/// Returns the USB vendor ID as assigned by the USB-IF, if available.
|
||||
pub fn vendor_id(&self) -> Option<u16> {
|
||||
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<u16> {
|
||||
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<GamepadInput>) -> Option<f32> {
|
||||
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<GamepadInput>) -> Option<f32> {
|
||||
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<Item = GamepadButton>) -> 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<Item = GamepadButton>) -> 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<Item = GamepadButton>) -> 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<Item = GamepadButton>) -> 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<Item = GamepadButton>,
|
||||
) -> 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<Item = GamepadButton>,
|
||||
) -> 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<Item = &GamepadButton> {
|
||||
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<Item = &GamepadButton> {
|
||||
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<Item = &GamepadButton> {
|
||||
self.digital.get_just_released()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all analog [axes].
|
||||
///
|
||||
/// [axes]: GamepadInput
|
||||
pub fn get_analog_axes(&self) -> impl Iterator<Item = &GamepadInput> {
|
||||
self.analog.all_axes()
|
||||
}
|
||||
|
||||
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state
|
||||
pub fn digital(&self) -> &ButtonInput<GamepadButton> {
|
||||
&self.digital
|
||||
}
|
||||
|
||||
/// Mutable [`ButtonInput`] of [`GamepadButton`] representing their digital state. Useful for mocking inputs.
|
||||
pub fn digital_mut(&mut self) -> &mut ButtonInput<GamepadButton> {
|
||||
&mut self.digital
|
||||
}
|
||||
|
||||
/// [`Axis`] of [`GamepadButton`] representing their analog state.
|
||||
pub fn analog(&self) -> &Axis<GamepadInput> {
|
||||
&self.analog
|
||||
}
|
||||
|
||||
/// Mutable [`Axis`] of [`GamepadButton`] representing their analog state. Useful for mocking inputs.
|
||||
pub fn analog_mut(&mut self) -> &mut Axis<GamepadInput> {
|
||||
&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::<Gamepad>(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::<Gamepad>(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::<Gamepad>(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::<Gamepad>(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::<Gamepad>(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::<Events<GamepadButtonStateChangedEvent>>()
|
||||
|
@ -2528,13 +2682,23 @@ mod tests {
|
|||
ctx.update();
|
||||
|
||||
// Check it is flagged for this frame
|
||||
let gamepad = ctx.app.world_mut().get::<Gamepad>(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::<Gamepad>(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]
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -30,6 +30,7 @@ ci_limits = []
|
|||
webgl = ["wgpu/webgl"]
|
||||
webgpu = ["wgpu/webgpu"]
|
||||
ios_simulator = []
|
||||
detailed_trace = []
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
|
|
|
@ -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<Mutex<Option<RenderResources>>>);
|
||||
|
||||
/// 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::<ShaderLoader>();
|
||||
|
||||
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<PrimaryWindow>>,
|
||||
> = 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<PrimaryWindow>>()
|
||||
.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::<FutureRendererResources>()
|
||||
.get_resource::<FutureRenderResources>()
|
||||
.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::<FutureRendererResources>()
|
||||
if let Some(future_render_resources) =
|
||||
app.world_mut().remove_resource::<FutureRenderResources>()
|
||||
{
|
||||
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())
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
/// <https://en.wikipedia.org/wiki/Euclidean_algorithm#Implementations>
|
||||
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) {
|
||||
|
|
|
@ -172,7 +172,7 @@ impl Deref for BindGroup {
|
|||
/// * The field's [`Handle<Storage>`](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 |
|
||||
/// |------------------------|-------------------------------------------------------------------------|----------------------|
|
||||
|
|
|
@ -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<RenderResources> for RenderCreation {
|
||||
fn from(value: RenderResources) -> Self {
|
||||
Self::Manual(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<V> = hashbrown::HashMap<MainEntity, V, EntityHash>;
|
|||
pub type MainEntityHashSet = hashbrown::HashSet<MainEntity, EntityHash>;
|
||||
|
||||
/// 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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -19,12 +19,44 @@ struct View {
|
|||
world_from_clip: mat4x4<f32>,
|
||||
world_from_view: mat4x4<f32>,
|
||||
view_from_world: mat4x4<f32>,
|
||||
// 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<f32>,
|
||||
view_from_clip: mat4x4<f32>,
|
||||
world_position: vec3<f32>,
|
||||
exposure: f32,
|
||||
// viewport(x_origin, y_origin, width, height)
|
||||
viewport: vec4<f32>,
|
||||
// 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<vec4<f32>, 6>,
|
||||
color_grading: ColorGrading,
|
||||
mip_bias: f32,
|
||||
|
|
|
@ -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<f32>,
|
||||
|
||||
/// 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<RenderVisibilityRanges>,
|
||||
visibility_ranges_query: Extract<Query<(Entity, &VisibilityRange)>>,
|
||||
changed_ranges_query: Extract<Query<Entity, Changed<VisibilityRange>>>,
|
||||
mut removed_visibility_ranges: Extract<RemovedComponents<VisibilityRange>>,
|
||||
) {
|
||||
if changed_ranges_query.is_empty() {
|
||||
if changed_ranges_query.is_empty() && removed_visibility_ranges.read().next().is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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<Shader> = Handle::weak_from_u128(2763343953151597127);
|
||||
pub const SPRITE_VIEW_BINDINGS_SHADER_HANDLE: Handle<Shader> =
|
||||
|
@ -135,7 +147,9 @@ impl Plugin for SpritePlugin {
|
|||
);
|
||||
|
||||
#[cfg(feature = "bevy_sprite_picking_backend")]
|
||||
if self.add_picking {
|
||||
app.add_plugins(picking_backend::SpritePickingPlugin);
|
||||
}
|
||||
|
||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<Font>) -> 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<Font>) -> 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;
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<ButtonInput<MouseButton>>,
|
||||
touches_input: Res<Touches>,
|
||||
ui_scale: Res<UiScale>,
|
||||
ui_stack: Res<UiStack>,
|
||||
mut node_query: Query<NodeQuery>,
|
||||
) {
|
||||
|
@ -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,
|
||||
|
|
|
@ -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<f32>| 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,16 +379,21 @@ 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
|
||||
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.)
|
||||
|
@ -394,8 +401,10 @@ with UI components as a child of an entity without UI components, your UI layout
|
|||
0.
|
||||
};
|
||||
|
||||
node.outline_offset = outline
|
||||
.offset
|
||||
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.);
|
||||
|
@ -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::<ComputedNode>(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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")]
|
||||
if self.add_picking {
|
||||
app.add_plugins(picking_backend::UiPickingPlugin);
|
||||
}
|
||||
|
||||
if !self.enable_rendering {
|
||||
return;
|
||||
|
|
|
@ -65,7 +65,6 @@ pub fn ui_picking(
|
|||
camera_query: Query<(Entity, &Camera, Has<IsDefaultUiCamera>)>,
|
||||
default_ui_camera: DefaultUiCamera,
|
||||
primary_window: Query<Entity, With<PrimaryWindow>>,
|
||||
ui_scale: Res<UiScale>,
|
||||
ui_stack: Res<UiStack>,
|
||||
node_query: Query<NodeQuery>,
|
||||
mut output: EventWriter<PointerHits>,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<ExtractedBoxShadows>,
|
||||
default_ui_camera: Extract<DefaultUiCamera>,
|
||||
ui_scale: Extract<Res<UiScale>>,
|
||||
camera_query: Extract<Query<(Entity, &Camera)>>,
|
||||
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<ExtractedBoxShadows>,
|
||||
ui_slicer_pipeline: Res<BoxShadowPipeline>,
|
||||
extracted_box_shadows: ResMut<ExtractedBoxShadows>,
|
||||
box_shadow_pipeline: Res<BoxShadowPipeline>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<BoxShadowPipeline>>,
|
||||
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
|
||||
mut views: Query<(Entity, &ExtractedView, Option<&UiBoxShadowSamples>)>,
|
||||
|
@ -342,7 +342,7 @@ pub fn queue_shadows(
|
|||
draw_functions: Res<DrawFunctions<TransparentUi>>,
|
||||
) {
|
||||
let draw_function = draw_functions.read().id::<DrawBoxShadows>();
|
||||
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<BoxShadowMeta>,
|
||||
mut extracted_shadows: ResMut<ExtractedBoxShadows>,
|
||||
view_uniforms: Res<ViewUniforms>,
|
||||
texture_slicer_pipeline: Res<BoxShadowPipeline>,
|
||||
box_shadow_pipeline: Res<BoxShadowPipeline>,
|
||||
mut phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
|
||||
mut previous_len: Local<usize>,
|
||||
) {
|
||||
|
@ -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),
|
||||
));
|
||||
|
||||
|
|
|
@ -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<ViewSortedRenderPhases<TransparentUi>>,
|
||||
ui_scale: Extract<Res<UiScale>>,
|
||||
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<ExtractedUiNodes>,
|
||||
camera_query: Extract<Query<&Camera>>,
|
||||
default_ui_camera: Extract<DefaultUiCamera>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
||||
ui_scale: Extract<Res<UiScale>>,
|
||||
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<UiPipeline>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<UiPipeline>>,
|
||||
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
|
||||
mut views: Query<(Entity, &ExtractedView, Option<&ExtractedAA>)>,
|
||||
mut views: Query<(Entity, &ExtractedView, Option<&UiAntiAlias>)>,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
draw_functions: Res<DrawFunctions<TransparentUi>>,
|
||||
) {
|
||||
let draw_function = draw_functions.read().id::<DrawUi>();
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -97,7 +97,6 @@ pub struct TransparentUi {
|
|||
pub draw_function: DrawFunctionId,
|
||||
pub batch_range: Range<u32>,
|
||||
pub extra_index: PhaseItemExtraIndex,
|
||||
pub inverse_scale_factor: f32,
|
||||
}
|
||||
|
||||
impl PhaseItem for TransparentUi {
|
||||
|
|
|
@ -22,7 +22,6 @@ struct VertexOutput {
|
|||
|
||||
// Position relative to the center of the rectangle.
|
||||
@location(6) point: vec2<f32>,
|
||||
@location(7) @interpolate(flat) scale_factor: f32,
|
||||
@builtin(position) position: vec4<f32>,
|
||||
};
|
||||
|
||||
|
@ -40,7 +39,6 @@ fn vertex(
|
|||
@location(5) border: vec4<f32>,
|
||||
@location(6) size: vec2<f32>,
|
||||
@location(7) point: vec2<f32>,
|
||||
@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<f32>, size: vec2<f32>, radius: vec4<f32>, 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<f32>) -> vec4<f32> {
|
||||
|
@ -151,7 +148,7 @@ fn draw(in: VertexOutput, texture_color: vec4<f32>) -> vec4<f32> {
|
|||
// 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<f32>) -> vec4<f32> {
|
|||
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
|
||||
|
|
|
@ -655,7 +655,6 @@ pub fn queue_ui_material_nodes<M: UiMaterial>(
|
|||
),
|
||||
batch_range: 0..0,
|
||||
extra_index: PhaseItemExtraIndex::NONE,
|
||||
inverse_scale_factor: 1.,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -279,10 +279,15 @@ pub fn update_image_content_size_system(
|
|||
continue;
|
||||
}
|
||||
|
||||
if let Some(size) = match &image.texture_atlas {
|
||||
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
|
||||
|
|
|
@ -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<Assets<Image>>,
|
||||
mut scale_factors_buffer: Local<EntityHashMap<f32>>,
|
||||
mut last_scale_factors: Local<EntityHashMap<f32>>,
|
||||
fonts: Res<Assets<Font>>,
|
||||
camera_query: Query<(Entity, &Camera)>,
|
||||
default_ui_camera: DefaultUiCamera,
|
||||
ui_scale: Res<UiScale>,
|
||||
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
|
||||
mut font_atlas_sets: ResMut<FontAtlasSets>,
|
||||
mut text_pipeline: ResMut<TextPipeline>,
|
||||
|
@ -414,40 +406,13 @@ pub fn text_system(
|
|||
&mut TextLayoutInfo,
|
||||
&mut TextNodeFlags,
|
||||
&mut ComputedTextBlock,
|
||||
Option<&TargetCamera>,
|
||||
)>,
|
||||
mut text_reader: TextUiReader,
|
||||
mut font_system: ResMut<CosmicFontSystem>,
|
||||
mut swash_cache: ResMut<SwashCache>,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ std = [
|
|||
"ahash/runtime-rng",
|
||||
]
|
||||
alloc = ["hashbrown/default"]
|
||||
detailed_trace = []
|
||||
serde = ["hashbrown/serde"]
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -411,7 +411,7 @@ pub fn error<E: Debug>(result: Result<(), E>) {
|
|||
#[macro_export]
|
||||
macro_rules! detailed_trace {
|
||||
($($tts:tt)*) => {
|
||||
if cfg!(detailed_trace) {
|
||||
if cfg!(feature = "detailed_trace") {
|
||||
$crate::tracing::trace!($($tts)*);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ allow = [
|
|||
exceptions = [
|
||||
{ name = "unicode-ident", allow = [
|
||||
"Unicode-DFS-2016",
|
||||
"Unicode-3.0",
|
||||
] },
|
||||
{ name = "symphonia", allow = [
|
||||
"MPL-2.0",
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -34,8 +34,7 @@ struct Animations {
|
|||
graph: Handle<AnimationGraph>,
|
||||
}
|
||||
|
||||
#[derive(Event, AnimationEvent, Reflect, Clone)]
|
||||
#[reflect(AnimationEvent)]
|
||||
#[derive(Event, Reflect, Clone)]
|
||||
struct OnStep;
|
||||
|
||||
fn observe_on_step(
|
||||
|
|
|
@ -11,39 +11,27 @@ fn main() {
|
|||
.add_plugins(DefaultPlugins)
|
||||
.add_event::<MessageEvent>()
|
||||
.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<MessageEvent>,
|
||||
trigger: Trigger<MessageEvent>,
|
||||
text: Single<(&mut Text2d, &mut TextColor), With<MessageText>>,
|
||||
) {
|
||||
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(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ fn gamepad_system(
|
|||
mut rumble_requests: EventWriter<GamepadRumbleRequest>,
|
||||
) {
|
||||
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 });
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -233,13 +233,13 @@ fn update_animation(
|
|||
|
||||
fn update_transform<T: UpdateTransform + Component>(
|
||||
animation: Res<AnimationState>,
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,12 +51,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
..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),
|
||||
] {
|
||||
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(),
|
||||
|
@ -66,11 +63,12 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
..default()
|
||||
},
|
||||
Node {
|
||||
width: Val::Px(width),
|
||||
height: Val::Px(height),
|
||||
width: Val::Px(16. * columns),
|
||||
height: Val::Px(16. * rows),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ crates=(
|
|||
bevy_core_pipeline
|
||||
bevy_input
|
||||
bevy_gilrs
|
||||
bevy_animation/derive
|
||||
bevy_animation
|
||||
bevy_pbr
|
||||
bevy_gltf
|
||||
|
|
Loading…
Reference in a new issue