Skip to main content

cranpose_render_pixels/
scene.rs

1use std::{ops::Range, rc::Rc, sync::Arc};
2
3use cranpose_core::NodeId;
4pub use cranpose_render_common::graph_scene::{HitRegion, Scene};
5use cranpose_render_common::{
6    graph::{LayerChange, LayerNode, ProjectiveTransform},
7    layer_shadow::{ShadowLight, ShadowRRect},
8    primitive_emit::ShapeDrawParams,
9};
10use cranpose_ui::TextStyle;
11use cranpose_ui_graphics::{
12    ArcGeometry, BlendMode, Brush, Color, ColorFilter, ImageBitmap, ImageSampling, LineGeometry,
13    Point, Rect, RoundedCornerShape, Stroke, Trapezoid,
14};
15
16use crate::draw::RenderItem;
17
18#[derive(Clone, Debug)]
19pub(crate) struct DrawShape {
20    pub rect: Rect,
21    pub snap_anchor: Option<Point>,
22    pub snap_to_pixel_grid: bool,
23    pub brush: Brush,
24    pub shape: Option<RoundedCornerShape>,
25    pub stroke: Option<Stroke>,
26    pub arc: Option<ArcGeometry>,
27    pub line: Option<LineGeometry>,
28    pub trapezoid: Option<Trapezoid>,
29    pub clip: Option<Rect>,
30    pub blend_mode: BlendMode,
31}
32
33/// A point moved by `dx`, `dy`.
34fn shifted(point: Point, dx: f32, dy: f32) -> Point {
35    Point::new(point.x + dx, point.y + dy)
36}
37
38impl DrawShape {
39    /// The shape moved by `dx`, `dy`, with no clip.
40    pub(crate) fn translated(&self, dx: f32, dy: f32) -> Self {
41        Self {
42            rect: self.rect.translate(dx, dy),
43            snap_anchor: self.snap_anchor.map(|anchor| shifted(anchor, dx, dy)),
44            arc: self.arc.map(|arc| ArcGeometry {
45                center: shifted(arc.center, dx, dy),
46                ..arc
47            }),
48            line: self.line.map(|line| {
49                line.placed(shifted(line.start, dx, dy), shifted(line.end, dx, dy), 1.0)
50            }),
51            trapezoid: self.trapezoid.map(|trapezoid| trapezoid.translate(dx, dy)),
52            clip: None,
53            ..self.clone()
54        }
55    }
56
57    /// The shape `params` resolved.
58    pub(crate) fn of_params(params: ShapeDrawParams) -> Self {
59        Self {
60            rect: params.rect,
61            snap_anchor: None,
62            snap_to_pixel_grid: false,
63            brush: params.brush.into_brush(),
64            shape: params.shape,
65            stroke: params.stroke,
66            arc: params.arc,
67            line: params.line,
68            trapezoid: params.trapezoid,
69            clip: params.clip,
70            blend_mode: params.blend_mode,
71        }
72    }
73}
74
75#[derive(Clone, Debug)]
76pub(crate) struct TextDraw {
77    pub rect: Rect,
78    pub snap_anchor: Option<Point>,
79    pub text: Rc<cranpose_ui::text::AnnotatedString>,
80    pub color: Color,
81    pub text_style: Arc<TextStyle>,
82    pub font_size: f32,
83    pub scale: f32,
84    pub clip: Option<Rect>,
85}
86
87#[derive(Clone)]
88pub(crate) struct ImageDraw {
89    pub rect: Rect,
90    pub snap_anchor: Option<Point>,
91    pub image: ImageBitmap,
92    pub alpha: f32,
93    pub color_filter: Option<ColorFilter>,
94    pub sampling: ImageSampling,
95    pub clip: Option<Rect>,
96    pub blend_mode: BlendMode,
97    pub src_rect: Option<Rect>,
98}
99
100/// One pass of a layer's elevation shadow: Skia's round rect shadow in
101/// device pixels, its color and the clip it is cut to.
102#[derive(Clone, Copy, Debug)]
103pub(crate) struct ShadowDraw {
104    pub shadow: ShadowRRect,
105    pub snap_anchor: Option<Point>,
106    pub color: Color,
107    pub clip: Option<Rect>,
108}
109
110impl TextDraw {
111    /// The text moved by `dx`, `dy`, with no clip.
112    pub(crate) fn translated(&self, dx: f32, dy: f32) -> Self {
113        Self {
114            rect: self.rect.translate(dx, dy),
115            snap_anchor: self.snap_anchor.map(|anchor| shifted(anchor, dx, dy)),
116            clip: None,
117            ..self.clone()
118        }
119    }
120}
121
122impl ImageDraw {
123    /// The image moved by `dx`, `dy`, with no clip.
124    pub(crate) fn translated(&self, dx: f32, dy: f32) -> Self {
125        Self {
126            rect: self.rect.translate(dx, dy),
127            snap_anchor: self.snap_anchor.map(|anchor| shifted(anchor, dx, dy)),
128            clip: None,
129            ..self.clone()
130        }
131    }
132}
133
134impl ShadowDraw {
135    /// The shadow moved by `dx`, `dy`, with no clip.
136    pub(crate) fn translated(&self, dx: f32, dy: f32) -> Self {
137        Self {
138            shadow: self.shadow.translated(dx, dy),
139            snap_anchor: self.snap_anchor.map(|anchor| shifted(anchor, dx, dy)),
140            clip: None,
141            ..*self
142        }
143    }
144}
145
146/// How many items of each kind a scene holds where a run of its items starts
147/// or stops.
148#[derive(Clone, Copy, Default)]
149pub(crate) struct ItemCounts {
150    pub order: usize,
151    pub shapes: usize,
152    pub images: usize,
153    pub texts: usize,
154    pub shadows: usize,
155}
156
157impl ItemCounts {
158    /// The counts of a run copied from where `from` began in one scene to
159    /// where `to` begins in another.
160    fn moved(self, from: Self, to: Self) -> Self {
161        Self {
162            order: to.order + self.order.saturating_sub(from.order),
163            shapes: to.shapes + self.shapes.saturating_sub(from.shapes),
164            images: to.images + self.images.saturating_sub(from.images),
165            texts: to.texts + self.texts.saturating_sub(from.texts),
166            shadows: to.shadows + self.shadows.saturating_sub(from.shadows),
167        }
168    }
169}
170
171/// What a layer passes down to the walks of its child layers.
172#[derive(Clone, PartialEq)]
173pub(crate) struct PassedDown {
174    pub transform: ProjectiveTransform,
175    pub alpha: f32,
176    pub color_filter: Option<ColorFilter>,
177    pub clip: Option<Rect>,
178    pub snap_anchor: Option<Point>,
179    pub translated: bool,
180}
181
182/// The items a layer's subtree pushed into a scene, in a run of each list,
183/// so the next scene can copy them when nothing they depend on changed.
184#[derive(Clone, Copy)]
185pub(crate) struct SubtreeItems {
186    /// The layer's node and the node whose outer draws it wraps.
187    pub identity: (Option<NodeId>, Option<NodeId>),
188    /// The layer's change number, which covers its subtree, when it pushed
189    /// the items.
190    pub change: LayerChange,
191    /// The layer's placement in its parent, which the number leaves out.
192    pub transform: ProjectiveTransform,
193    /// Where [`RasterScene::passed_down`] holds what the layer passed to its
194    /// child layers, when it has any.
195    pub passed_down: Option<usize>,
196    /// The index of the first record after the subtree's, its descendants'
197    /// included.
198    pub end: usize,
199    pub start: ItemCounts,
200    pub stop: ItemCounts,
201}
202
203pub(crate) struct RasterScene {
204    pub shapes: Vec<DrawShape>,
205    pub images: Vec<ImageDraw>,
206    pub texts: Vec<TextDraw>,
207    pub shadows: Vec<ShadowDraw>,
208    /// Every item in the order it was pushed, which is the order it draws in.
209    pub order: Vec<RenderItem>,
210    /// A record for each layer the scene was built from, in the order the
211    /// layers were visited: a layer's descendants follow it.
212    pub subtrees: Vec<SubtreeItems>,
213    /// What the layers with child layers passed down to them.
214    pub passed_down: Vec<PassedDown>,
215    /// The scale and the shadow light the walk started from.
216    pub start: Option<(f32, ShadowLight)>,
217}
218
219impl RasterScene {
220    pub fn new() -> Self {
221        Self {
222            shapes: Vec::new(),
223            images: Vec::new(),
224            texts: Vec::new(),
225            shadows: Vec::new(),
226            order: Vec::new(),
227            subtrees: Vec::new(),
228            passed_down: Vec::new(),
229            start: None,
230        }
231    }
232
233    /// Empties the scene and keeps its storage.
234    pub(crate) fn clear(&mut self) {
235        self.shapes.clear();
236        self.images.clear();
237        self.texts.clear();
238        self.shadows.clear();
239        self.order.clear();
240        self.subtrees.clear();
241        self.passed_down.clear();
242        self.start = None;
243    }
244
245    pub(crate) fn counts(&self) -> ItemCounts {
246        ItemCounts {
247            order: self.order.len(),
248            shapes: self.shapes.len(),
249            images: self.images.len(),
250            texts: self.texts.len(),
251            shadows: self.shadows.len(),
252        }
253    }
254
255    /// Opens the record of `layer`'s subtree, whose items come next, and
256    /// returns its index for [`Self::close_subtree`].
257    pub(crate) fn open_subtree(&mut self, layer: &LayerNode) -> usize {
258        let index = self.subtrees.len();
259        let counts = self.counts();
260        self.subtrees.push(SubtreeItems {
261            identity: (layer.node_id, layer.wraps),
262            change: layer.change,
263            transform: layer.transform_to_parent,
264            passed_down: None,
265            end: index + 1,
266            start: counts,
267            stop: counts,
268        });
269        index
270    }
271
272    /// Notes in the record `index` what its layer passes down.
273    pub(crate) fn note_passed_down(&mut self, index: usize, passed_down: PassedDown) {
274        let at = self.passed_down.len();
275        self.passed_down.push(passed_down);
276        if let Some(record) = self.subtrees.get_mut(index) {
277            record.passed_down = Some(at);
278        }
279    }
280
281    /// Closes the record `index`: the subtree pushed everything since it
282    /// opened.
283    pub(crate) fn close_subtree(&mut self, index: usize) {
284        let end = self.subtrees.len();
285        let stop = self.counts();
286        if let Some(record) = self.subtrees.get_mut(index) {
287            record.end = end;
288            record.stop = stop;
289        }
290    }
291
292    /// Pushes the items and records of the subtrees `old` recorded at
293    /// `records`, siblings one after another, which pushed their items there
294    /// up to `stop`.
295    pub(crate) fn copy_subtrees(
296        &mut self,
297        old: &RasterScene,
298        records: Range<usize>,
299        stop: ItemCounts,
300    ) {
301        let Some(from) = old.subtrees.get(records.start).map(|first| first.start) else {
302            return;
303        };
304        let to = self.counts();
305        let run = |start: usize, stop: usize| start..stop.max(start);
306        self.shapes.extend_from_slice(
307            old.shapes
308                .get(run(from.shapes, stop.shapes))
309                .unwrap_or_default(),
310        );
311        self.images.extend_from_slice(
312            old.images
313                .get(run(from.images, stop.images))
314                .unwrap_or_default(),
315        );
316        self.texts.extend_from_slice(
317            old.texts
318                .get(run(from.texts, stop.texts))
319                .unwrap_or_default(),
320        );
321        self.shadows.extend_from_slice(
322            old.shadows
323                .get(run(from.shadows, stop.shadows))
324                .unwrap_or_default(),
325        );
326        let moved = |index: usize, from: usize, to: usize| to + index.saturating_sub(from);
327        self.order.extend(
328            old.order
329                .get(run(from.order, stop.order))
330                .unwrap_or_default()
331                .iter()
332                .map(|item| match *item {
333                    RenderItem::Shape(index) => {
334                        RenderItem::Shape(moved(index, from.shapes, to.shapes))
335                    }
336                    RenderItem::Image(index) => {
337                        RenderItem::Image(moved(index, from.images, to.images))
338                    }
339                    RenderItem::Text(index) => RenderItem::Text(moved(index, from.texts, to.texts)),
340                    RenderItem::Shadow(index) => {
341                        RenderItem::Shadow(moved(index, from.shadows, to.shadows))
342                    }
343                }),
344        );
345        let base = self.subtrees.len();
346        let first = records.start;
347        self.subtrees
348            .extend_from_slice(old.subtrees.get(records).unwrap_or_default());
349        let passed_down = &mut self.passed_down;
350        for record in self.subtrees.get_mut(base..).unwrap_or_default() {
351            record.end = moved(record.end, first, base);
352            record.start = record.start.moved(from, to);
353            record.stop = record.stop.moved(from, to);
354            record.passed_down = record
355                .passed_down
356                .and_then(|at| old.passed_down.get(at))
357                .map(|copied| {
358                    passed_down.push(copied.clone());
359                    passed_down.len() - 1
360                });
361        }
362    }
363
364    /// Queues `shape` to draw over everything queued before.
365    pub(crate) fn push_draw_shape(&mut self, shape: DrawShape) {
366        self.order.push(RenderItem::Shape(self.shapes.len()));
367        self.shapes.push(shape);
368    }
369
370    pub fn push_shape(
371        &mut self,
372        rect: Rect,
373        brush: Brush,
374        shape: Option<RoundedCornerShape>,
375        clip: Option<Rect>,
376        blend_mode: BlendMode,
377    ) {
378        self.push_draw_shape(DrawShape {
379            rect,
380            snap_anchor: None,
381            snap_to_pixel_grid: false,
382            brush,
383            shape,
384            stroke: None,
385            arc: None,
386            line: None,
387            trapezoid: None,
388            clip,
389            blend_mode,
390        });
391    }
392
393    pub fn push_shadow(&mut self, shadow: ShadowRRect, color: Color, clip: Option<Rect>) {
394        self.order.push(RenderItem::Shadow(self.shadows.len()));
395        self.shadows.push(ShadowDraw {
396            shadow,
397            snap_anchor: None,
398            color,
399            clip,
400        });
401    }
402
403    pub fn push_pixel_snapped_shape(
404        &mut self,
405        rect: Rect,
406        brush: Brush,
407        shape: Option<RoundedCornerShape>,
408        clip: Option<Rect>,
409        blend_mode: BlendMode,
410    ) {
411        self.push_draw_shape(DrawShape {
412            rect,
413            snap_anchor: None,
414            snap_to_pixel_grid: true,
415            brush,
416            shape,
417            stroke: None,
418            arc: None,
419            line: None,
420            trapezoid: None,
421            clip,
422            blend_mode,
423        });
424    }
425
426    /// Queues a shape an emitted primitive resolved: a rect, rounded rect,
427    /// stroke, arc band, line segment or slice of a path fill.
428    pub fn push_shape_params(&mut self, params: ShapeDrawParams) {
429        self.push_draw_shape(DrawShape::of_params(params));
430    }
431
432    #[expect(clippy::too_many_arguments)]
433    pub fn push_image_with_geometry(
434        &mut self,
435        rect: Rect,
436        _local_rect: Rect,
437        _quad: [[f32; 2]; 4],
438        image: ImageBitmap,
439        alpha: f32,
440        color_filter: Option<ColorFilter>,
441        sampling: ImageSampling,
442        clip: Option<Rect>,
443        src_rect: Option<Rect>,
444        blend_mode: BlendMode,
445    ) {
446        self.order.push(RenderItem::Image(self.images.len()));
447        self.images.push(ImageDraw {
448            rect,
449            snap_anchor: None,
450            image,
451            alpha: alpha.clamp(0.0, 1.0),
452            color_filter,
453            sampling,
454            clip,
455            blend_mode,
456            src_rect,
457        });
458    }
459
460    #[expect(clippy::too_many_arguments)]
461    pub fn push_text(
462        &mut self,
463        rect: Rect,
464        text: Rc<cranpose_ui::text::AnnotatedString>,
465        color: Color,
466        text_style: Arc<TextStyle>,
467        font_size: f32,
468        scale: f32,
469        clip: Option<Rect>,
470    ) {
471        self.order.push(RenderItem::Text(self.texts.len()));
472        self.texts.push(TextDraw {
473            rect,
474            snap_anchor: None,
475            text,
476            color,
477            text_style,
478            font_size,
479            scale,
480            clip,
481        });
482    }
483}
484
485impl Default for RasterScene {
486    fn default() -> Self {
487        Self::new()
488    }
489}