1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
//! Interior clips — the main-world half of clip-independent layer capture.
//!
//! A promoted layer's capture must not bake **ancestor** clipping (scroll
//! containers / viewport) into its cached pixels: the cache treats scroll as
//! pure translation (see [`super::fold_member_geometry`]), so pixels captured
//! under an ancestor clip would be stale the moment the layer scrolls. Web
//! semantics agree: `filter` applies to the element's full painted content,
//! and ancestor overflow clips the *result* at composite time.
//!
//! `bevy_ui` hands every node its **inherited** clip as a list
//! ([`CalculatedClip`]): one rect per clipping ancestor, outermost first,
//! each in its clipper's local space; a node's own overflow is appended only
//! to the list its children get (`bevy_ui`'s `layout/clipping.rs`). So with
//! no reset in between, a member's list is its layer root's list plus the
//! clippers from the root down — and the clip the member needs *inside the
//! capture* is its own list minus that leading part. [`sync_layer_clips`]
//! keeps exactly that in [`LayerClips`]:
//!
//! - `strip`: per subtree member, how many leading rects of its real clip
//! come from above its layer root ([`inherited_len`]). Only members with at
//! least one have a row. Consumed by the render world's extract-window swap
//! (`layer::render::clip`), which drops those rects from the members'
//! [`CalculatedClip`] for exactly the duration of `ExtractSchedule` — the
//! main world is exclusively borrowed there, so no main-world system
//! (picking, focus) can ever observe the swap, and every stock extractor
//! (backgrounds, text, gradients, box shadows, texture slices, UI
//! materials) picks the interior clips up uniformly. What stays is Bevy's
//! own rects, untouched: clips originating inside the subtree still apply
//! (they move with the layer, so cached pixels stay valid), rotated or
//! scaled clippers included. Text self-clips (`TextScroll` content boxes)
//! recompose automatically in the stock text extractors from the swapped
//! value.
//! - `quads`: per layer root, what clips its **composite quad** instead
//! ([`QuadClip`]) — a top-level root's inherited [`CalculatedClip`], or,
//! for a nested root, its list minus the enclosing root's (its quad draws
//! inside the enclosing capture). Clippers that only translate/scale (the
//! common case, a layout transition's FLIP scale included) reduce to one
//! screen-space rect the quad clamps to; a rotated clipper keeps the list,
//! and the quad is cut to its polygon.
//!
//! Outside the extract window the real `CalculatedClip` values are always in
//! place: picking and `bevy_ui`'s other consumers keep seeing true inherited
//! clips.
//!
//! **What the strip relies on.** Bevy's list being append-only and outermost
//! first, and a child's leading rects being clones of the values its ancestor
//! got in the same pass — so an equal prefix is recognized with plain
//! equality (the tests in `clip/tests.rs` run Bevy's own clipping system and
//! fail if either changes). Where the lists differ the member keeps its whole
//! clip:
//!
//! - An `OverrideClip` between the root and the member restarts the member's
//! list. (The one unsound corner: a clipper below such a reset whose rect
//! and transform equal, bit for bit, a clipper above the root would be
//! taken for it — the capture is then under-clipped by a rect the quad
//! clamp applies anyway. bevy-react never inserts the component.)
//! - A `FixedNode` restarts the list too, but never between a root and one
//! of its members: membership stops at a fixed node
//! ([`LayerMembership`]), so its subtree has no rows here and extracts
//! with its real clips. That matters: a `positionType: "fixed"` panel
//! clipping at the size of a scrollport above the root is the corner
//! above made common. A fixed *root* starts from an empty list, like any
//! unclipped root.
//! - A fully clipped member (`display: none`, a clipper with a singular
//! transform) stays fully clipped. When the cause sits **above** the root
//! the whole subtree extracts fully clipped and the capture is empty; it is
//! never shown (the quad is clipped away too), and the cause lifting
//! changes the root's size or linear part, which re-captures.
//!
//! **Retained extraction.** `bevy_ui_render` re-extracts a node only when one
//! of its components changed, and the swap deliberately bypasses change
//! ticks. So the clip a node's render data holds is the one in place at its
//! last extraction: a node that joins a layer under a scrollport (promotion)
//! would keep its real ancestor clip baked into the capture, and one that
//! leaves (demotion) its interior clip, drawing outside its scroll container.
//! Every node whose `strip` row appears, changes or disappears is therefore
//! recorded in [`LayerClips::changed`], and [`mark_reclipped_members`] flags
//! its `CalculatedClip` as changed — re-extracted this frame, inside the swap
//! window. (A real clip change needs no help: bevy's own write flags it.)
//!
//! Known divergence (deliberate, rare): an `OverrideClip` escapee inside a
//! subtree is still clamped by the layer's quad clip at composite time
//! (stock lets it escape every ancestor clip).
//!
//! Offscreen layers still capture (when dirty) like any other — a fully
//! clipped-away quad simply draws nothing. The cost is invisible re-captures
//! for continuously-animated offscreen layers; the win is that scrolling
//! never re-captures and a layer scrolled into view is correct by
//! construction (the bug this module fixes: captures taken under clipping
//! were cached and served stale after scrolling).
use Arc;
use EntityHashMap;
use Rect;
use *;
use CalculatedClip;
use LayerMembership;
/// Clip maps for promoted layers, maintained by [`sync_layer_clips`]. They
/// persist across frames: re-derived for every member when
/// [`LayerMembership`] is rebuilt (a structural frame), per node whose
/// `CalculatedClip` changed otherwise, untouched on a clip-idle frame.
/// What clips a layer root's composite quad (physical px, screen space).
/// The screen-space (physical px) bounds of an inherited [`CalculatedClip`]:
/// the intersection of every clip rect's world-space bounding box. `None` =
/// unclipped (no clip rects); a fully clipped node yields an empty rect. Exact
/// for clippers that only translate/scale; a rotated clipper contributes its
/// bounding box (the over-approximation errs on drawing, never on hiding).
/// [`clip_aabb`] of `clip` without its first `skip` rects.
/// A [`CalculatedClip`] of one screen-space rect (an identity
/// `world_to_clip_local`) — how tests spell a plain clip rect.
pub
/// How many leading rects of `clip` are `above`'s whole list — the part of a
/// node's clip that comes from above the layer root whose inherited clip is
/// `above`. 0 when the root is unclipped or either is fully clipped, and when
/// the lists differ (an `OverrideClip` reset in between).
/// Maintains [`LayerClips`]. Runs in `PostUpdate` after
/// [`super::sync_layer_geometry`] (this frame's [`LayerMembership`]) and after
/// `bevy_ui`'s `UiSystems::PostLayout` (this frame's [`CalculatedClip`]
/// values, the inserts and removals applied). Must NOT feed
/// [`super::resolve_layer_repaints`]: clip changes never dirty a capture —
/// that is the point.
///
/// A node's rows depend on its own clip list and its (enclosing) root's. A
/// membership rebuild re-derives every member's; otherwise only the nodes
/// whose own `CalculatedClip` changed or went away are looked at — a root's
/// list cannot change under a member that inherits it without the member's
/// changing too (it contains it), and a member that does not inherit it has
/// nothing to strip either way. Bevy writes the component only on a real
/// change, so a scroll (the clipper stays put) touches nothing here.
/// Flag the `CalculatedClip` of every node whose `strip` row changed
/// ([`LayerClips::changed`]) so `bevy_ui_render`'s retained extraction
/// re-extracts it this frame, inside the swap window. Runs right after
/// [`sync_layer_clips`]. Entities without the component (unclipped, or
/// despawned) need nothing: there is no clip in their render data to refresh.
/// The flag reads as a clip change to [`sync_layer_clips`] next frame, which
/// finds the row unchanged and stops there.
pub