use bevy::ecs::query::QueryState;
use bevy::platform::collections::HashMap;
use bevy::prelude::*;
use bevy::render::MainWorld;
use bevy::render::sync_world::MainEntity;
use bevy::ui::ComputedNode;
use super::ExtractedUiLayers;
type MembersQuery = QueryState<(Entity, &'static mut InheritedVisibility), With<ComputedNode>>;
#[derive(Resource, Default)]
pub struct HiddenMembers {
pub stash: Vec<Entity>,
query: Option<MembersQuery>,
}
pub fn hide_cached_layer_members(
mut main_world: ResMut<MainWorld>,
extracted: Res<ExtractedUiLayers>,
mut hidden: ResMut<HiddenMembers>,
) {
let hidden = &mut *hidden;
hidden.stash.clear();
if extracted.layers.iter().all(|l| l.needs_capture) {
return;
}
hide_in(
&mut main_world,
&extracted.membership,
|idx| !extracted.layers[idx].needs_capture,
&mut hidden.stash,
&mut hidden.query,
);
}
pub fn restore_cached_layer_members(
mut main_world: ResMut<MainWorld>,
mut hidden: ResMut<HiddenMembers>,
) {
restore_out(&mut main_world, &mut hidden.stash);
}
pub fn hide_in(
world: &mut World,
membership: &HashMap<MainEntity, usize>,
cached: impl Fn(usize) -> bool,
stash: &mut Vec<Entity>,
query: &mut Option<MembersQuery>,
) {
stash.clear();
if membership.is_empty() {
return;
}
let query = query.get_or_insert_with(|| world.query_filtered());
for (entity, mut visibility) in query.iter_mut(world) {
if !visibility.get() {
continue; }
let Some(&idx) = membership.get(&MainEntity::from(entity)) else {
continue; };
if !cached(idx) {
continue; }
*visibility.bypass_change_detection() = InheritedVisibility::HIDDEN;
stash.push(entity);
}
}
pub fn restore_out(world: &mut World, stash: &mut Vec<Entity>) {
for entity in stash.drain(..) {
if let Some(mut visibility) = world.get_mut::<InheritedVisibility>(entity) {
*visibility.bypass_change_detection() = InheritedVisibility::VISIBLE;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn node(world: &mut World, visibility: InheritedVisibility) -> Entity {
world.spawn((ComputedNode::default(), visibility)).id()
}
#[test]
fn hide_restore_roundtrip() {
let mut world = World::new();
let cached_root = node(&mut world, InheritedVisibility::VISIBLE);
let cached_member = node(&mut world, InheritedVisibility::VISIBLE);
let app_hidden_member = node(&mut world, InheritedVisibility::HIDDEN);
let live_member = node(&mut world, InheritedVisibility::VISIBLE);
let outside = node(&mut world, InheritedVisibility::VISIBLE);
let span = world.spawn(InheritedVisibility::VISIBLE).id();
let mut membership: HashMap<MainEntity, usize> = HashMap::default();
for e in [cached_root, cached_member, app_hidden_member, span] {
membership.insert(MainEntity::from(e), 0);
}
membership.insert(MainEntity::from(live_member), 1);
let cached = |idx: usize| idx == 0;
world.clear_trackers();
let tick_of = |world: &World, e: Entity| {
world
.entity(e)
.get_ref::<InheritedVisibility>()
.unwrap()
.last_changed()
};
let before = tick_of(&world, cached_member);
let mut stash = Vec::new();
let mut query = None;
hide_in(&mut world, &membership, cached, &mut stash, &mut query);
let vis = |world: &World, e: Entity| world.get::<InheritedVisibility>(e).unwrap().get();
assert!(
!vis(&world, cached_root),
"root's own drawable is capture content"
);
assert!(!vis(&world, cached_member));
assert!(!vis(&world, app_hidden_member));
assert!(
vis(&world, live_member),
"re-capturing layer keeps its items"
);
assert!(vis(&world, outside));
assert!(vis(&world, span));
assert_eq!(stash.len(), 2, "only really-flipped members are stashed");
assert_eq!(
tick_of(&world, cached_member),
before,
"bypasses change detection"
);
restore_out(&mut world, &mut stash);
assert!(vis(&world, cached_root));
assert!(vis(&world, cached_member));
assert!(!vis(&world, app_hidden_member), "app-hidden stays hidden");
assert!(stash.is_empty(), "stash drains on restore");
assert_eq!(tick_of(&world, cached_member), before);
let late = node(&mut world, InheritedVisibility::VISIBLE);
membership.insert(MainEntity::from(late), 0);
hide_in(&mut world, &membership, cached, &mut stash, &mut query);
assert!(!vis(&world, late));
assert_eq!(stash.len(), 3);
restore_out(&mut world, &mut stash);
}
#[test]
fn hide_in_empty_and_restore_stale() {
let mut world = World::new();
let e = node(&mut world, InheritedVisibility::VISIBLE);
let mut stash = vec![e];
hide_in(
&mut world,
&HashMap::default(),
|_| true,
&mut stash,
&mut None,
);
assert!(stash.is_empty());
world.despawn(e);
let mut stash = vec![e];
restore_out(&mut world, &mut stash);
assert!(stash.is_empty());
}
}