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
//! Culling while building quads during `paint`.
//!
//! `CullCtx` decides whether an element is visible at all for the current
//! clip (from `ScrollView`, `Container::clip(true)`, or a `Canvas` with
//! `clip_self`). If an element is entirely outside the clip, we skip:
//!
//! - `paint_self` (text shaping, gradient building, pushing to `RenderList`),
//! - recursion into children, **only if** the element clips itself
//! (`clip_self = true`).
//!
//! This is always safe: the clip is authoritative — pixels outside it never
//! reach the image, so omitting them from the `RenderList` changes nothing.
//!
//! `RenderList` also drops fully off-screen items when they are pushed, but
//! that happens after text shaping and after the view has walked its subtree.
//! Culling at this stage saves that CPU work and the push itself.
use sequoia_core::Rect;
/// Culling configuration for `Runtime::paint_region`.
///
/// `ENABLED` is the default for `paint`, `paint_if_dirty`, and the platform.
/// `DISABLED` forces a full CPU pass; `RenderList` still clips fully
/// off-screen items at push time, so the visible output is unchanged.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CullCtx {
enabled: bool,
/// Margin around the element rect -- headroom for AA, shadows, and glow.
margin: f32,
}
impl CullCtx {
/// Culling disabled -- walk and paint the full tree.
pub const DISABLED: Self = Self {
enabled: false,
margin: 0.0,
};
/// Culling enabled with an 8 px margin (safe for `BoxStyle` shadows).
pub const ENABLED: Self = Self {
enabled: true,
margin: 8.0,
};
pub const fn new() -> Self {
Self::ENABLED
}
pub const fn disabled() -> Self {
Self::DISABLED
}
pub fn with_margin(mut self, margin: f32) -> Self {
self.margin = if margin.is_finite() {
margin.clamp(0.0, 64.0)
} else {
8.0
};
self
}
pub fn is_enabled(self) -> bool {
self.enabled
}
pub fn margin(self) -> f32 {
self.margin
}
/// Does `rect` (expanded by the margin) intersect `clip`?
///
/// - Culling disabled → `true`.
/// - `clip = None` (no clip on the path) → `true`.
/// - Empty `clip` → `false` (nothing is visible).
pub fn intersects(self, rect: Rect, clip: Option<Rect>) -> bool {
if !self.enabled {
return true;
}
let Some(clip) = clip else {
return true;
};
if clip.is_empty() {
return false;
}
rect.inflate(self.margin, self.margin).intersects(clip)
}
/// Is the whole subtree invisible?
///
/// Unlike `!intersects`, this also requires the element to clip itself
/// (`clip_self`), so its children are clipped to the parent rect too.
/// Without `clip_self`, children may overflow (e.g. `overflow: Visible`)
/// and must not be skipped.
pub fn fully_clipped(self, rect: Rect, clip: Option<Rect>, clip_self: bool) -> bool {
if !self.enabled {
return false;
}
if !clip_self {
return false;
}
!self.intersects(rect, clip)
}
}
impl Default for CullCtx {
fn default() -> Self {
Self::ENABLED
}
}
#[cfg(test)]
mod tests {
use super::*;
fn r(x: f32, y: f32, w: f32, h: f32) -> Rect {
Rect::from_xywh(x, y, w, h)
}
#[test]
fn disabled_never_culls() {
let c = CullCtx::DISABLED;
let clip = Some(r(0.0, 0.0, 100.0, 100.0));
assert!(c.intersects(r(500.0, 500.0, 10.0, 10.0), clip));
assert!(!c.fully_clipped(r(500.0, 500.0, 10.0, 10.0), clip, true));
}
#[test]
fn no_clip_never_culls() {
let c = CullCtx::ENABLED;
assert!(c.intersects(r(0.0, 0.0, 1.0, 1.0), None));
}
#[test]
fn empty_clip_culls_everything() {
let c = CullCtx::ENABLED;
assert!(!c.intersects(r(0.0, 0.0, 1.0, 1.0), Some(Rect::ZERO)));
assert!(!c.intersects(r(0.0, 0.0, 1.0, 1.0), Some(r(5.0, 5.0, 0.0, 0.0))));
}
#[test]
fn rect_inside_clip_kept() {
let c = CullCtx::ENABLED.with_margin(0.0);
let clip = Some(r(0.0, 0.0, 100.0, 100.0));
assert!(c.intersects(r(10.0, 10.0, 20.0, 20.0), clip));
assert!(
c.intersects(r(90.0, 90.0, 20.0, 20.0), clip),
"partial overlap"
);
assert!(
c.intersects(r(-5.0, -5.0, 20.0, 20.0), clip),
"partial overlap top-left"
);
}
#[test]
fn rect_outside_clip_culled() {
let c = CullCtx::ENABLED.with_margin(0.0);
let clip = Some(r(0.0, 0.0, 100.0, 100.0));
assert!(!c.intersects(r(200.0, 200.0, 20.0, 20.0), clip));
assert!(!c.intersects(r(200.0, 0.0, 20.0, 20.0), clip), "right");
assert!(!c.intersects(r(0.0, -100.0, 20.0, 20.0), clip), "above");
}
#[test]
fn margin_expands_the_visible_band() {
let clip = Some(r(0.0, 0.0, 100.0, 100.0));
// 2 px past the right edge — outside the clip, but close.
let rect = r(102.0, 10.0, 20.0, 20.0);
assert!(!CullCtx::ENABLED.with_margin(0.0).intersects(rect, clip));
assert!(
CullCtx::ENABLED.with_margin(8.0).intersects(rect, clip),
"shadow/glow on the border must survive"
);
}
#[test]
fn fully_clipped_requires_clip_self() {
let c = CullCtx::ENABLED.with_margin(0.0);
let clip = Some(r(0.0, 0.0, 100.0, 100.0));
let far = r(200.0, 200.0, 10.0, 10.0);
assert!(!c.fully_clipped(far, clip, false), "children may overflow");
assert!(c.fully_clipped(far, clip, true), "clipping element — safe");
}
#[test]
fn margin_sanitizes() {
assert_eq!(CullCtx::ENABLED.with_margin(-5.0).margin(), 0.0);
assert_eq!(CullCtx::ENABLED.with_margin(f32::NAN).margin(), 8.0);
assert_eq!(CullCtx::ENABLED.with_margin(1e6).margin(), 64.0);
}
}