use bevy::math::{Rect, UVec2, Vec2};
use bevy::prelude::*;
use bevy::render::MainWorld;
use bevy::ui::CalculatedClip;
use crate::layer::clip::LayerClips;
pub const UNCLIPPED: Rect = Rect {
min: Vec2::splat(f32::NEG_INFINITY),
max: Vec2::splat(f32::INFINITY),
};
#[derive(Resource, Default)]
pub struct SwappedClips(pub Vec<(Entity, Rect)>);
pub fn swap_interior_clips_in(mut main_world: ResMut<MainWorld>, mut stash: ResMut<SwappedClips>) {
swap_in(&mut main_world, &mut stash.0);
}
pub fn swap_interior_clips_out(mut main_world: ResMut<MainWorld>, mut stash: ResMut<SwappedClips>) {
swap_out(&mut main_world, &mut stash.0);
}
pub fn swap_in(world: &mut World, stash: &mut Vec<(Entity, Rect)>) {
stash.clear();
if world.get_resource::<LayerClips>().is_none() {
return; }
world.resource_scope(|world, clips: Mut<LayerClips>| {
for (&entity, &interior) in clips.interior.iter() {
let Ok(mut e) = world.get_entity_mut(entity) else {
continue;
};
let Some(mut clip) = e.get_mut::<CalculatedClip>() else {
continue;
};
let clip = clip.bypass_change_detection();
stash.push((entity, clip.clip));
clip.clip = interior.unwrap_or(UNCLIPPED);
}
});
}
pub fn swap_out(world: &mut World, stash: &mut Vec<(Entity, Rect)>) {
for (entity, original) in stash.drain(..) {
if let Ok(mut e) = world.get_entity_mut(entity)
&& let Some(mut clip) = e.get_mut::<CalculatedClip>()
{
clip.bypass_change_detection().clip = original;
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ClippedQuad {
pub pos_min: Vec2,
pub pos_max: Vec2,
pub uv_min: Vec2,
pub uv_max: Vec2,
}
pub fn clip_quad(min: Vec2, size: UVec2, clip: Option<Rect>) -> Option<ClippedQuad> {
let size = size.as_vec2();
let max = min + size;
let (pos_min, pos_max) = match clip {
None => (min, max),
Some(c) => (min.max(c.min), max.min(c.max)),
};
if pos_min.x >= pos_max.x || pos_min.y >= pos_max.y {
return None;
}
Some(ClippedQuad {
pos_min,
pos_max,
uv_min: (pos_min - min) / size,
uv_max: (pos_max - min) / size,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clip_quad_table() {
let min = Vec2::new(10.0, 20.0);
let size = UVec2::new(100, 50);
let full = clip_quad(min, size, None).expect("unclipped quad");
assert_eq!(full.pos_min, min);
assert_eq!(full.pos_max, Vec2::new(110.0, 70.0));
assert_eq!(full.uv_min, Vec2::ZERO);
assert_eq!(full.uv_max, Vec2::ONE);
let q = clip_quad(min, size, Some(Rect::new(60.0, 0.0, 300.0, 300.0)))
.expect("partial overlap");
assert_eq!(q.pos_min, Vec2::new(60.0, 20.0));
assert_eq!(q.pos_max, Vec2::new(110.0, 70.0));
assert_eq!(q.uv_min, Vec2::new(0.5, 0.0));
assert_eq!(q.uv_max, Vec2::ONE);
let q =
clip_quad(min, size, Some(Rect::new(0.0, 0.0, 300.0, 50.0))).expect("partial overlap");
assert_eq!(q.pos_min, min);
assert_eq!(q.pos_max, Vec2::new(110.0, 50.0));
assert_eq!(q.uv_min, Vec2::ZERO);
assert_eq!(q.uv_max, Vec2::new(1.0, 0.6));
let q =
clip_quad(min, size, Some(Rect::new(0.0, 0.0, 500.0, 500.0))).expect("containing clip");
assert_eq!(q, full);
assert_eq!(
clip_quad(min, size, Some(Rect::new(200.0, 0.0, 300.0, 300.0))),
None
);
assert_eq!(
clip_quad(min, size, Some(Rect::new(110.0, 0.0, 300.0, 300.0))),
None
);
assert_eq!(clip_quad(min, size, Some(Rect::default())), None);
}
#[test]
fn swap_roundtrip() {
let mut world = World::new();
let real = Rect::new(0.0, 0.0, 100.0, 100.0);
let inner = Rect::new(10.0, 10.0, 50.0, 50.0);
let a = world.spawn(CalculatedClip { clip: real }).id();
let b = world.spawn(CalculatedClip { clip: real }).id();
let c = world.spawn_empty().id();
let outside = world.spawn(CalculatedClip { clip: real }).id();
let dead = world.spawn_empty().id();
world.despawn(dead);
let mut clips = LayerClips::default();
clips.interior.insert(a, Some(inner));
clips.interior.insert(b, None);
clips.interior.insert(c, None);
clips.interior.insert(dead, Some(inner));
world.insert_resource(clips);
let mut stash = Vec::new();
swap_in(&mut world, &mut stash);
assert_eq!(world.get::<CalculatedClip>(a).unwrap().clip, inner);
assert_eq!(world.get::<CalculatedClip>(b).unwrap().clip, UNCLIPPED);
assert!(world.get::<CalculatedClip>(c).is_none());
assert_eq!(world.get::<CalculatedClip>(outside).unwrap().clip, real);
assert_eq!(stash.len(), 2, "only really-swapped members are stashed");
swap_out(&mut world, &mut stash);
assert_eq!(world.get::<CalculatedClip>(a).unwrap().clip, real);
assert_eq!(world.get::<CalculatedClip>(b).unwrap().clip, real);
assert!(stash.is_empty(), "stash drains on restore");
}
#[test]
fn swap_in_clears_previous_stash() {
let mut world = World::new();
world.insert_resource(LayerClips::default());
let mut stash = vec![(Entity::PLACEHOLDER, UNCLIPPED)];
swap_in(&mut world, &mut stash);
assert!(stash.is_empty());
}
}