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
33fn shifted(point: Point, dx: f32, dy: f32) -> Point {
35 Point::new(point.x + dx, point.y + dy)
36}
37
38impl DrawShape {
39 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 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#[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 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 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 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#[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 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#[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#[derive(Clone, Copy)]
185pub(crate) struct SubtreeItems {
186 pub identity: (Option<NodeId>, Option<NodeId>),
188 pub change: LayerChange,
191 pub transform: ProjectiveTransform,
193 pub passed_down: Option<usize>,
196 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 pub order: Vec<RenderItem>,
210 pub subtrees: Vec<SubtreeItems>,
213 pub passed_down: Vec<PassedDown>,
215 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 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 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 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 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 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 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 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}