Skip to main content

cranpose_render_pixels/
scene.rs

1use std::{rc::Rc, sync::Arc};
2
3pub use cranpose_render_common::graph_scene::{HitRegion, Scene};
4use cranpose_render_common::{layer_shadow::ShadowRRect, primitive_emit::ShapeDrawParams};
5use cranpose_ui::TextStyle;
6use cranpose_ui_graphics::{
7    ArcGeometry, BlendMode, Brush, Color, ColorFilter, ImageBitmap, ImageSampling, LineGeometry,
8    Point, Rect, RoundedCornerShape, Stroke, Trapezoid,
9};
10
11use crate::draw::RenderItem;
12
13#[derive(Clone, Debug)]
14pub(crate) struct DrawShape {
15    pub rect: Rect,
16    pub snap_anchor: Option<Point>,
17    pub snap_to_pixel_grid: bool,
18    pub brush: Brush,
19    pub shape: Option<RoundedCornerShape>,
20    pub stroke: Option<Stroke>,
21    pub arc: Option<ArcGeometry>,
22    pub line: Option<LineGeometry>,
23    pub trapezoid: Option<Trapezoid>,
24    pub clip: Option<Rect>,
25    pub blend_mode: BlendMode,
26}
27
28/// A point moved by `dx`, `dy`.
29fn shifted(point: Point, dx: f32, dy: f32) -> Point {
30    Point::new(point.x + dx, point.y + dy)
31}
32
33impl DrawShape {
34    /// The shape moved by `dx`, `dy`, with no clip.
35    pub(crate) fn translated(&self, dx: f32, dy: f32) -> Self {
36        Self {
37            rect: self.rect.translate(dx, dy),
38            snap_anchor: self.snap_anchor.map(|anchor| shifted(anchor, dx, dy)),
39            arc: self.arc.map(|arc| ArcGeometry {
40                center: shifted(arc.center, dx, dy),
41                ..arc
42            }),
43            line: self.line.map(|line| {
44                line.placed(shifted(line.start, dx, dy), shifted(line.end, dx, dy), 1.0)
45            }),
46            trapezoid: self.trapezoid.map(|trapezoid| trapezoid.translate(dx, dy)),
47            clip: None,
48            ..self.clone()
49        }
50    }
51
52    /// The shape `params` resolved.
53    pub(crate) fn of_params(params: ShapeDrawParams) -> Self {
54        Self {
55            rect: params.rect,
56            snap_anchor: None,
57            snap_to_pixel_grid: false,
58            brush: params.brush.into_brush(),
59            shape: params.shape,
60            stroke: params.stroke,
61            arc: params.arc,
62            line: params.line,
63            trapezoid: params.trapezoid,
64            clip: params.clip,
65            blend_mode: params.blend_mode,
66        }
67    }
68}
69
70#[derive(Clone, Debug)]
71pub(crate) struct TextDraw {
72    pub rect: Rect,
73    pub snap_anchor: Option<Point>,
74    pub text: Rc<cranpose_ui::text::AnnotatedString>,
75    pub color: Color,
76    pub text_style: Arc<TextStyle>,
77    pub font_size: f32,
78    pub scale: f32,
79    pub clip: Option<Rect>,
80}
81
82#[derive(Clone)]
83pub(crate) struct ImageDraw {
84    pub rect: Rect,
85    pub snap_anchor: Option<Point>,
86    pub image: ImageBitmap,
87    pub alpha: f32,
88    pub color_filter: Option<ColorFilter>,
89    pub sampling: ImageSampling,
90    pub clip: Option<Rect>,
91    pub blend_mode: BlendMode,
92    pub src_rect: Option<Rect>,
93}
94
95/// One pass of a layer's elevation shadow: Skia's round rect shadow in
96/// device pixels, its color and the clip it is cut to.
97#[derive(Clone, Copy, Debug)]
98pub(crate) struct ShadowDraw {
99    pub shadow: ShadowRRect,
100    pub snap_anchor: Option<Point>,
101    pub color: Color,
102    pub clip: Option<Rect>,
103}
104
105impl TextDraw {
106    /// The text moved by `dx`, `dy`, with no clip.
107    pub(crate) fn translated(&self, dx: f32, dy: f32) -> Self {
108        Self {
109            rect: self.rect.translate(dx, dy),
110            snap_anchor: self.snap_anchor.map(|anchor| shifted(anchor, dx, dy)),
111            clip: None,
112            ..self.clone()
113        }
114    }
115}
116
117impl ImageDraw {
118    /// The image moved by `dx`, `dy`, with no clip.
119    pub(crate) fn translated(&self, dx: f32, dy: f32) -> Self {
120        Self {
121            rect: self.rect.translate(dx, dy),
122            snap_anchor: self.snap_anchor.map(|anchor| shifted(anchor, dx, dy)),
123            clip: None,
124            ..self.clone()
125        }
126    }
127}
128
129impl ShadowDraw {
130    /// The shadow moved by `dx`, `dy`, with no clip.
131    pub(crate) fn translated(&self, dx: f32, dy: f32) -> Self {
132        Self {
133            shadow: self.shadow.translated(dx, dy),
134            snap_anchor: self.snap_anchor.map(|anchor| shifted(anchor, dx, dy)),
135            clip: None,
136            ..*self
137        }
138    }
139}
140
141pub(crate) struct RasterScene {
142    pub shapes: Vec<DrawShape>,
143    pub images: Vec<ImageDraw>,
144    pub texts: Vec<TextDraw>,
145    pub shadows: Vec<ShadowDraw>,
146    /// Every item in the order it was pushed, which is the order it draws in.
147    pub order: Vec<RenderItem>,
148}
149
150impl RasterScene {
151    pub fn new() -> Self {
152        Self {
153            shapes: Vec::new(),
154            images: Vec::new(),
155            texts: Vec::new(),
156            shadows: Vec::new(),
157            order: Vec::new(),
158        }
159    }
160
161    /// Empties the scene and keeps its storage.
162    pub(crate) fn clear(&mut self) {
163        self.shapes.clear();
164        self.images.clear();
165        self.texts.clear();
166        self.shadows.clear();
167        self.order.clear();
168    }
169
170    /// Queues `shape` to draw over everything queued before.
171    pub(crate) fn push_draw_shape(&mut self, shape: DrawShape) {
172        self.order.push(RenderItem::Shape(self.shapes.len()));
173        self.shapes.push(shape);
174    }
175
176    pub fn push_shape(
177        &mut self,
178        rect: Rect,
179        brush: Brush,
180        shape: Option<RoundedCornerShape>,
181        clip: Option<Rect>,
182        blend_mode: BlendMode,
183    ) {
184        self.push_draw_shape(DrawShape {
185            rect,
186            snap_anchor: None,
187            snap_to_pixel_grid: false,
188            brush,
189            shape,
190            stroke: None,
191            arc: None,
192            line: None,
193            trapezoid: None,
194            clip,
195            blend_mode,
196        });
197    }
198
199    pub fn push_shadow(&mut self, shadow: ShadowRRect, color: Color, clip: Option<Rect>) {
200        self.order.push(RenderItem::Shadow(self.shadows.len()));
201        self.shadows.push(ShadowDraw {
202            shadow,
203            snap_anchor: None,
204            color,
205            clip,
206        });
207    }
208
209    pub fn push_pixel_snapped_shape(
210        &mut self,
211        rect: Rect,
212        brush: Brush,
213        shape: Option<RoundedCornerShape>,
214        clip: Option<Rect>,
215        blend_mode: BlendMode,
216    ) {
217        self.push_draw_shape(DrawShape {
218            rect,
219            snap_anchor: None,
220            snap_to_pixel_grid: true,
221            brush,
222            shape,
223            stroke: None,
224            arc: None,
225            line: None,
226            trapezoid: None,
227            clip,
228            blend_mode,
229        });
230    }
231
232    /// Queues a shape an emitted primitive resolved: a rect, rounded rect,
233    /// stroke, arc band, line segment or slice of a path fill.
234    pub fn push_shape_params(&mut self, params: ShapeDrawParams) {
235        self.push_draw_shape(DrawShape::of_params(params));
236    }
237
238    #[expect(clippy::too_many_arguments)]
239    pub fn push_image_with_geometry(
240        &mut self,
241        rect: Rect,
242        _local_rect: Rect,
243        _quad: [[f32; 2]; 4],
244        image: ImageBitmap,
245        alpha: f32,
246        color_filter: Option<ColorFilter>,
247        sampling: ImageSampling,
248        clip: Option<Rect>,
249        src_rect: Option<Rect>,
250        blend_mode: BlendMode,
251    ) {
252        self.order.push(RenderItem::Image(self.images.len()));
253        self.images.push(ImageDraw {
254            rect,
255            snap_anchor: None,
256            image,
257            alpha: alpha.clamp(0.0, 1.0),
258            color_filter,
259            sampling,
260            clip,
261            blend_mode,
262            src_rect,
263        });
264    }
265
266    #[expect(clippy::too_many_arguments)]
267    pub fn push_text(
268        &mut self,
269        rect: Rect,
270        text: Rc<cranpose_ui::text::AnnotatedString>,
271        color: Color,
272        text_style: Arc<TextStyle>,
273        font_size: f32,
274        scale: f32,
275        clip: Option<Rect>,
276    ) {
277        self.order.push(RenderItem::Text(self.texts.len()));
278        self.texts.push(TextDraw {
279            rect,
280            snap_anchor: None,
281            text,
282            color,
283            text_style,
284            font_size,
285            scale,
286            clip,
287        });
288    }
289}
290
291impl Default for RasterScene {
292    fn default() -> Self {
293        Self::new()
294    }
295}