use std::collections::HashMap;
use bevy::ecs::query::QueryData;
use bevy::prelude::*;
use bevy::ui::UiTransform;
use super::protocol::{AnimatableProperty, AnimatedBindings, Binding};
#[cfg(test)]
mod ext_tests;
mod filter_params;
mod gradient;
mod node_colors;
#[cfg(test)]
mod tests;
use filter_params::apply_filter_params;
use node_colors::stage_node_and_colors;
use super::{AnimatedNode, SharedValues, build_ui_transform, eval_color, eval_scalar, props};
#[derive(QueryData)]
#[query_data(mutable)]
pub(crate) struct AnimTargets {
transform: &'static mut UiTransform,
bg: Option<&'static mut BackgroundColor>,
border: Option<&'static mut BorderColor>,
text: Option<&'static mut TextColor>,
image: Option<&'static mut ImageNode>,
node: Option<&'static mut Node>,
promoted: Option<&'static crate::layer::PromotedLayer>,
layer_alpha: Option<&'static mut crate::layer::LayerGroupAlpha>,
resolved_filter: Option<&'static mut crate::filters::ResolvedFilterChain>,
resolved_backdrop: Option<&'static mut crate::filters::ResolvedBackdropChain>,
resolved_morph: Option<&'static mut crate::filters::ResolvedMorphChain>,
rnode: Option<&'static crate::bridge::ReactNode>,
transform3d: Option<&'static mut crate::layer::transform3d::LayerTransform3d>,
ext: Option<&'static mut crate::ext::DrivenExtValues>,
gradient_input: Option<&'static crate::ui_map::GradientTargets>,
bg_gradient: Option<&'static mut bevy::ui::BackgroundGradient>,
border_gradient: Option<&'static mut bevy::ui::BorderGradient>,
}
type AnyTargetChanged = Or<(
Changed<AnimatedNode>,
Changed<UiTransform>,
Changed<BackgroundColor>,
Changed<BorderColor>,
Changed<TextColor>,
Changed<ImageNode>,
Changed<Node>,
Changed<crate::layer::PromotedLayer>,
Changed<crate::layer::LayerGroupAlpha>,
Changed<crate::filters::ResolvedFilterChain>,
Changed<crate::filters::ResolvedBackdropChain>,
Changed<crate::filters::ResolvedMorphChain>,
Changed<crate::layer::transform3d::LayerTransform3d>,
Changed<crate::ext::DrivenExtValues>,
Or<(
Changed<crate::ui_map::GradientTargets>,
Changed<bevy::ui::BackgroundGradient>,
Changed<bevy::ui::BorderGradient>,
)>,
)>;
#[derive(bevy::ecs::system::SystemParam)]
pub(crate) struct RemovedTargets<'w, 's> {
anim: RemovedComponents<'w, 's, AnimatedNode>,
bg: RemovedComponents<'w, 's, BackgroundColor>,
border: RemovedComponents<'w, 's, BorderColor>,
text: RemovedComponents<'w, 's, TextColor>,
image: RemovedComponents<'w, 's, ImageNode>,
node: RemovedComponents<'w, 's, Node>,
promoted: RemovedComponents<'w, 's, crate::layer::PromotedLayer>,
layer_alpha: RemovedComponents<'w, 's, crate::layer::LayerGroupAlpha>,
resolved_filter: RemovedComponents<'w, 's, crate::filters::ResolvedFilterChain>,
resolved_backdrop: RemovedComponents<'w, 's, crate::filters::ResolvedBackdropChain>,
resolved_morph: RemovedComponents<'w, 's, crate::filters::ResolvedMorphChain>,
transform3d: RemovedComponents<'w, 's, crate::layer::transform3d::LayerTransform3d>,
ext: RemovedComponents<'w, 's, crate::ext::DrivenExtValues>,
gradient_input: RemovedComponents<'w, 's, crate::ui_map::GradientTargets>,
bg_gradient: RemovedComponents<'w, 's, bevy::ui::BackgroundGradient>,
border_gradient: RemovedComponents<'w, 's, bevy::ui::BorderGradient>,
}
impl RemovedTargets<'_, '_> {
super::removed_any!(
anim,
bg,
border,
text,
image,
node,
promoted,
layer_alpha,
resolved_filter,
resolved_backdrop,
resolved_morph,
transform3d,
ext,
gradient_input,
bg_gradient,
border_gradient
);
}
pub(crate) struct ValidationMemory<S>(HashMap<Entity, S>);
impl<S> Default for ValidationMemory<S> {
fn default() -> Self {
Self(HashMap::new())
}
}
impl<S: PartialEq> ValidationMemory<S> {
pub(super) fn should_validate(&self, entity: Entity, current: &S) -> bool {
self.0.get(&entity) != Some(current)
}
pub(super) fn stamp(&mut self, entity: Entity, validated_now: bool, pre: &S, post: S) {
if validated_now || &post != pre {
self.0.insert(entity, post);
}
}
pub(super) fn prune(&mut self, live: &[Entity]) {
if self.0.len() > live.len() {
self.0.retain(|e, _| live.contains(e));
}
}
}
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
pub(crate) fn apply_animated_nodes(
mut commands: Commands,
values: Res<SharedValues>,
mut dirt: ResMut<crate::layer::LayerContentDirt>,
mut validated: Local<ValidationMemory<(Option<u32>, Option<u32>, Option<u32>)>>,
mut gradient_validated: Local<ValidationMemory<()>>,
mut queries: ParamSet<(
Query<(), (With<AnimatedNode>, AnyTargetChanged)>,
Query<(Entity, Ref<AnimatedNode>, AnimTargets)>,
)>,
mut removed: RemovedTargets,
) {
if !values.is_changed() && queries.p0().is_empty() && !removed.any() {
return;
}
let mut filter_bound: Vec<Entity> = Vec::new();
let mut gradient_bound: Vec<Entity> = Vec::new();
let mut query = queries.p1();
for (entity, anim, mut t) in &mut query {
let b = &anim.0;
let promoted = t.promoted.is_some();
stage_transform(entity, b, &values, promoted, &mut dirt, &mut t);
stage_transform3d(b, &values, &mut t);
let opacity_alpha = b
.get(AnimatableProperty::Opacity)
.and_then(|x| eval_scalar(x, &values));
stage_node_and_colors(
entity,
&mut commands,
b,
&values,
opacity_alpha,
promoted,
&mut dirt,
&mut t,
);
if let Some(alpha) = opacity_alpha {
write_final_alpha(
entity,
alpha,
promoted,
t.layer_alpha.as_mut(),
t.bg.as_mut(),
t.text.as_mut(),
t.image.as_mut(),
&mut dirt,
);
}
stage_filter_params(
entity,
anim.is_changed(),
b,
&values,
&mut validated,
&mut filter_bound,
&mut dirt,
&mut t,
);
stage_ext(b, &values, &mut t);
stage_gradient_params(
entity,
anim.is_changed(),
b,
&values,
opacity_alpha,
promoted,
&mut gradient_validated,
&mut gradient_bound,
&mut dirt,
&mut t,
);
}
validated.prune(&filter_bound);
gradient_validated.prune(&gradient_bound);
}
pub(crate) fn push_transform_dirt(
entity: Entity,
old: &UiTransform,
new: &UiTransform,
promoted: bool,
dirt: &mut crate::layer::LayerContentDirt,
) {
let translate_only = old.scale == new.scale && old.rotation == new.rotation;
if promoted && translate_only {
dirt.composite_only.push(entity);
} else {
dirt.nodes.push(entity);
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn write_final_alpha(
entity: Entity,
alpha: f32,
promoted: bool,
layer_alpha: Option<&mut Mut<crate::layer::LayerGroupAlpha>>,
bg: Option<&mut Mut<BackgroundColor>>,
text: Option<&mut Mut<TextColor>>,
image: Option<&mut Mut<ImageNode>>,
dirt: &mut crate::layer::LayerContentDirt,
) {
if promoted {
if let Some(la) = layer_alpha
&& la.0 != alpha
{
la.0 = alpha;
dirt.composite_only.push(entity);
}
return;
}
let mut wrote = false;
if let Some(c) = bg
&& c.0.alpha() != alpha
{
c.0 = c.0.with_alpha(alpha);
wrote = true;
}
if let Some(tc) = text
&& tc.0.alpha() != alpha
{
tc.0 = tc.0.with_alpha(alpha);
wrote = true;
}
if let Some(img) = image
&& img.color.alpha() != alpha
{
img.color = img.color.with_alpha(alpha);
wrote = true;
}
if wrote {
dirt.nodes.push(entity);
}
}
fn stage_transform(
entity: Entity,
b: &AnimatedBindings,
values: &SharedValues,
promoted: bool,
dirt: &mut crate::layer::LayerContentDirt,
t: &mut AnimTargetsItem,
) {
use AnimatableProperty as P;
if !b.has_stage(props::PropStage::Transform) {
return;
}
let new = build_ui_transform(
b.get(P::TranslateX)
.and_then(|x| eval_scalar(x, values))
.map(Val::Px),
b.get(P::TranslateY)
.and_then(|x| eval_scalar(x, values))
.map(Val::Px),
b.get(P::Scale).and_then(|x| eval_scalar(x, values)),
b.get(P::ScaleX).and_then(|x| eval_scalar(x, values)),
b.get(P::ScaleY).and_then(|x| eval_scalar(x, values)),
b.get(P::Rotate)
.and_then(|x| eval_scalar(x, values))
.map(f32::to_radians),
);
if *t.transform != new {
push_transform_dirt(entity, &t.transform, &new, promoted, dirt);
*t.transform = new;
}
}
fn stage_transform3d(b: &AnimatedBindings, values: &SharedValues, t: &mut AnimTargetsItem) {
use AnimatableProperty as P;
if !b.has_stage(props::PropStage::Transform3d) {
return;
}
let Some(t3d) = &mut t.transform3d else {
return;
};
use crate::animations::protocol::Transform3dField as F;
use crate::protocol::animatable::Animatable::Static;
use crate::protocol::{transform::Transform3dOrigin, units::Angle, units::Length};
let mut new = t3d.0.clone();
for (property, binding) in b.iter() {
if !matches!(property, P::Transform3d(_)) {
continue;
}
let Some(v) = eval_scalar(binding, values) else {
continue;
};
let deg = || Some(Static(Angle::from_radians(v.to_radians())));
let origin =
|o: &crate::protocol::transform::Transform3d| o.origin.clone().unwrap_or_default();
macro_rules! rule {
($prop:tt, (t3d $f:ident num $d:tt)) => {
if property == &$prop {
new.$f = Some(Static(v));
}
};
($prop:tt, (t3d $f:ident angle)) => {
if property == &$prop {
new.$f = deg();
}
};
($prop:tt, (t3d_origin x)) => {
if property == &$prop {
new.origin = Some(Transform3dOrigin {
x: Static(Length::Px(v)),
y: origin(&new).y,
});
}
};
($prop:tt, (t3d_origin y)) => {
if property == &$prop {
new.origin = Some(Transform3dOrigin {
x: origin(&new).x,
y: Static(Length::Px(v)),
});
}
};
($prop:tt, $other:tt) => {};
}
macro_rules! walk {
($(($prop:tt, $acc:tt, $write:tt, $stage:ident, $park:ident),)*) => {
$(rule!($prop, $acc);)*
};
}
props::with_animatable_props!(walk);
}
if t3d.0 != new {
t3d.0 = new;
}
}
#[allow(clippy::too_many_arguments)]
fn stage_filter_params(
entity: Entity,
anim_changed: bool,
b: &AnimatedBindings,
values: &SharedValues,
validated: &mut ValidationMemory<(Option<u32>, Option<u32>, Option<u32>)>,
filter_bound: &mut Vec<Entity>,
dirt: &mut crate::layer::LayerContentDirt,
t: &mut AnimTargetsItem,
) {
let has_filter = b.has_stage(props::PropStage::Filter);
let has_backdrop = b.has_stage(props::PropStage::Backdrop);
let has_morph = b.has_stage(props::PropStage::Morph);
if !(has_filter || has_backdrop || has_morph) {
return;
}
filter_bound.push(entity);
let pre = (
t.resolved_filter.as_ref().map(|c| c.version),
t.resolved_backdrop.as_ref().map(|c| c.0.version),
t.resolved_morph.as_ref().map(|c| c.0.version),
);
let validate = anim_changed || validated.should_validate(entity, &pre);
if has_filter {
apply_filter_params(
entity,
b,
values,
t.resolved_filter.as_mut(),
t.rnode,
validate,
dirt,
filter_params::ChainDomain::Filter,
);
}
if has_backdrop {
let mut backdrop = t
.resolved_backdrop
.as_mut()
.map(|m| m.reborrow().map_unchanged(|b| &mut b.0));
apply_filter_params(
entity,
b,
values,
backdrop.as_mut(),
t.rnode,
validate,
dirt,
filter_params::ChainDomain::Backdrop,
);
}
if has_morph {
let mut morph = t
.resolved_morph
.as_mut()
.map(|m| m.reborrow().map_unchanged(|b| &mut b.0));
apply_filter_params(
entity,
b,
values,
morph.as_mut(),
t.rnode,
validate,
dirt,
filter_params::ChainDomain::Morph,
);
}
let post = (
t.resolved_filter.as_ref().map(|c| c.version),
t.resolved_backdrop.as_ref().map(|c| c.0.version),
t.resolved_morph.as_ref().map(|c| c.0.version),
);
validated.stamp(entity, validate, &pre, post);
}
fn stage_ext(b: &AnimatedBindings, values: &SharedValues, t: &mut AnimTargetsItem) {
use crate::ext::{DrivenExt, DrivenValue};
let Some(ext) = t.ext.as_mut() else {
return; };
let next: Vec<DrivenExt> = b
.iter()
.filter_map(|(property, binding)| {
let AnimatableProperty::Ext { domain, name } = property else {
return None;
};
let value = match binding {
Binding::InterpolateColor { .. } => {
DrivenValue::Color(eval_color(binding, values)?)
}
_ => DrivenValue::Scalar(eval_scalar(binding, values)?),
};
Some(DrivenExt {
domain,
name: name.clone(),
value,
})
})
.collect();
if ext.0 != next {
ext.0 = next;
}
}
#[allow(clippy::too_many_arguments)]
fn stage_gradient_params(
entity: Entity,
anim_changed: bool,
b: &AnimatedBindings,
values: &SharedValues,
opacity_alpha: Option<f32>,
promoted: bool,
gradient_validated: &mut ValidationMemory<()>,
gradient_bound: &mut Vec<Entity>,
dirt: &mut crate::layer::LayerContentDirt,
t: &mut AnimTargetsItem,
) {
if !b.has_stage(props::PropStage::Gradient) {
return;
}
gradient_bound.push(entity);
let validate = anim_changed || gradient_validated.should_validate(entity, &());
gradient::apply_gradient_params(
entity,
b,
values,
t.gradient_input,
t.bg_gradient.as_mut(),
t.border_gradient.as_mut(),
opacity_alpha,
promoted,
t.rnode,
validate,
dirt,
);
gradient_validated.stamp(entity, validate, &(), ());
}