1use bevy::{
2 ecs::{
3 component::ComponentTicks,
4 system::{SystemParam, SystemState},
5 },
6 log::{debug, error, trace},
7 prelude::{
8 AssetEvent, AssetId, Assets, Changed, Children, Component, Deref, DerefMut, Entity,
9 EventReader, Mut, Name, Query, Res, ResMut, Resource, With, World,
10 },
11 ui::{Interaction, Node},
12 utils::HashMap,
13};
14use smallvec::SmallVec;
15
16use crate::{
17 component::{Class, MatchSelectorElement, StyleSheet},
18 property::{SelectedEntities, StyleSheetState, TrackedEntities},
19 selector::{PseudoClassElement, Selector, SelectorElement},
20 StyleSheetAsset,
21};
22
23pub(crate) trait ComponentFilter {
26 fn filter(&mut self, world: &World) -> SmallVec<[Entity; 8]>;
28
29 fn get_change_ticks(&self, world: &World, entity: Entity) -> Option<ComponentTicks>;
31}
32
33impl<'w, 's, T: Component> ComponentFilter for SystemState<Query<'w, 's, Entity, With<T>>> {
34 fn filter(&mut self, world: &World) -> SmallVec<[Entity; 8]> {
35 self.get(world).iter().collect()
36 }
37
38 fn get_change_ticks(&self, world: &World, entity: Entity) -> Option<ComponentTicks> {
39 world.entity(entity).get_change_ticks::<T>()
40 }
41}
42
43#[derive(Default, Resource, Deref, DerefMut)]
45pub(crate) struct ComponentFilterRegistry(
46 pub HashMap<&'static str, Box<dyn ComponentFilter + Send + Sync>>,
47);
48
49#[derive(SystemParam)]
51pub(crate) struct CssQueryParam<'w, 's> {
52 assets: Res<'w, Assets<StyleSheetAsset>>,
53 nodes: Query<
54 'w,
55 's,
56 (Entity, Option<&'static Children>, &'static StyleSheet),
57 Changed<StyleSheet>,
58 >,
59 names: Query<'w, 's, (Entity, &'static Name)>,
60 classes: Query<'w, 's, (Entity, &'static Class)>,
61 children: Query<'w, 's, &'static Children, With<Node>>,
62 any: Query<'w, 's, Entity, With<Node>>,
63}
64
65#[derive(Deref, DerefMut, Resource)]
67pub(crate) struct PrepareParams(SystemState<CssQueryParam<'static, 'static>>);
68
69impl PrepareParams {
70 pub fn new(world: &mut World) -> Self {
71 Self(SystemState::new(world))
72 }
73}
74
75pub(crate) fn prepare(world: &mut World) {
77 world.resource_scope(|world, mut params: Mut<PrepareParams>| {
78 world.resource_scope(|world, mut registry: Mut<ComponentFilterRegistry>| {
79 let css_query = params.get(world);
80 let state = prepare_state(world, css_query, &mut registry);
81
82 if state.has_any_selected_entities() {
83 let mut state_res = world
84 .get_resource_mut::<StyleSheetState>()
85 .expect("Should be added by plugin");
86
87 *state_res = state;
88 }
89 });
90 });
91}
92
93pub(crate) fn prepare_state(
95 world: &World,
96 css_query: CssQueryParam,
97 registry: &mut ComponentFilterRegistry,
98) -> StyleSheetState {
99 let mut state = StyleSheetState::default();
100
101 for (root, maybe_children, sheet_handle) in &css_query.nodes {
102 for id in sheet_handle.handles().iter().map(|h| h.id()) {
103 if let Some(sheet) = css_query.assets.get(id) {
104 let mut tracked_entities = TrackedEntities::default();
105 let mut selected_entities = SelectedEntities::default();
106 debug!("Applying style {}", sheet.path());
107
108 for rule in sheet.iter() {
109 let entities = select_entities(
110 root,
111 maybe_children,
112 &rule.selector,
113 world,
114 &css_query,
115 registry,
116 &mut tracked_entities,
117 );
118
119 trace!(
120 "Applying rule ({}) on {} entities",
121 rule.selector.to_string(),
122 entities.len()
123 );
124
125 selected_entities.push((rule.selector.clone(), entities));
126 }
127
128 selected_entities.sort_by(|(a, _), (b, _)| a.weight.cmp(&b.weight));
129 state.push((id, tracked_entities, selected_entities));
130 }
131 }
132 }
133
134 state
135}
136
137fn select_entities(
141 root: Entity,
142 maybe_children: Option<&Children>,
143 selector: &Selector,
144 world: &World,
145 css_query: &CssQueryParam,
146 registry: &mut ComponentFilterRegistry,
147 tracked_entities: &mut TrackedEntities,
148) -> SmallVec<[Entity; 8]> {
149 let mut parent_tree = selector.get_parent_tree();
150
151 if parent_tree.is_empty() {
152 return SmallVec::new();
153 }
154
155 let mut entity_tree = std::iter::once(root)
158 .chain(
159 maybe_children
160 .map(|children| get_children_recursively(children, &css_query.children))
161 .unwrap_or_default(),
162 )
163 .collect::<SmallVec<_>>();
164
165 loop {
166 let node = parent_tree.remove(0);
169
170 let entities = select_entities_node(
171 node,
172 world,
173 css_query,
174 registry,
175 entity_tree.clone(),
176 tracked_entities,
177 );
178
179 if parent_tree.is_empty() {
180 break entities;
181 } else {
182 entity_tree = entities
183 .into_iter()
184 .filter_map(|e| css_query.children.get(e).ok())
185 .flat_map(|children| get_children_recursively(children, &css_query.children))
186 .collect();
187 }
188 }
189}
190
191#[derive(Debug, Default, Clone, Deref, DerefMut)]
192struct FilteredEntities(SmallVec<[Entity; 8]>);
193
194#[derive(Debug, Default, Clone, Deref, DerefMut)]
195struct MatchedEntities(SmallVec<[Entity; 8]>);
196
197fn select_entities_node(
200 node: SmallVec<[&SelectorElement; 8]>,
201 world: &World,
202 css_query: &CssQueryParam,
203 registry: &mut ComponentFilterRegistry,
204 entities: SmallVec<[Entity; 8]>,
205 tracked_entities: &mut TrackedEntities,
206) -> SmallVec<[Entity; 8]> {
207 node.into_iter().fold(entities, |entities, element| {
208 let (filtered, matched) = match element {
209 SelectorElement::Name(name) => {
210 get_entities_with(name.as_str(), &css_query.names, entities)
211 }
212 SelectorElement::Class(class) => {
213 get_entities_with(class.as_str(), &css_query.classes, entities)
214 }
215 SelectorElement::Component(component) => {
216 get_entities_with_component(component.as_str(), world, registry, entities)
217 }
218 SelectorElement::PseudoClass(pseudo_class) => {
219 get_entities_with_pseudo_class(world, *pseudo_class, entities.clone())
220 }
221 SelectorElement::Any => get_entities_with_any_component(&css_query.any, entities),
222 SelectorElement::Child => unreachable!(),
224 };
225
226 if !matched.is_empty() {
227 trace!("Tracking element {:?}: {}", element, matched.len());
228
229 tracked_entities
230 .entry(element.clone())
231 .or_default()
232 .extend(matched.0);
233 }
234
235 filtered.0
236 })
237}
238
239fn get_entities_with<T>(
242 name: &str,
243 query: &Query<(Entity, &'static T)>,
244 entities: SmallVec<[Entity; 8]>,
245) -> (FilteredEntities, MatchedEntities)
246where
247 T: Component + MatchSelectorElement,
248{
249 let entities = query
250 .iter()
251 .filter_map(|(e, rhs)| {
252 if entities.contains(&e) && rhs.matches(name) {
253 Some(e)
254 } else {
255 None
256 }
257 })
258 .collect::<SmallVec<_>>();
259
260 (
261 FilteredEntities(entities.clone()),
262 MatchedEntities(entities),
263 )
264}
265
266fn get_entities_with_pseudo_class(
269 world: &World,
270 pseudo_class: PseudoClassElement,
271 entities: SmallVec<[Entity; 8]>,
272) -> (FilteredEntities, MatchedEntities) {
273 match pseudo_class {
274 PseudoClassElement::Hover => get_entities_with_pseudo_class_hover(world, entities),
275 PseudoClassElement::Unsupported => (FilteredEntities(entities), Default::default()),
276 }
277}
278
279fn get_entities_with_pseudo_class_hover(
283 world: &World,
284 entities: SmallVec<[Entity; 8]>,
285) -> (FilteredEntities, MatchedEntities) {
286 let filtered = entities
287 .iter()
288 .copied()
289 .filter(|&e| {
290 world
291 .entity(e)
292 .get::<Interaction>()
293 .is_some_and(|interaction| matches!(interaction, Interaction::Hovered))
294 })
295 .collect::<SmallVec<_>>();
296
297 (FilteredEntities(filtered), MatchedEntities(entities))
298}
299
300fn get_entities_with_component(
304 name: &str,
305 world: &World,
306 components: &mut ComponentFilterRegistry,
307 entities: SmallVec<[Entity; 8]>,
308) -> (FilteredEntities, MatchedEntities) {
309 if let Some(query) = components.0.get_mut(name) {
310 let filtered = query
311 .filter(world)
312 .into_iter()
313 .filter(|e| entities.contains(e))
314 .collect::<SmallVec<_>>();
315
316 (
317 FilteredEntities(filtered.clone()),
318 MatchedEntities(filtered),
319 )
320 } else {
321 error!("Unregistered component selector {}", name);
322 Default::default()
323 }
324}
325
326fn get_entities_with_any_component(
329 query: &Query<Entity, With<Node>>,
330 entities: SmallVec<[Entity; 8]>,
331) -> (FilteredEntities, MatchedEntities) {
332 let filtered = query
333 .iter()
334 .filter(|e| entities.contains(e))
335 .collect::<SmallVec<_>>();
336
337 (
338 FilteredEntities(filtered.clone()),
339 MatchedEntities(filtered),
340 )
341}
342
343fn get_children_recursively(
345 children: &Children,
346 q_childs: &Query<&Children, With<Node>>,
347) -> SmallVec<[Entity; 8]> {
348 children
349 .iter()
350 .flat_map(|&e| {
351 std::iter::once(e).chain(
352 q_childs
353 .get(e)
354 .map_or(SmallVec::new(), |gc| get_children_recursively(gc, q_childs)),
355 )
356 })
357 .collect()
358}
359
360pub(crate) fn hot_reload_style_sheets(
362 mut assets_events: EventReader<AssetEvent<StyleSheetAsset>>,
363 mut q_sheets: Query<&mut StyleSheet>,
364) {
365 for evt in assets_events.read() {
366 if let AssetEvent::Modified { id } = evt {
367 q_sheets
368 .iter_mut()
369 .filter(|sheet| sheet.handles().iter().any(|h| h.id() == *id))
370 .for_each(|mut sheet| {
371 debug!("Refreshing sheet {:?} due to asset reload", sheet);
372 sheet.refresh();
373 });
374 }
375 }
376}
377
378pub(crate) fn clear_state(mut sheet_rule: ResMut<StyleSheetState>) {
380 if sheet_rule.has_any_selected_entities() {
381 debug!("Finished applying style sheet.");
382 sheet_rule.clear_selected_entities();
383 }
384}
385
386pub(crate) fn watch_tracked_entities(world: &mut World) {
392 if world.is_resource_changed::<StyleSheetState>() {
393 trace!("StyleSheetState resource changed! Skipping watch tracked entities");
394 return;
395 }
396
397 let Some(state) = world.get_resource::<StyleSheetState>() else {
398 return;
399 };
400
401 let changed_assets = check_for_changed_assets(state, world);
402
403 if !changed_assets.is_empty() {
405 let mut query_state: SystemState<Query<&mut StyleSheet>> = SystemState::new(world);
406 for asset_id in changed_assets {
407 let mut query = query_state.get_mut(world);
408 for mut stylesheet in query.iter_mut() {
409 if stylesheet.handles().iter().any(|h| h.id() == asset_id) {
410 debug!("Refreshing sheet {:?} due to changed entities", stylesheet);
411 stylesheet.refresh();
412 }
413 }
414 }
415 }
416}
417
418fn check_for_changed_assets(
421 state: &StyleSheetState,
422 world: &World,
423) -> Vec<AssetId<StyleSheetAsset>> {
424 let mut changed_assets = vec![];
425 for (asset_id, tracked_entities, _) in state.iter() {
426 for (element, entities) in tracked_entities.iter() {
427 if entities.is_empty() {
428 continue;
429 }
430
431 let changed = match element {
432 SelectorElement::Name(_) => any_component::<Name>(world, entities),
433 SelectorElement::Component(c) => any_component_changed_by_name(world, entities, c),
434 SelectorElement::Class(_) => any_component::<Class>(world, entities),
435 SelectorElement::PseudoClass(pseudo_class) => {
436 any_component_changed_by_pseudo_class(world, entities, *pseudo_class)
437 }
438 SelectorElement::Any => any_component::<Node>(world, entities),
439 _ => unreachable!(),
440 };
441
442 if changed {
443 trace!("Changed! {:?}", element);
444 changed_assets.push(*asset_id);
445 break;
446 }
447 }
448 }
449
450 changed_assets
451}
452
453fn any_component<T: Component>(world: &World, entities: &SmallVec<[Entity; 8]>) -> bool {
455 let this_run = world.read_change_tick();
456 let last_run = world.last_change_tick();
457 for e in entities {
458 if let Some(ticks) = world.entity(*e).get_change_ticks::<T>() {
459 if ticks.is_changed(last_run, this_run) {
460 return true;
461 }
462 }
463 }
464 false
465}
466
467fn any_component_changed_by_name(
469 world: &World,
470 entities: &SmallVec<[Entity; 8]>,
471 component_name: &str,
472) -> bool {
473 let this_run = world.read_change_tick();
474 let last_run = world.last_change_tick();
475
476 let Some(registry) = world.get_resource::<ComponentFilterRegistry>() else {
477 return false;
478 };
479 let Some(boxed_state) = registry.get(component_name) else {
480 return false;
481 };
482
483 for e in entities {
484 if let Some(ticks) = boxed_state.get_change_ticks(world, *e) {
485 if ticks.is_changed(last_run, this_run) {
486 return true;
487 }
488 }
489 }
490 false
491}
492
493fn any_component_changed_by_pseudo_class(
495 world: &World,
496 entities: &SmallVec<[Entity; 8]>,
497 pseudo_class: PseudoClassElement,
498) -> bool {
499 match pseudo_class {
500 PseudoClassElement::Hover => any_component::<Interaction>(world, entities),
501 PseudoClassElement::Unsupported => false,
502 }
503}