use bevy::ecs::entity::{EntityHashMap, EntityHashSet};
use bevy::prelude::*;
use bevy::ui::widget::text_system;
use smallvec::SmallVec;
use crate::prelude::*;
use crate::sickle::DynamicStylePostUpdate;
const ALPHA_ROUNDING_ERROR: f32 = 0.0000001;
fn color_alpha(color: &Color) -> f32
{
match color {
Color::Srgba(Srgba { alpha, .. })
| Color::LinearRgba(LinearRgba { alpha, .. })
| Color::Hsla(Hsla { alpha, .. })
| Color::Hsva(Hsva { alpha, .. })
| Color::Hwba(Hwba { alpha, .. })
| Color::Laba(Laba { alpha, .. })
| Color::Lcha(Lcha { alpha, .. })
| Color::Oklaba(Oklaba { alpha, .. })
| Color::Oklcha(Oklcha { alpha, .. })
| Color::Xyza(Xyza { alpha, .. }) => *alpha,
}
}
fn set_color_alpha(color: &mut Color, new_alpha: f32)
{
match color {
Color::Srgba(Srgba { alpha, .. })
| Color::LinearRgba(LinearRgba { alpha, .. })
| Color::Hsla(Hsla { alpha, .. })
| Color::Hsva(Hsva { alpha, .. })
| Color::Hwba(Hwba { alpha, .. })
| Color::Laba(Laba { alpha, .. })
| Color::Lcha(Lcha { alpha, .. })
| Color::Oklaba(Oklaba { alpha, .. })
| Color::Oklcha(Oklcha { alpha, .. })
| Color::Xyza(Xyza { alpha, .. }) => {
*alpha = new_alpha;
}
}
}
#[derive(Component, Clone, Debug, Default)]
struct RestorableOpacity
{
ui_image: f32,
text: SmallVec<[f32; 1]>,
border_color: (f32, f32, f32, f32),
background_color: f32,
box_shadows: SmallVec<[f32; 4]>,
}
fn recursively_propagate_opacity_value(
mut first_traversal: bool,
mut accumulated_opacity: f32,
seen_propagators: &mut EntityHashSet,
insertion_first_traversal_vals: &mut EntityHashMap<RestorableOpacity>,
c: &mut Commands,
writer: &mut TextUiWriter,
children_query: &Query<&Children>,
nodes: &mut Query<
(
Option<&PropagateOpacity>,
Option<&mut RestorableOpacity>,
Option<&mut ImageNode>,
Has<Text>,
Option<&mut BorderColor>,
Option<&mut BackgroundColor>,
Option<&mut BoxShadow>,
),
With<Node>,
>,
entity: Entity,
)
{
let Ok((
maybe_propagator,
maybe_restorable,
maybe_img,
has_text,
maybe_br_color,
maybe_bg_color,
maybe_box_shadow,
)) = nodes.get_mut(entity)
else {
return;
};
if let Some(PropagateOpacity(value)) = maybe_propagator {
if !seen_propagators.insert(entity) {
first_traversal = false;
}
if !value.is_nan() && *value != 1.0 {
accumulated_opacity *= *value;
}
}
if first_traversal && (accumulated_opacity - 1.0).abs() <= ALPHA_ROUNDING_ERROR {
return;
}
if maybe_img.is_some()
|| has_text
|| maybe_br_color.is_some()
|| maybe_bg_color.is_some()
|| maybe_box_shadow.is_some()
{
let update_restorable = |restorable: &mut RestorableOpacity| {
if let Some(mut img) = maybe_img {
if first_traversal {
restorable.ui_image = color_alpha(&img.color);
}
let computed = restorable.ui_image * accumulated_opacity;
if (color_alpha(&img.color) - computed).abs() > ALPHA_ROUNDING_ERROR {
set_color_alpha(&mut img.color, computed);
}
}
if has_text {
if first_traversal {
restorable.text.clear();
}
let mut idx = 0;
writer.for_each_color(entity, |mut color| {
if first_traversal {
restorable.text.push(color_alpha(&color));
}
let original = restorable.text[idx];
let computed = original * accumulated_opacity;
if (color_alpha(&color) - computed).abs() > ALPHA_ROUNDING_ERROR {
set_color_alpha(&mut color, computed);
}
idx += 1;
});
}
if let Some(mut br_color) = maybe_br_color {
if first_traversal {
restorable.border_color.0 = color_alpha(&br_color.top);
restorable.border_color.1 = color_alpha(&br_color.bottom);
restorable.border_color.2 = color_alpha(&br_color.left);
restorable.border_color.3 = color_alpha(&br_color.right);
}
let computed_top = restorable.border_color.0 * accumulated_opacity;
if (color_alpha(&br_color.top) - computed_top).abs() > ALPHA_ROUNDING_ERROR {
set_color_alpha(&mut br_color.top, computed_top);
}
let computed_bottom = restorable.border_color.1 * accumulated_opacity;
if (color_alpha(&br_color.bottom) - computed_bottom).abs() > ALPHA_ROUNDING_ERROR {
set_color_alpha(&mut br_color.bottom, computed_bottom);
}
let computed_left = restorable.border_color.2 * accumulated_opacity;
if (color_alpha(&br_color.left) - computed_left).abs() > ALPHA_ROUNDING_ERROR {
set_color_alpha(&mut br_color.left, computed_left);
}
let computed_right = restorable.border_color.3 * accumulated_opacity;
if (color_alpha(&br_color.right) - computed_right).abs() > ALPHA_ROUNDING_ERROR {
set_color_alpha(&mut br_color.right, computed_right);
}
}
if let Some(mut bg_color) = maybe_bg_color {
if first_traversal {
restorable.background_color = color_alpha(&bg_color.0);
}
let computed = restorable.background_color * accumulated_opacity;
if (color_alpha(&bg_color.0) - computed).abs() > ALPHA_ROUNDING_ERROR {
set_color_alpha(&mut bg_color.0, computed);
}
}
if let Some(mut box_shadow) = maybe_box_shadow {
if first_traversal {
restorable.box_shadows =
SmallVec::from_iter(box_shadow.iter().map(|bs| color_alpha(&bs.color)));
}
box_shadow
.iter_mut()
.zip(restorable.box_shadows.iter())
.map(|(bs, opacity)| {
let computed = opacity * accumulated_opacity;
if (color_alpha(&bs.color) - computed).abs() > ALPHA_ROUNDING_ERROR {
set_color_alpha(&mut bs.color, computed);
}
})
.count();
}
};
if let Some(restorable) = maybe_restorable {
update_restorable(restorable.into_inner());
} else {
let mut restorable = insertion_first_traversal_vals
.get(&entity)
.cloned()
.unwrap_or_default();
update_restorable(&mut restorable);
if first_traversal {
insertion_first_traversal_vals.insert(entity, restorable.clone());
c.entity(entity).insert(restorable);
}
}
}
let Ok(children) = children_query.get(entity) else { return };
for child in children.iter() {
recursively_propagate_opacity_value(
first_traversal,
accumulated_opacity,
seen_propagators,
insertion_first_traversal_vals,
c,
writer,
children_query,
nodes,
child,
);
}
}
fn propagate_opacity_values(
mut seen_propagators: Local<EntityHashSet>,
mut insertion_first_traversal_vals: Local<EntityHashMap<RestorableOpacity>>,
mut c: Commands,
mut writer: TextUiWriter,
propagators: Query<Entity, With<PropagateOpacity>>,
children: Query<&Children>,
mut nodes: Query<
(
// Include this in case we need to merge modifiers.
Option<&PropagateOpacity>,
Option<&mut RestorableOpacity>,
Option<&mut ImageNode>,
Has<Text>,
Option<&mut BorderColor>,
Option<&mut BackgroundColor>,
Option<&mut BoxShadow>,
),
With<Node>,
>,
)
{
seen_propagators.clear();
insertion_first_traversal_vals.clear();
for propagator in propagators.iter() {
if seen_propagators.contains(&propagator) {
continue;
}
recursively_propagate_opacity_value(
true,
1.0,
&mut *seen_propagators,
&mut *insertion_first_traversal_vals,
&mut c,
&mut writer,
&children,
&mut nodes,
propagator,
);
}
}
fn restore_opacity(
mut nodes: Query<
(
Entity,
&RestorableOpacity,
Option<&mut ImageNode>,
Has<Text>,
Option<&mut BorderColor>,
Option<&mut BackgroundColor>,
Option<&mut BoxShadow>,
),
Changed<RestorableOpacity>,
>,
mut text_writer: TextUiWriter,
)
{
for (entity, restorable, maybe_img, has_text, maybe_br_color, maybe_bg_color, maybe_box_shadow) in
nodes.iter_mut()
{
if let Some(mut img) = maybe_img {
if color_alpha(&img.color) != restorable.ui_image {
set_color_alpha(&mut img.color, restorable.ui_image);
}
}
if has_text {
let mut idx = 0;
text_writer.for_each_until(entity, |_, _, _, _, mut color| {
let Some(restorable) = restorable.text.get(idx) else {
return false;
};
set_color_alpha(&mut color, *restorable);
idx += 1;
true
});
}
if let Some(mut br_color) = maybe_br_color {
if color_alpha(&br_color.top) != restorable.border_color.0 {
set_color_alpha(&mut br_color.top, restorable.border_color.0);
}
if color_alpha(&br_color.bottom) != restorable.border_color.1 {
set_color_alpha(&mut br_color.bottom, restorable.border_color.1);
}
if color_alpha(&br_color.left) != restorable.border_color.2 {
set_color_alpha(&mut br_color.left, restorable.border_color.2);
}
if color_alpha(&br_color.right) != restorable.border_color.3 {
set_color_alpha(&mut br_color.right, restorable.border_color.3);
}
}
if let Some(mut bg_color) = maybe_bg_color {
if color_alpha(&bg_color.0) != restorable.background_color {
set_color_alpha(&mut bg_color.0, restorable.background_color);
}
}
if let Some(mut box_shadow) = maybe_box_shadow {
box_shadow
.iter_mut()
.zip(restorable.box_shadows.iter())
.map(|(bs, opacity)| {
if color_alpha(&bs.color) != *opacity {
set_color_alpha(&mut bs.color, *opacity);
}
})
.count();
}
}
}
#[derive(Component, AnimatedNewtype, Reflect, Default, Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct PropagateOpacity(pub f32);
pub(crate) struct UiOpacityPlugin;
impl Plugin for UiOpacityPlugin
{
fn build(&self, app: &mut App)
{
app.register_animatable::<PropagateOpacity>()
.add_systems(
PostUpdate,
propagate_opacity_values
.after(ControlSet)
.after(DynamicStylePostUpdate)
.before(text_system),
)
.add_systems(First, restore_opacity);
}
}