use bevy::prelude::*;
use bevy::ui::{Gradient, RadialGradientShape};
use super::super::protocol::{AnimatableProperty, AnimatedBindings, Binding, GradientLeaf};
use super::super::{SharedValues, eval_color, eval_scalar};
const SURFACES: [(&str, bool); 2] = [("backgroundGradient", false), ("borderGradient", true)];
type WarnSink<'a> = &'a dyn Fn(bool, &dyn Fn() -> (String, String));
fn leaf_key(surface: &str, index: u8, leaf: GradientLeaf) -> String {
let suffix = match leaf {
GradientLeaf::Angle => "angle".to_string(),
GradientLeaf::StopColor(i) => format!("stops[{i}].color"),
GradientLeaf::StopPosition(i) => format!("stops[{i}].position"),
GradientLeaf::StopHint(i) => format!("stops[{i}].hint"),
GradientLeaf::ShapeX => "shape.x".to_string(),
GradientLeaf::ShapeY => "shape.y".to_string(),
};
format!("{surface}[{index}].{suffix}")
}
#[allow(clippy::too_many_arguments)] pub(super) fn apply_gradient_params(
entity: Entity,
bindings: &AnimatedBindings,
values: &SharedValues,
input: Option<&crate::ui_map::GradientTargets>,
bg: Option<&mut Mut<bevy::ui::BackgroundGradient>>,
border: Option<&mut Mut<bevy::ui::BorderGradient>>,
opacity_alpha: Option<f32>,
promoted: bool,
rnode: Option<&crate::bridge::ReactNode>,
validate: bool,
dirt: &mut crate::layer::LayerContentDirt,
) {
let _diag = rnode.map(|r| crate::diag::node_scope(r.0));
let fold = opacity_alpha
.filter(|_| !promoted)
.or(input.and_then(|i| i.opacity));
let bg = bg.map(|m| m.reborrow().map_unchanged(|g| &mut g.0));
let border = border.map(|m| m.reborrow().map_unchanged(|g| &mut g.0));
for ((surface, is_border), component) in SURFACES.into_iter().zip([bg, border]) {
let stamp = input.and_then(|i| {
if is_border {
i.border.as_ref()
} else {
i.background.as_ref()
}
});
apply_surface(
entity, surface, is_border, bindings, values, stamp, component, fold, validate, dirt,
);
}
}
fn surface_leaf(property: &AnimatableProperty, is_border: bool) -> Option<(u8, GradientLeaf)> {
match (property, is_border) {
(AnimatableProperty::BackgroundGradientParam { index, leaf }, false)
| (AnimatableProperty::BorderGradientParam { index, leaf }, true) => Some((*index, *leaf)),
_ => None,
}
}
#[allow(clippy::too_many_arguments)] fn apply_surface(
entity: Entity,
surface: &'static str,
is_border: bool,
bindings: &AnimatedBindings,
values: &SharedValues,
stamp: Option<&Vec<Gradient>>,
component: Option<Mut<Vec<Gradient>>>,
fold: Option<f32>,
validate: bool,
dirt: &mut crate::layer::LayerContentDirt,
) {
let warn = |validate: bool, make: &dyn Fn() -> (String, String)| {
crate::diag::report_if(validate, "gradientBinding", make)
};
let mut bound = bindings
.iter()
.filter_map(|(p, b)| surface_leaf(p, is_border).map(|a| (a, b)))
.peekable();
if bound.peek().is_none() {
return; }
let Some(stamp) = stamp else {
for ((index, leaf), _) in bound {
warn(validate, &|| {
let key = leaf_key(surface, index, leaf);
let msg = format!(
"binding {key}: the node has no resolved {surface} to drive \
(no `{surface}` style) — binding ignored"
);
(key, msg)
});
}
return;
};
let mut rebuilt = stamp.clone();
for ((index, leaf), binding) in bound {
let Some(gradient) = rebuilt.get_mut(index as usize) else {
warn(validate, &|| {
let key = leaf_key(surface, index, leaf);
let msg = format!(
"{key}: the {surface} list has no entry at index {index} — binding ignored"
);
(key, msg)
});
continue;
};
apply_leaf(
gradient, surface, index, leaf, binding, values, validate, &warn,
);
}
let folded = crate::ui_map::fold_gradients(&rebuilt, fold);
if let Some(mut component) = component
&& *component != folded
{
*component = folded;
dirt.nodes.push(entity);
}
}
#[allow(clippy::too_many_arguments)] fn apply_leaf(
gradient: &mut Gradient,
surface: &'static str,
index: u8,
leaf: GradientLeaf,
binding: &Binding,
values: &SharedValues,
validate: bool,
warn: WarnSink,
) {
let kind_miss = |what: &str| {
let key = leaf_key(surface, index, leaf);
let msg = format!("{key}: {what} — binding ignored");
(key, msg)
};
if let GradientLeaf::StopColor(si) = leaf {
let Some(rgba) = eval_color(binding, values) else {
if !matches!(binding, Binding::InterpolateColor { .. }) {
warn(validate, &|| {
kind_miss("stop colors are colors — bind an interpolateColor, not a scalar")
});
}
return;
};
let color = Color::srgba(rgba[0], rgba[1], rgba[2], rgba[3]);
let written = match gradient {
Gradient::Linear(l) => l.stops.get_mut(si as usize).map(|s| s.color = color),
Gradient::Radial(r) => r.stops.get_mut(si as usize).map(|s| s.color = color),
Gradient::Conic(c) => c.stops.get_mut(si as usize).map(|s| s.color = color),
};
if written.is_none() {
warn(validate, &|| {
kind_miss(&format!("the gradient has no stop at index {si}"))
});
}
return;
}
let Some(v) = eval_scalar(binding, values) else {
if matches!(binding, Binding::InterpolateColor { .. }) {
warn(validate, &|| {
kind_miss("this leaf is a scalar — an interpolateColor binding cannot drive it")
});
}
return;
};
match leaf {
GradientLeaf::StopColor(_) => unreachable!("handled above"),
GradientLeaf::Angle => match gradient {
Gradient::Linear(l) => l.angle = v.to_radians(),
Gradient::Conic(c) => c.start = v.to_radians(),
Gradient::Radial(_) => {
warn(validate, &|| {
kind_miss("a radial gradient has no angle leaf")
});
}
},
GradientLeaf::StopPosition(si) => {
let written = match gradient {
Gradient::Linear(l) => l.stops.get_mut(si as usize).map(|s| s.point = Val::Px(v)),
Gradient::Radial(r) => r.stops.get_mut(si as usize).map(|s| s.point = Val::Px(v)),
Gradient::Conic(c) => c
.stops
.get_mut(si as usize)
.map(|s| s.angle = Some(v.to_radians())),
};
if written.is_none() {
warn(validate, &|| {
kind_miss(&format!("the gradient has no stop at index {si}"))
});
}
}
GradientLeaf::StopHint(si) => {
let h = v.clamp(0.0, 1.0);
let written = match gradient {
Gradient::Linear(l) => l.stops.get_mut(si as usize).map(|s| s.hint = h),
Gradient::Radial(r) => r.stops.get_mut(si as usize).map(|s| s.hint = h),
Gradient::Conic(c) => c.stops.get_mut(si as usize).map(|s| s.hint = h),
};
if written.is_none() {
warn(validate, &|| {
kind_miss(&format!("the gradient has no stop at index {si}"))
});
}
}
GradientLeaf::ShapeX => match gradient {
Gradient::Radial(r) => match &mut r.shape {
RadialGradientShape::Circle(radius) => *radius = Val::Px(v),
RadialGradientShape::Ellipse(x, _) => *x = Val::Px(v),
_ => warn(validate, &|| {
kind_miss("the radial shape is a keyword — no radius to drive")
}),
},
_ => warn(validate, &|| {
kind_miss("only a radial gradient has shape radii")
}),
},
GradientLeaf::ShapeY => match gradient {
Gradient::Radial(r) => match &mut r.shape {
RadialGradientShape::Ellipse(_, y) => *y = Val::Px(v),
_ => warn(validate, &|| {
kind_miss("only an ellipse has a Y radius to drive")
}),
},
_ => warn(validate, &|| {
kind_miss("only a radial gradient has shape radii")
}),
},
}
}