use bevy::prelude::*;
use crate::bridge::{FocusState, RNode, StyleVariants};
use crate::ui_map::overlay_style;
#[allow(clippy::type_complexity)]
pub fn apply_interaction_styles(
mut commands: Commands,
query: Query<
(
Entity,
Option<&Interaction>,
Option<&FocusState>,
&StyleVariants,
Option<&crate::layer::PromotedLayer>,
),
Or<(
Changed<Interaction>,
Changed<FocusState>,
Changed<StyleVariants>,
)>,
>,
rnodes: Query<&RNode>,
assets: Res<AssetServer>,
bridge: Option<Res<crate::bridge::JsBridge>>,
) {
for (entity, interaction, focus, variants, promoted) in &query {
let mut style = match interaction {
Some(Interaction::Pressed) => overlay_style(
&overlay_style(&variants.base, &variants.hover),
&variants.press,
),
Some(Interaction::Hovered) => overlay_style(&variants.base, &variants.hover),
_ => variants.base.clone(),
};
if focus.is_some_and(|f| f.0) {
style = overlay_style(&style, &variants.focus);
}
let rnode = rnodes.get(entity).ok();
let _diag = rnode.map(|r| crate::diag::node_scope(r.0));
let mut ec = commands.entity(entity);
crate::ui_map::apply_style_promoted(&mut ec, &style, promoted.is_some());
let foreign = bridge
.as_ref()
.zip(rnode)
.is_some_and(|(b, r)| b.foreign_images.contains(&r.0));
if !foreign {
crate::background_image::apply_background_image(
&mut ec,
&style,
crate::protocol::style::StyleDirty::ALL,
promoted.is_some(),
&assets,
);
}
}
}
#[cfg(test)]
mod tests {
use super::super::test_util::op_app;
use super::*;
use crate::bridge::JsBridge;
use crate::protocol::op::Op;
use bevy::ui::widget::ImageNode;
#[test]
fn hover_variant_swaps_background_image() {
let (mut app, ops_tx) = op_app();
app.add_systems(
Update,
apply_interaction_styles.after(crate::reconcile::apply_js_ops),
);
ops_tx
.send(vec![
Op::Create {
id: 1,
kind: "node".into(),
props: serde_json::from_value(serde_json::json!({
"style": { "backgroundImage": { "src": "a.png" } },
"hoverStyle": { "backgroundImage": { "src": "b.png" } },
}))
.unwrap(),
text: None,
},
Op::Create {
id: 2,
kind: "node".into(),
props: serde_json::from_value(serde_json::json!({
"hoverStyle": { "backgroundImage": { "src": "b.png" } },
}))
.unwrap(),
text: None,
},
])
.unwrap();
app.update();
let bridge = app.world().resource::<JsBridge>();
let (e1, e2) = (bridge.nodes[&1], bridge.nodes[&2]);
let base_handle = app
.world()
.entity(e1)
.get::<ImageNode>()
.unwrap()
.image
.clone();
app.world_mut().entity_mut(e1).insert(Interaction::Hovered);
app.world_mut().entity_mut(e2).insert(Interaction::Hovered);
app.update();
let hover_handle = app
.world()
.entity(e1)
.get::<ImageNode>()
.unwrap()
.image
.clone();
assert_ne!(
base_handle, hover_handle,
"hover swaps the background image handle"
);
assert!(
app.world().entity(e2).get::<ImageNode>().is_some(),
"a hover-only spec mounts the image on hover-in"
);
app.world_mut().entity_mut(e1).insert(Interaction::None);
app.world_mut().entity_mut(e2).insert(Interaction::None);
app.update();
assert_eq!(
app.world().entity(e1).get::<ImageNode>().unwrap().image,
base_handle,
"hover-out restores the base image"
);
assert!(
app.world().entity(e2).get::<ImageNode>().is_none(),
"hover-out removes the image when the base has no spec"
);
}
}