Skip to main content

cranpose_render_pixels/
scene.rs

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