mittens_engine/engine/ecs/system/
clipping_system.rs1use crate::engine::ecs::ComponentId;
2use crate::engine::ecs::World;
3use crate::engine::ecs::component::RenderableComponent;
4use crate::engine::graphics::VisualWorld;
5use std::collections::HashSet;
6
7pub(crate) const OWNED_LAYOUT_STENCIL_CLIP_LABEL: &str = "__layout_stencil_clip";
8
9#[derive(Debug, Default)]
10pub struct ClippingSystem {
11 active_stencil_clips: HashSet<ComponentId>,
12}
13
14impl ClippingSystem {
15 pub fn register_renderable(
16 &self,
17 world: &World,
18 visuals: &mut VisualWorld,
19 renderable_component: ComponentId,
20 ) {
21 self.sync_renderable_stencil_ref(world, visuals, renderable_component);
22 }
23
24 pub fn register_stencil_clip(
25 &mut self,
26 world: &World,
27 visuals: &mut VisualWorld,
28 component: ComponentId,
29 ) {
30 self.active_stencil_clips.insert(component);
31
32 let stencil_ref = Self::stencil_ref_for_clip(world, component);
33 if let Some(handle) = Self::find_stencil_clip_renderable_handle(world, component) {
34 visuals.register_stencil_clip(handle, stencil_ref);
35 }
36
37 if let Some(scope_root) = Self::stencil_clip_scope_root(world, component) {
38 self.sync_stencil_refs_in_subtree(world, visuals, scope_root);
39 }
40 }
41
42 pub fn unregister_stencil_clip(
43 &mut self,
44 world: &World,
45 visuals: &mut VisualWorld,
46 component: ComponentId,
47 ) {
48 self.active_stencil_clips.remove(&component);
49
50 if let Some(handle) = Self::find_stencil_clip_renderable_handle(world, component) {
51 visuals.unregister_stencil_clip(handle);
52 }
53
54 if let Some(scope_root) = Self::stencil_clip_scope_root(world, component) {
55 self.sync_stencil_refs_in_subtree(world, visuals, scope_root);
56 }
57 }
58
59 pub fn unregister_stencil_clip_for_subtree_node(
60 &mut self,
61 world: &World,
62 visuals: &mut VisualWorld,
63 component: ComponentId,
64 ) {
65 self.active_stencil_clips.remove(&component);
66 if let Some(handle) = Self::find_stencil_clip_renderable_handle(world, component) {
67 visuals.unregister_stencil_clip(handle);
68 }
69 }
70
71 pub fn resync_after_renderable_flush(&mut self, world: &World, visuals: &mut VisualWorld) {
72 let stencil_clips: Vec<ComponentId> = self.active_stencil_clips(world).collect();
73
74 for stencil_clip in stencil_clips {
75 let stencil_ref = Self::stencil_ref_for_clip(world, stencil_clip);
76 if let Some(handle) = Self::find_stencil_clip_renderable_handle(world, stencil_clip) {
77 let _ = visuals.register_stencil_clip(handle, stencil_ref);
78 }
79 if let Some(scope_root) = Self::stencil_clip_scope_root(world, stencil_clip) {
80 self.sync_stencil_refs_in_subtree(world, visuals, scope_root);
81 }
82 }
83 }
84
85 fn sync_renderable_stencil_ref(
86 &self,
87 world: &World,
88 visuals: &mut VisualWorld,
89 renderable_component: ComponentId,
90 ) {
91 let Some(renderable) =
92 world.get_component_by_id_as::<RenderableComponent>(renderable_component)
93 else {
94 return;
95 };
96 let Some(handle) = renderable.get_handle() else {
97 return;
98 };
99
100 let stencil_ref = self
101 .stencil_clip_for_renderable_component(world, renderable_component)
102 .map(|clip_component| Self::stencil_ref_for_clip(world, clip_component))
103 .unwrap_or_else(|| Self::stencil_ref_for_renderable(world, renderable_component));
104 let _ = visuals.update_stencil_ref(handle, stencil_ref);
105 }
106
107 fn stencil_clip_for_renderable_component(
108 &self,
109 world: &World,
110 renderable_component: ComponentId,
111 ) -> Option<ComponentId> {
112 self.active_stencil_clips
113 .iter()
114 .copied()
115 .find(|&clip_component| {
116 world.get_component_record(clip_component).is_some()
117 && Self::find_stencil_clip_renderable_component(world, clip_component)
118 == Some(renderable_component)
119 })
120 }
121
122 fn sync_stencil_refs_in_subtree(
123 &self,
124 world: &World,
125 visuals: &mut VisualWorld,
126 root: ComponentId,
127 ) {
128 let mut stack = vec![root];
129 while let Some(node) = stack.pop() {
130 if world
131 .get_component_by_id_as::<RenderableComponent>(node)
132 .is_some()
133 {
134 self.sync_renderable_stencil_ref(world, visuals, node);
135 }
136 for &child in world.children_of(node) {
137 stack.push(child);
138 }
139 }
140 }
141
142 fn active_stencil_clips<'a>(
143 &'a mut self,
144 world: &World,
145 ) -> impl Iterator<Item = ComponentId> + 'a {
146 self.active_stencil_clips
147 .retain(|&component| world.get_component_record(component).is_some());
148 self.active_stencil_clips.iter().copied()
149 }
150
151 fn stencil_ref_for_renderable(world: &World, renderable_component: ComponentId) -> u8 {
152 use crate::engine::ecs::component::StencilClipComponent;
153
154 let mut depth: u8 = 0;
155 let mut cursor = Some(renderable_component);
156 while let Some(node) = cursor {
157 if world
158 .get_component_by_id_as::<StencilClipComponent>(node)
159 .is_some()
160 || Self::is_layout_clip_scope_root(world, node)
161 {
162 depth = depth.saturating_add(1);
163 }
164 cursor = world.parent_of(node);
165 }
166 depth
167 }
168
169 fn stencil_ref_for_clip(world: &World, component: ComponentId) -> u8 {
170 use crate::engine::ecs::component::StencilClipComponent;
171
172 let mut depth: u8 = 0;
173 let mut cursor = if Self::is_layout_owned_stencil_clip(world, component) {
174 world
175 .parent_of(component)
176 .and_then(|scope_root| world.parent_of(scope_root))
177 } else {
178 world.parent_of(component)
179 };
180
181 while let Some(node) = cursor {
182 if world
183 .get_component_by_id_as::<StencilClipComponent>(node)
184 .is_some()
185 || Self::is_layout_clip_scope_root(world, node)
186 {
187 depth = depth.saturating_add(1);
188 }
189 cursor = world.parent_of(node);
190 }
191
192 depth
193 }
194
195 fn is_layout_owned_stencil_clip(world: &World, component: ComponentId) -> bool {
196 world.component_label(component) == Some(OWNED_LAYOUT_STENCIL_CLIP_LABEL)
197 && world
198 .get_component_by_id_as::<crate::engine::ecs::component::StencilClipComponent>(
199 component,
200 )
201 .is_some()
202 }
203
204 fn immediate_owned_layout_stencil_clip(
205 world: &World,
206 scope_root: ComponentId,
207 ) -> Option<ComponentId> {
208 world
209 .children_of(scope_root)
210 .iter()
211 .copied()
212 .find(|&child| Self::is_layout_owned_stencil_clip(world, child))
213 }
214
215 fn layout_bg_node(world: &World, scope_root: ComponentId) -> Option<ComponentId> {
216 world
217 .children_of(scope_root)
218 .iter()
219 .copied()
220 .find(|&child| world.component_label(child) == Some("__bg"))
221 }
222
223 fn subtree_first_renderable(world: &World, root: ComponentId) -> Option<ComponentId> {
224 let mut stack = vec![root];
225 while let Some(node) = stack.pop() {
226 if world
227 .get_component_by_id_as::<RenderableComponent>(node)
228 .is_some()
229 {
230 return Some(node);
231 }
232 for &child in world.children_of(node).iter().rev() {
233 stack.push(child);
234 }
235 }
236 None
237 }
238
239 fn is_layout_clip_scope_root(world: &World, node: ComponentId) -> bool {
240 Self::immediate_owned_layout_stencil_clip(world, node).is_some()
241 }
242
243 fn stencil_clip_scope_root(world: &World, component: ComponentId) -> Option<ComponentId> {
244 if Self::is_layout_owned_stencil_clip(world, component) {
245 return world.parent_of(component);
246 }
247 let parent = world.parent_of(component)?;
248 if world.component_label(parent) == Some("__bg") {
249 return world.parent_of(parent);
250 }
251 Some(parent)
252 }
253
254 fn find_stencil_clip_renderable_component(
255 world: &World,
256 component: ComponentId,
257 ) -> Option<ComponentId> {
258 let mut cursor = world.parent_of(component);
259 while let Some(cid) = cursor {
260 if world
261 .get_component_by_id_as::<RenderableComponent>(cid)
262 .is_some()
263 {
264 return Some(cid);
265 }
266 cursor = world.parent_of(cid);
267 }
268
269 if Self::is_layout_owned_stencil_clip(world, component) {
270 let scope_root = world.parent_of(component)?;
271 let bg_id = Self::layout_bg_node(world, scope_root)?;
272 return Self::subtree_first_renderable(world, bg_id);
273 }
274
275 None
276 }
277
278 fn find_stencil_clip_renderable_handle(
279 world: &World,
280 component: ComponentId,
281 ) -> Option<crate::engine::graphics::primitives::InstanceHandle> {
282 let renderable_component = Self::find_stencil_clip_renderable_component(world, component)?;
283 world
284 .get_component_by_id_as::<RenderableComponent>(renderable_component)
285 .and_then(|r| r.get_handle())
286 }
287}