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}