Skip to main content

lgui_core/core/scene/render/
primitive.rs

1use super::{signature::command_signature_part, *};
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4pub enum RenderPhase {
5    Background,
6    Content,
7    Overlay,
8    Popup,
9}
10
11#[derive(Clone, Debug, PartialEq)]
12pub enum ScenePrimitive {
13    Rect {
14        id: UiId,
15        rect: UiRect,
16        style: VisualStyle,
17        phase: RenderPhase,
18    },
19    Ellipse {
20        id: UiId,
21        rect: UiRect,
22        style: VisualStyle,
23        phase: RenderPhase,
24    },
25    Text {
26        id: UiId,
27        rect: UiRect,
28        text: Cow<'static, str>,
29        style: TextStyle,
30        phase: RenderPhase,
31    },
32    Custom {
33        id: UiId,
34        rect: UiRect,
35        key: &'static str,
36        // Style may contain caller animation state. Backends should treat Custom as immediate
37        // paint unless the specific painter exposes a separate stable-cache contract.
38        style: Option<CustomPaintStyle>,
39        phase: RenderPhase,
40    },
41    Line {
42        id: UiId,
43        start: super::Point,
44        end: super::Point,
45        stroke: super::Stroke,
46        phase: RenderPhase,
47    },
48    Path {
49        id: UiId,
50        rect: UiRect,
51        path: UiPath,
52        style: PathStyle,
53        phase: RenderPhase,
54    },
55    Image {
56        id: UiId,
57        rect: UiRect,
58        source: UiImageSource,
59        request: ImageRequest,
60        fit: ImageFit,
61        phase: RenderPhase,
62    },
63    Icon {
64        id: UiId,
65        rect: UiRect,
66        key: &'static str,
67        style: super::IconStyle,
68        phase: RenderPhase,
69    },
70    Glow {
71        id: UiId,
72        rect: UiRect,
73        color: super::Color,
74        alpha: u8,
75        phase: RenderPhase,
76    },
77    BackdropBlur {
78        id: UiId,
79        rect: UiRect,
80        style: BackdropBlurStyle,
81        phase: RenderPhase,
82    },
83    BackdropBlurPath {
84        id: UiId,
85        rect: UiRect,
86        path: UiPath,
87        style: BackdropBlurStyle,
88        phase: RenderPhase,
89    },
90    Overlay {
91        id: UiId,
92        rect: UiRect,
93        style: OverlayStyle,
94        phase: RenderPhase,
95    },
96    CompositingLayer {
97        id: UiId,
98        rect: UiRect,
99        spec: CompositingLayerSpec,
100        /// Commands use layer-local coordinates so moving a layer does not invalidate its surface.
101        commands: Vec<ScenePrimitive>,
102        content_signature: u64,
103        phase: RenderPhase,
104    },
105    StaticLayer {
106        id: UiId,
107        rect: UiRect,
108        spec: StaticLayerSpec,
109        commands: Vec<ScenePrimitive>,
110        child_signature: u64,
111        phase: RenderPhase,
112    },
113    ScrollRaster {
114        id: UiId,
115        viewport: UiRect,
116        spec: ScrollRasterSpec,
117        commands: Vec<ScenePrimitive>,
118        child_signature: u64,
119        phase: RenderPhase,
120    },
121    Clip {
122        id: UiId,
123        rect: UiRect,
124        commands: Vec<ScenePrimitive>,
125        child_signature: u64,
126        phase: RenderPhase,
127    },
128    ClipPath {
129        id: UiId,
130        rect: UiRect,
131        path: UiPath,
132        commands: Vec<ScenePrimitive>,
133        child_signature: u64,
134        phase: RenderPhase,
135    },
136}
137
138#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
139pub enum ScenePrimitiveKind {
140    Rect,
141    Ellipse,
142    Text,
143    Custom,
144    Line,
145    Path,
146    Image,
147    Icon,
148    Glow,
149    BackdropBlur,
150    BackdropBlurPath,
151    Overlay,
152    CompositingLayer,
153    StaticLayer,
154    ScrollRaster,
155    Clip,
156    ClipPath,
157}
158
159impl ScenePrimitiveKind {
160    pub const ALL: [Self; 17] = [
161        Self::Rect,
162        Self::Ellipse,
163        Self::Text,
164        Self::Custom,
165        Self::Line,
166        Self::Path,
167        Self::Image,
168        Self::Icon,
169        Self::Glow,
170        Self::BackdropBlur,
171        Self::BackdropBlurPath,
172        Self::Overlay,
173        Self::CompositingLayer,
174        Self::StaticLayer,
175        Self::ScrollRaster,
176        Self::Clip,
177        Self::ClipPath,
178    ];
179
180    pub const fn as_str(self) -> &'static str {
181        match self {
182            Self::Rect => "Rect",
183            Self::Ellipse => "Ellipse",
184            Self::Text => "Text",
185            Self::Custom => "Custom",
186            Self::Line => "Line",
187            Self::Path => "Path",
188            Self::Image => "Image",
189            Self::Icon => "Icon",
190            Self::Glow => "Glow",
191            Self::BackdropBlur => "BackdropBlur",
192            Self::BackdropBlurPath => "BackdropBlurPath",
193            Self::Overlay => "Overlay",
194            Self::CompositingLayer => "CompositingLayer",
195            Self::StaticLayer => "StaticLayer",
196            Self::ScrollRaster => "ScrollRaster",
197            Self::Clip => "Clip",
198            Self::ClipPath => "ClipPath",
199        }
200    }
201}
202
203#[derive(Clone, Debug, PartialEq)]
204pub struct ScrollRasterSpec {
205    pub cache_epoch: u64,
206    pub content_height: f32,
207    pub scroll_y: f32,
208    pub tile_height_px: f32,
209    pub memory_budget_bytes: usize,
210    pub background_fill: Option<Color>,
211    pub visible_tiles: Vec<usize>,
212    pub prefetch_tiles: Vec<usize>,
213    pub max_prefetch_tiles_per_frame: usize,
214    pub max_prefetch_ms_per_frame: u32,
215}
216
217impl Eq for ScrollRasterSpec {}
218
219impl Hash for ScrollRasterSpec {
220    fn hash<H: Hasher>(&self, state: &mut H) {
221        self.cache_epoch.hash(state);
222        normalized_f32_bits(self.content_height).hash(state);
223        normalized_f32_bits(self.scroll_y).hash(state);
224        normalized_f32_bits(self.tile_height_px).hash(state);
225        self.memory_budget_bytes.hash(state);
226        self.background_fill.hash(state);
227        self.visible_tiles.hash(state);
228        self.prefetch_tiles.hash(state);
229        self.max_prefetch_tiles_per_frame.hash(state);
230        self.max_prefetch_ms_per_frame.hash(state);
231    }
232}
233
234impl ScenePrimitive {
235    pub(crate) fn contains_shadow(&self) -> bool {
236        match self {
237            Self::CompositingLayer { spec, commands, .. } => {
238                spec.shadow.is_some() || commands.iter().any(Self::contains_shadow)
239            }
240            Self::StaticLayer { commands, .. }
241            | Self::ScrollRaster { commands, .. }
242            | Self::Clip { commands, .. }
243            | Self::ClipPath { commands, .. } => commands.iter().any(Self::contains_shadow),
244            _ => false,
245        }
246    }
247
248    pub const fn kind(&self) -> ScenePrimitiveKind {
249        match self {
250            Self::Rect { .. } => ScenePrimitiveKind::Rect,
251            Self::Ellipse { .. } => ScenePrimitiveKind::Ellipse,
252            Self::Text { .. } => ScenePrimitiveKind::Text,
253            Self::Custom { .. } => ScenePrimitiveKind::Custom,
254            Self::Line { .. } => ScenePrimitiveKind::Line,
255            Self::Path { .. } => ScenePrimitiveKind::Path,
256            Self::Image { .. } => ScenePrimitiveKind::Image,
257            Self::Icon { .. } => ScenePrimitiveKind::Icon,
258            Self::Glow { .. } => ScenePrimitiveKind::Glow,
259            Self::BackdropBlur { .. } => ScenePrimitiveKind::BackdropBlur,
260            Self::BackdropBlurPath { .. } => ScenePrimitiveKind::BackdropBlurPath,
261            Self::Overlay { .. } => ScenePrimitiveKind::Overlay,
262            Self::CompositingLayer { .. } => ScenePrimitiveKind::CompositingLayer,
263            Self::StaticLayer { .. } => ScenePrimitiveKind::StaticLayer,
264            Self::ScrollRaster { .. } => ScenePrimitiveKind::ScrollRaster,
265            Self::Clip { .. } => ScenePrimitiveKind::Clip,
266            Self::ClipPath { .. } => ScenePrimitiveKind::ClipPath,
267        }
268    }
269
270    pub fn id(&self) -> &UiId {
271        match self {
272            ScenePrimitive::Rect { id, .. }
273            | ScenePrimitive::Ellipse { id, .. }
274            | ScenePrimitive::Text { id, .. }
275            | ScenePrimitive::Custom { id, .. }
276            | ScenePrimitive::Line { id, .. }
277            | ScenePrimitive::Path { id, .. }
278            | ScenePrimitive::Image { id, .. }
279            | ScenePrimitive::Icon { id, .. }
280            | ScenePrimitive::Glow { id, .. }
281            | ScenePrimitive::BackdropBlur { id, .. }
282            | ScenePrimitive::BackdropBlurPath { id, .. }
283            | ScenePrimitive::Overlay { id, .. }
284            | ScenePrimitive::CompositingLayer { id, .. }
285            | ScenePrimitive::StaticLayer { id, .. }
286            | ScenePrimitive::ScrollRaster { id, .. }
287            | ScenePrimitive::Clip { id, .. }
288            | ScenePrimitive::ClipPath { id, .. } => id,
289        }
290    }
291
292    pub fn phase(&self) -> RenderPhase {
293        match self {
294            ScenePrimitive::Rect { phase, .. }
295            | ScenePrimitive::Ellipse { phase, .. }
296            | ScenePrimitive::Text { phase, .. }
297            | ScenePrimitive::Custom { phase, .. }
298            | ScenePrimitive::Line { phase, .. }
299            | ScenePrimitive::Path { phase, .. }
300            | ScenePrimitive::Image { phase, .. }
301            | ScenePrimitive::Icon { phase, .. }
302            | ScenePrimitive::Glow { phase, .. }
303            | ScenePrimitive::BackdropBlur { phase, .. }
304            | ScenePrimitive::BackdropBlurPath { phase, .. }
305            | ScenePrimitive::Overlay { phase, .. }
306            | ScenePrimitive::CompositingLayer { phase, .. }
307            | ScenePrimitive::StaticLayer { phase, .. }
308            | ScenePrimitive::ScrollRaster { phase, .. }
309            | ScenePrimitive::Clip { phase, .. }
310            | ScenePrimitive::ClipPath { phase, .. } => *phase,
311        }
312    }
313
314    pub fn rect(&self) -> UiRect {
315        match self {
316            ScenePrimitive::Rect { rect, .. }
317            | ScenePrimitive::Ellipse { rect, .. }
318            | ScenePrimitive::Text { rect, .. }
319            | ScenePrimitive::Custom { rect, .. }
320            | ScenePrimitive::Image { rect, .. }
321            | ScenePrimitive::Path { rect, .. }
322            | ScenePrimitive::Icon { rect, .. }
323            | ScenePrimitive::Glow { rect, .. }
324            | ScenePrimitive::BackdropBlur { rect, .. }
325            | ScenePrimitive::BackdropBlurPath { rect, .. }
326            | ScenePrimitive::Overlay { rect, .. } => *rect,
327            ScenePrimitive::CompositingLayer { rect, .. } => *rect,
328            ScenePrimitive::StaticLayer { rect, spec, .. } => {
329                rect.translate(spec.offset_x, spec.offset_y)
330            }
331            ScenePrimitive::ScrollRaster { viewport, .. } => *viewport,
332            ScenePrimitive::Clip { rect, .. } | ScenePrimitive::ClipPath { rect, .. } => *rect,
333            ScenePrimitive::Line { start, end, .. } => UiRect::new(
334                start.x.min(end.x),
335                start.y.min(end.y),
336                start.x.max(end.x) + 1.0,
337                start.y.max(end.y) + 1.0,
338            ),
339        }
340    }
341
342    pub fn paint_bounds(&self) -> UiRect {
343        let rect = match self {
344            ScenePrimitive::CompositingLayer { rect, spec, .. } => {
345                spec.transform.transformed_bounds(*rect)
346            }
347            _ => self.rect(),
348        };
349        let stroke_width = match self {
350            ScenePrimitive::Rect { style, .. } | ScenePrimitive::Ellipse { style, .. } => {
351                style.stroke.map(|stroke| stroke.width).unwrap_or(0.0)
352            }
353            ScenePrimitive::Line { stroke, .. } => stroke.width,
354            ScenePrimitive::Path { style, .. } => {
355                style.stroke.map(|stroke| stroke.width).unwrap_or(0.0)
356            }
357            _ => 0.0,
358        };
359        let outset = (stroke_width.max(0.0) + 1.0) / 2.0;
360        rect.inflate(outset, outset)
361    }
362
363    pub fn signature(&self) -> u64 {
364        let mut hasher = DefaultHasher::new();
365        command_signature_part(self, &mut hasher);
366        hasher.finish()
367    }
368}