ailloli_ui_runtime/scene/scene_graph.rs
1//! Immutable renderer-facing scene graph produced by the paint engine.
2
3use ailloli_ui_core::ClipShape;
4
5use super::{ClipStackSnapshot, DrawCmd, IsolatedEffects};
6
7/// Paint ordering stratum for a scene layer.
8///
9/// Base layers are normally submitted first; overlays are intended to appear
10/// above all base content. [`Scene`] preserves insertion order rather than
11/// sorting by this discriminant.
12///
13/// # Examples
14///
15/// ```
16/// use ailloli_ui_runtime::scene::LayerKind;
17/// assert_ne!(LayerKind::Base, LayerKind::Overlay);
18/// ```
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum LayerKind {
21 /// Ordinary retained-tree content.
22 Base,
23 /// Top-level content intended to paint above base layers.
24 Overlay,
25}
26
27/// Ordered draw commands sharing a clip and optional isolated effects.
28///
29/// `isolated_depth` is meaningful only when `isolated` is true. Constructors
30/// initialize commands empty; public fields allow consumers to assemble custom
31/// layers without validation.
32///
33/// # Examples
34///
35/// ```
36/// use ailloli_ui_runtime::scene::{ClipStackSnapshot, Layer, LayerKind};
37/// let layer = Layer::base(ClipStackSnapshot::empty());
38/// assert_eq!(layer.kind, LayerKind::Base);
39/// assert!(layer.cmds.is_empty() && !layer.isolated);
40/// ```
41#[derive(Debug, Clone)]
42pub struct Layer {
43 /// Base or overlay paint stratum.
44 pub kind: LayerKind,
45 /// Immutable clip stack applied to every command in this layer.
46 pub clip: ClipStackSnapshot,
47 /// Draw commands in painter submission order.
48 pub cmds: Vec<DrawCmd>,
49 /// When true, content is rendered offscreen then composited (isolated compositor).
50 pub isolated: bool,
51 /// Nesting depth among isolated scopes (0 = root). nested isolated compositor.
52 pub isolated_depth: u8,
53 /// Opacity, blend, and filter parameters for an isolated layer.
54 pub effects: IsolatedEffects,
55}
56
57/// Provides the operations defined for Layer.
58impl Layer {
59 /// Creates an empty, non-isolated base layer with no effects.
60 ///
61 /// # Examples
62 ///
63 /// ```
64 /// use ailloli_ui_runtime::scene::{ClipStackSnapshot, Layer, LayerKind};
65 /// let layer = Layer::base(ClipStackSnapshot::empty());
66 /// assert_eq!(layer.kind, LayerKind::Base);
67 /// ```
68 pub fn base(clip: ClipStackSnapshot) -> Self {
69 Self {
70 kind: LayerKind::Base,
71 clip,
72 cmds: Vec::new(),
73 isolated: false,
74 isolated_depth: 0,
75 effects: IsolatedEffects::default(),
76 }
77 }
78
79 /// Creates an empty, non-isolated overlay layer with no effects.
80 ///
81 /// # Examples
82 ///
83 /// ```
84 /// use ailloli_ui_runtime::scene::{ClipStackSnapshot, Layer, LayerKind};
85 /// let layer = Layer::overlay(ClipStackSnapshot::empty());
86 /// assert_eq!(layer.kind, LayerKind::Overlay);
87 /// ```
88 pub fn overlay(clip: ClipStackSnapshot) -> Self {
89 Self {
90 kind: LayerKind::Overlay,
91 clip,
92 cmds: Vec::new(),
93 isolated: false,
94 isolated_depth: 0,
95 effects: IsolatedEffects::default(),
96 }
97 }
98
99 /// Creates an empty isolated base layer at caller-supplied nesting `depth`.
100 ///
101 /// Depth uses `0` for the outermost isolated scope and is stored verbatim;
102 /// this constructor does not check nesting consistency or overflow.
103 ///
104 /// # Examples
105 ///
106 /// ```
107 /// use ailloli_ui_runtime::scene::{ClipStackSnapshot, IsolatedEffects, Layer};
108 /// let layer = Layer::isolated_base(ClipStackSnapshot::empty(), IsolatedEffects::default(), 2);
109 /// assert!(layer.isolated);
110 /// assert_eq!(layer.isolated_depth, 2);
111 /// ```
112 pub fn isolated_base(clip: ClipStackSnapshot, effects: IsolatedEffects, depth: u8) -> Self {
113 Self {
114 kind: LayerKind::Base,
115 clip,
116 cmds: Vec::new(),
117 isolated: true,
118 isolated_depth: depth,
119 effects,
120 }
121 }
122
123 /// Creates a base layer from zero or one clip shape.
124 ///
125 /// The root flag is ignored when `clip` is `None`.
126 ///
127 /// # Examples
128 ///
129 /// ```
130 /// use ailloli_ui_runtime::scene::Layer;
131 /// assert!(Layer::base_with_clip(None, true).clip.is_empty());
132 /// ```
133 pub fn base_with_clip(clip: Option<ClipShape>, is_window_root: bool) -> Self {
134 Self::base(ClipStackSnapshot::from_clip(clip, is_window_root))
135 }
136
137 /// Creates an overlay layer from zero or one clip shape.
138 ///
139 /// # Examples
140 ///
141 /// ```
142 /// use ailloli_ui_runtime::scene::{Layer, LayerKind};
143 /// assert_eq!(Layer::overlay_with_clip(None, false).kind, LayerKind::Overlay);
144 /// ```
145 pub fn overlay_with_clip(clip: Option<ClipShape>, is_window_root: bool) -> Self {
146 Self::overlay(ClipStackSnapshot::from_clip(clip, is_window_root))
147 }
148}
149
150/// Ordered paint layers (base + overlays) with optional per-layer clips.
151///
152/// The vector is the final submission order; [`Scene`] does not sort or merge
153/// layers. An empty scene is valid and renders nothing.
154///
155/// # Examples
156///
157/// ```
158/// use ailloli_ui_runtime::scene::Scene;
159/// assert!(Scene::default().layers.is_empty());
160/// ```
161#[derive(Debug, Default, Clone)]
162pub struct Scene {
163 /// Layers in back-to-front submission order.
164 pub layers: Vec<Layer>,
165}
166
167/// Provides the operations defined for Scene.
168impl Scene {
169 /// Appends a layer without reordering it by [`LayerKind`].
170 ///
171 /// # Examples
172 ///
173 /// ```
174 /// use ailloli_ui_runtime::scene::{ClipStackSnapshot, Layer, Scene};
175 /// let mut scene = Scene::default();
176 /// scene.push_layer(Layer::base(ClipStackSnapshot::empty()));
177 /// assert_eq!(scene.layers.len(), 1);
178 /// ```
179 pub fn push_layer(&mut self, layer: Layer) {
180 self.layers.push(layer);
181 }
182
183 /// Appends an empty base layer using `clip`.
184 ///
185 /// # Examples
186 ///
187 /// ```
188 /// use ailloli_ui_runtime::scene::{ClipStackSnapshot, LayerKind, Scene};
189 /// let mut scene = Scene::default();
190 /// scene.push_base_layer(ClipStackSnapshot::empty());
191 /// assert_eq!(scene.layers[0].kind, LayerKind::Base);
192 /// ```
193 pub fn push_base_layer(&mut self, clip: ClipStackSnapshot) {
194 self.layers.push(Layer::base(clip));
195 }
196
197 /// Appends an empty overlay layer using `clip`.
198 ///
199 /// # Examples
200 ///
201 /// ```
202 /// use ailloli_ui_runtime::scene::{ClipStackSnapshot, LayerKind, Scene};
203 /// let mut scene = Scene::default();
204 /// scene.push_overlay_layer(ClipStackSnapshot::empty());
205 /// assert_eq!(scene.layers[0].kind, LayerKind::Overlay);
206 /// ```
207 pub fn push_overlay_layer(&mut self, clip: ClipStackSnapshot) {
208 self.layers.push(Layer::overlay(clip));
209 }
210
211 /// Builds exactly two layers: base first and overlay second.
212 ///
213 /// Command vectors are moved without cloning. `None` clips create empty
214 /// clip snapshots, and both root flags are `false`.
215 ///
216 /// # Examples
217 ///
218 /// ```
219 /// use ailloli_ui_runtime::scene::{LayerKind, Scene};
220 /// let scene = Scene::from_base_and_overlay(None, vec![], None, vec![]);
221 /// assert_eq!(scene.layers.len(), 2);
222 /// assert_eq!((scene.layers[0].kind, scene.layers[1].kind), (LayerKind::Base, LayerKind::Overlay));
223 /// ```
224 pub fn from_base_and_overlay(
225 base_clip: Option<ClipShape>,
226 base_cmds: Vec<DrawCmd>,
227 overlay_clip: Option<ClipShape>,
228 overlay_cmds: Vec<DrawCmd>,
229 ) -> Self {
230 let mut layers = Vec::new();
231 let mut base = Layer::base_with_clip(base_clip, false);
232 base.cmds = base_cmds;
233 layers.push(base);
234 let mut overlay = Layer::overlay_with_clip(overlay_clip, false);
235 overlay.cmds = overlay_cmds;
236 layers.push(overlay);
237 Self { layers }
238 }
239}