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        blur: Option<BlurStyle>,
62        phase: RenderPhase,
63    },
64    Icon {
65        id: UiId,
66        rect: UiRect,
67        key: &'static str,
68        style: super::IconStyle,
69        phase: RenderPhase,
70    },
71    Glow {
72        id: UiId,
73        rect: UiRect,
74        color: super::Color,
75        alpha: u8,
76        phase: RenderPhase,
77    },
78    BackdropBlur {
79        id: UiId,
80        rect: UiRect,
81        style: BlurStyle,
82        phase: RenderPhase,
83    },
84    BackdropBlurPath {
85        id: UiId,
86        rect: UiRect,
87        path: UiPath,
88        style: BlurStyle,
89        phase: RenderPhase,
90    },
91    ContentBlur {
92        id: UiId,
93        rect: UiRect,
94        style: BlurStyle,
95        commands: Vec<ScenePrimitive>,
96        child_signature: u64,
97        phase: RenderPhase,
98    },
99    Overlay {
100        id: UiId,
101        rect: UiRect,
102        style: OverlayStyle,
103        phase: RenderPhase,
104    },
105    CompositingLayer {
106        id: UiId,
107        rect: UiRect,
108        spec: CompositingLayerSpec,
109        /// Commands use layer-local coordinates so moving a layer does not invalidate its surface.
110        commands: Vec<ScenePrimitive>,
111        content_signature: u64,
112        phase: RenderPhase,
113    },
114    StaticLayer {
115        id: UiId,
116        rect: UiRect,
117        spec: StaticLayerSpec,
118        commands: Vec<ScenePrimitive>,
119        child_signature: u64,
120        phase: RenderPhase,
121    },
122    ScrollRaster {
123        id: UiId,
124        viewport: UiRect,
125        spec: ScrollRasterSpec,
126        commands: Vec<ScenePrimitive>,
127        child_signature: u64,
128        phase: RenderPhase,
129    },
130    Clip {
131        id: UiId,
132        rect: UiRect,
133        commands: Vec<ScenePrimitive>,
134        child_signature: u64,
135        phase: RenderPhase,
136    },
137    ClipPath {
138        id: UiId,
139        rect: UiRect,
140        path: UiPath,
141        commands: Vec<ScenePrimitive>,
142        child_signature: u64,
143        phase: RenderPhase,
144    },
145}
146
147#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
148pub enum ScenePrimitiveKind {
149    Rect,
150    Ellipse,
151    Text,
152    Custom,
153    Line,
154    Path,
155    Image,
156    Icon,
157    Glow,
158    BackdropBlur,
159    BackdropBlurPath,
160    ContentBlur,
161    Overlay,
162    CompositingLayer,
163    StaticLayer,
164    ScrollRaster,
165    Clip,
166    ClipPath,
167}
168
169impl ScenePrimitiveKind {
170    pub const ALL: [Self; 18] = [
171        Self::Rect,
172        Self::Ellipse,
173        Self::Text,
174        Self::Custom,
175        Self::Line,
176        Self::Path,
177        Self::Image,
178        Self::Icon,
179        Self::Glow,
180        Self::BackdropBlur,
181        Self::BackdropBlurPath,
182        Self::ContentBlur,
183        Self::Overlay,
184        Self::CompositingLayer,
185        Self::StaticLayer,
186        Self::ScrollRaster,
187        Self::Clip,
188        Self::ClipPath,
189    ];
190
191    pub const fn as_str(self) -> &'static str {
192        match self {
193            Self::Rect => "Rect",
194            Self::Ellipse => "Ellipse",
195            Self::Text => "Text",
196            Self::Custom => "Custom",
197            Self::Line => "Line",
198            Self::Path => "Path",
199            Self::Image => "Image",
200            Self::Icon => "Icon",
201            Self::Glow => "Glow",
202            Self::BackdropBlur => "BackdropBlur",
203            Self::BackdropBlurPath => "BackdropBlurPath",
204            Self::ContentBlur => "ContentBlur",
205            Self::Overlay => "Overlay",
206            Self::CompositingLayer => "CompositingLayer",
207            Self::StaticLayer => "StaticLayer",
208            Self::ScrollRaster => "ScrollRaster",
209            Self::Clip => "Clip",
210            Self::ClipPath => "ClipPath",
211        }
212    }
213}
214
215#[derive(Clone, Debug, PartialEq)]
216pub struct ScrollRasterSpec {
217    pub cache_epoch: u64,
218    pub content_height: f32,
219    pub scroll_y: f32,
220    pub tile_height_px: f32,
221    pub memory_budget_bytes: usize,
222    pub background_fill: Option<Color>,
223    pub visible_tiles: Vec<usize>,
224    pub prefetch_tiles: Vec<usize>,
225    pub max_prefetch_tiles_per_frame: usize,
226    pub max_prefetch_ms_per_frame: u32,
227}
228
229impl Eq for ScrollRasterSpec {}
230
231impl Hash for ScrollRasterSpec {
232    fn hash<H: Hasher>(&self, state: &mut H) {
233        self.cache_epoch.hash(state);
234        normalized_f32_bits(self.content_height).hash(state);
235        normalized_f32_bits(self.scroll_y).hash(state);
236        normalized_f32_bits(self.tile_height_px).hash(state);
237        self.memory_budget_bytes.hash(state);
238        self.background_fill.hash(state);
239        self.visible_tiles.hash(state);
240        self.prefetch_tiles.hash(state);
241        self.max_prefetch_tiles_per_frame.hash(state);
242        self.max_prefetch_ms_per_frame.hash(state);
243    }
244}
245
246impl ScenePrimitive {
247    pub(crate) fn contains_shadow(&self) -> bool {
248        match self {
249            Self::CompositingLayer { spec, commands, .. } => {
250                spec.shadow.is_some() || commands.iter().any(Self::contains_shadow)
251            }
252            Self::StaticLayer { commands, .. }
253            | Self::ScrollRaster { commands, .. }
254            | Self::ContentBlur { commands, .. }
255            | Self::Clip { commands, .. }
256            | Self::ClipPath { commands, .. } => commands.iter().any(Self::contains_shadow),
257            _ => false,
258        }
259    }
260
261    pub const fn kind(&self) -> ScenePrimitiveKind {
262        match self {
263            Self::Rect { .. } => ScenePrimitiveKind::Rect,
264            Self::Ellipse { .. } => ScenePrimitiveKind::Ellipse,
265            Self::Text { .. } => ScenePrimitiveKind::Text,
266            Self::Custom { .. } => ScenePrimitiveKind::Custom,
267            Self::Line { .. } => ScenePrimitiveKind::Line,
268            Self::Path { .. } => ScenePrimitiveKind::Path,
269            Self::Image { .. } => ScenePrimitiveKind::Image,
270            Self::Icon { .. } => ScenePrimitiveKind::Icon,
271            Self::Glow { .. } => ScenePrimitiveKind::Glow,
272            Self::BackdropBlur { .. } => ScenePrimitiveKind::BackdropBlur,
273            Self::BackdropBlurPath { .. } => ScenePrimitiveKind::BackdropBlurPath,
274            Self::ContentBlur { .. } => ScenePrimitiveKind::ContentBlur,
275            Self::Overlay { .. } => ScenePrimitiveKind::Overlay,
276            Self::CompositingLayer { .. } => ScenePrimitiveKind::CompositingLayer,
277            Self::StaticLayer { .. } => ScenePrimitiveKind::StaticLayer,
278            Self::ScrollRaster { .. } => ScenePrimitiveKind::ScrollRaster,
279            Self::Clip { .. } => ScenePrimitiveKind::Clip,
280            Self::ClipPath { .. } => ScenePrimitiveKind::ClipPath,
281        }
282    }
283
284    pub fn id(&self) -> &UiId {
285        match self {
286            ScenePrimitive::Rect { id, .. }
287            | ScenePrimitive::Ellipse { id, .. }
288            | ScenePrimitive::Text { id, .. }
289            | ScenePrimitive::Custom { id, .. }
290            | ScenePrimitive::Line { id, .. }
291            | ScenePrimitive::Path { id, .. }
292            | ScenePrimitive::Image { id, .. }
293            | ScenePrimitive::Icon { id, .. }
294            | ScenePrimitive::Glow { id, .. }
295            | ScenePrimitive::BackdropBlur { id, .. }
296            | ScenePrimitive::BackdropBlurPath { id, .. }
297            | ScenePrimitive::ContentBlur { id, .. }
298            | ScenePrimitive::Overlay { id, .. }
299            | ScenePrimitive::CompositingLayer { id, .. }
300            | ScenePrimitive::StaticLayer { id, .. }
301            | ScenePrimitive::ScrollRaster { id, .. }
302            | ScenePrimitive::Clip { id, .. }
303            | ScenePrimitive::ClipPath { id, .. } => id,
304        }
305    }
306
307    pub fn phase(&self) -> RenderPhase {
308        match self {
309            ScenePrimitive::Rect { phase, .. }
310            | ScenePrimitive::Ellipse { phase, .. }
311            | ScenePrimitive::Text { phase, .. }
312            | ScenePrimitive::Custom { phase, .. }
313            | ScenePrimitive::Line { phase, .. }
314            | ScenePrimitive::Path { phase, .. }
315            | ScenePrimitive::Image { phase, .. }
316            | ScenePrimitive::Icon { phase, .. }
317            | ScenePrimitive::Glow { phase, .. }
318            | ScenePrimitive::BackdropBlur { phase, .. }
319            | ScenePrimitive::BackdropBlurPath { phase, .. }
320            | ScenePrimitive::ContentBlur { phase, .. }
321            | ScenePrimitive::Overlay { phase, .. }
322            | ScenePrimitive::CompositingLayer { phase, .. }
323            | ScenePrimitive::StaticLayer { phase, .. }
324            | ScenePrimitive::ScrollRaster { phase, .. }
325            | ScenePrimitive::Clip { phase, .. }
326            | ScenePrimitive::ClipPath { phase, .. } => *phase,
327        }
328    }
329
330    pub fn rect(&self) -> UiRect {
331        match self {
332            ScenePrimitive::Rect { rect, .. }
333            | ScenePrimitive::Ellipse { rect, .. }
334            | ScenePrimitive::Text { rect, .. }
335            | ScenePrimitive::Custom { rect, .. }
336            | ScenePrimitive::Image { rect, .. }
337            | ScenePrimitive::Path { rect, .. }
338            | ScenePrimitive::Icon { rect, .. }
339            | ScenePrimitive::Glow { rect, .. }
340            | ScenePrimitive::BackdropBlur { rect, .. }
341            | ScenePrimitive::BackdropBlurPath { rect, .. }
342            | ScenePrimitive::ContentBlur { rect, .. }
343            | ScenePrimitive::Overlay { rect, .. } => *rect,
344            ScenePrimitive::CompositingLayer { rect, .. } => *rect,
345            ScenePrimitive::StaticLayer { rect, spec, .. } => {
346                rect.translate(spec.offset_x, spec.offset_y)
347            }
348            ScenePrimitive::ScrollRaster { viewport, .. } => *viewport,
349            ScenePrimitive::Clip { rect, .. } | ScenePrimitive::ClipPath { rect, .. } => *rect,
350            ScenePrimitive::Line { start, end, .. } => UiRect::new(
351                start.x.min(end.x),
352                start.y.min(end.y),
353                start.x.max(end.x) + 1.0,
354                start.y.max(end.y) + 1.0,
355            ),
356        }
357    }
358
359    pub fn paint_bounds(&self) -> UiRect {
360        let rect = match self {
361            ScenePrimitive::CompositingLayer { rect, spec, .. } => {
362                spec.transform.transformed_bounds(*rect)
363            }
364            _ => self.rect(),
365        };
366        let stroke_width = match self {
367            ScenePrimitive::Rect { style, .. } | ScenePrimitive::Ellipse { style, .. } => {
368                style.stroke.map(|stroke| stroke.width).unwrap_or(0.0)
369            }
370            ScenePrimitive::Line { stroke, .. } => stroke.width,
371            ScenePrimitive::Path { style, .. } => {
372                style.stroke.map(|stroke| stroke.width).unwrap_or(0.0)
373            }
374            _ => 0.0,
375        };
376        let blur_outset = match self {
377            ScenePrimitive::BackdropBlur { style, .. }
378            | ScenePrimitive::BackdropBlurPath { style, .. }
379            | ScenePrimitive::ContentBlur { style, .. } => style.outset(),
380            ScenePrimitive::Image {
381                blur: Some(blur), ..
382            } => blur.outset(),
383            _ => 0.0,
384        };
385        let outset = (stroke_width.max(0.0) + 1.0) / 2.0 + blur_outset;
386        rect.inflate(outset, outset)
387    }
388
389    pub fn signature(&self) -> u64 {
390        let mut hasher = DefaultHasher::new();
391        command_signature_part(self, &mut hasher);
392        hasher.finish()
393    }
394}