1use std::{rc::Rc, sync::Arc};
2
3use cranpose_ui::text::{AnnotatedString, TextStyle, drawn_text_style};
4use cranpose_ui_graphics::{
5 ArcGeometry, BlendMode, Brush, Color, ColorFilter, CornerRadii, DrawPrimitive, GraphicsLayer,
6 ImageBitmap, ImageSampling, LineGeometry, Point, Rect, RoundedCornerShape, ShadowPrimitive,
7 Stroke, TextPrimitive, Trapezoid, arc_band, inflate_rect,
8};
9
10use crate::{
11 graph::quad_bounds,
12 layer_transform::{
13 apply_layer_affine_to_point, apply_layer_affine_to_rect, apply_layer_to_quad,
14 apply_layer_to_rect, layer_uniform_scale,
15 },
16 style_shared::{
17 ResolvedBrush, apply_layer_to_brush, apply_layer_to_color, compose_color_filters,
18 resolve_layer_brush, scale_corner_radii,
19 },
20};
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum PrimitiveClipSpace {
25 Local,
27 LayerTransformed,
29}
30
31pub struct ShapeDrawParams {
33 pub rect: Rect,
35 pub local_rect: Rect,
37 pub quad: [[f32; 2]; 4],
39 pub brush: ResolvedBrush,
43 pub shape: Option<RoundedCornerShape>,
45 pub stroke: Option<Stroke>,
50 pub arc: Option<ArcGeometry>,
53 pub line: Option<LineGeometry>,
56 pub trapezoid: Option<Trapezoid>,
60 pub clip: Option<Rect>,
62 pub blend_mode: BlendMode,
64 pub motion_context_animated: bool,
67}
68
69fn stroked_draw_rect(
70 local_rect: Rect,
71 stroke: Option<Stroke>,
72 layer_bounds: Rect,
73 layer: &GraphicsLayer,
74) -> Option<(Rect, Option<Stroke>)> {
75 let draw_rect = local_rect.translate(layer_bounds.x, layer_bounds.y);
76 let Some(stroke) = stroke else {
77 return Some((draw_rect, None));
78 };
79 if !stroke.is_visible() {
80 return None;
81 }
82 let outset = stroke.half_width();
83 Some((
84 inflate_rect(draw_rect, outset),
85 Some(stroke.scaled(layer_uniform_scale(layer))),
86 ))
87}
88
89pub struct ImageDrawParams {
91 pub rect: Rect,
93 pub local_rect: Rect,
95 pub quad: [[f32; 2]; 4],
97 pub image: ImageBitmap,
99 pub alpha: f32,
101 pub color_filter: Option<ColorFilter>,
103 pub sampling: ImageSampling,
105 pub clip: Option<Rect>,
107 pub src_rect: Option<Rect>,
109 pub blend_mode: BlendMode,
111 pub motion_context_animated: bool,
114}
115
116pub struct TextDrawParams {
123 pub rect: Rect,
126 pub text: Rc<AnnotatedString>,
128 pub layout: cranpose_text::TextLayout,
130 pub color: Color,
132 pub text_style: Arc<TextStyle>,
134 pub style_hash: u64,
136 pub font_size: f32,
138 pub scale: f32,
140 pub clip: Option<Rect>,
142}
143
144pub trait DrawPrimitiveSink {
146 fn push_shape(&mut self, params: ShapeDrawParams);
148
149 fn push_image(&mut self, params: ImageDrawParams);
151
152 fn push_shadow(
154 &mut self,
155 shadow_primitive: &ShadowPrimitive,
156 layer_bounds: Rect,
157 layer: &GraphicsLayer,
158 clip: Option<Rect>,
159 );
160
161 fn push_text(&mut self, params: TextDrawParams) {
165 let _ = params;
166 }
167}
168
169pub fn draw_shape_params_for_primitive(
171 primitive: &DrawPrimitive,
172 layer_bounds: Rect,
173 layer: &GraphicsLayer,
174 clip: Option<Rect>,
175 blend_mode: BlendMode,
176) -> Option<ShapeDrawParams> {
177 struct SingleShapeSink {
178 shape: Option<ShapeDrawParams>,
179 }
180
181 impl DrawPrimitiveSink for SingleShapeSink {
182 fn push_shape(&mut self, params: ShapeDrawParams) {
183 if self.shape.is_none() {
184 self.shape = Some(params);
185 }
186 }
187
188 fn push_image(&mut self, _params: ImageDrawParams) {}
189
190 fn push_shadow(
191 &mut self,
192 _shadow_primitive: &ShadowPrimitive,
193 _layer_bounds: Rect,
194 _layer: &GraphicsLayer,
195 _clip: Option<Rect>,
196 ) {
197 }
198 }
199
200 let mut sink = SingleShapeSink { shape: None };
201 emit_draw_primitive(
202 primitive,
203 layer_bounds,
204 layer,
205 clip,
206 &mut sink,
207 Some(blend_mode),
208 false,
209 );
210 sink.shape
211}
212
213pub fn resolve_clip(parent_clip: Option<Rect>, requested_clip: Option<Rect>) -> Option<Rect> {
218 match (parent_clip, requested_clip) {
219 (Some(parent), Some(current)) => Some(parent.intersect(current).unwrap_or(Rect::EMPTY)),
220 (Some(parent), None) => Some(parent),
221 (None, Some(current)) => Some(current),
222 (None, None) => None,
223 }
224}
225
226pub fn resolve_primitive_clip(
230 local_clip: Option<Rect>,
231 layer_bounds: Rect,
232 layer: &GraphicsLayer,
233 parent_clip: Option<Rect>,
234 clip_space: PrimitiveClipSpace,
235) -> Option<Rect> {
236 let Some(local_clip) = local_clip else {
237 return parent_clip;
238 };
239 let clip_rect = Rect {
240 x: layer_bounds.x + local_clip.x,
241 y: layer_bounds.y + local_clip.y,
242 width: local_clip.width,
243 height: local_clip.height,
244 };
245 let requested_clip = match clip_space {
246 PrimitiveClipSpace::Local => clip_rect,
247 PrimitiveClipSpace::LayerTransformed => apply_layer_to_rect(clip_rect, layer_bounds, layer),
248 };
249 resolve_clip(parent_clip, Some(requested_clip))
250}
251
252struct ShapePlacement<'a> {
255 layer_bounds: Rect,
256 layer: &'a GraphicsLayer,
257 clip: Option<Rect>,
258 blend_mode: BlendMode,
259 motion_context_animated: bool,
260}
261
262impl ShapePlacement<'_> {
263 fn fill(&self, draw_rect: Rect, brush: &Brush) -> ShapeDrawParams {
265 let quad = apply_layer_to_quad(draw_rect, self.layer_bounds, self.layer);
266 ShapeDrawParams {
267 rect: quad_bounds(quad),
268 local_rect: apply_layer_affine_to_rect(draw_rect, self.layer_bounds, self.layer),
269 quad,
270 brush: resolve_layer_brush(brush, self.layer),
271 shape: None,
272 stroke: None,
273 arc: None,
274 line: None,
275 trapezoid: None,
276 clip: self.clip,
277 blend_mode: self.blend_mode,
278 motion_context_animated: self.motion_context_animated,
279 }
280 }
281
282 fn translate(&self, rect: Rect) -> Rect {
283 rect.translate(self.layer_bounds.x, self.layer_bounds.y)
284 }
285
286 fn place(&self, point: Point) -> Point {
287 apply_layer_affine_to_point(
288 Point::new(point.x + self.layer_bounds.x, point.y + self.layer_bounds.y),
289 self.layer_bounds,
290 self.layer,
291 )
292 }
293}
294
295fn rect_shape_params(
296 local_rect: Rect,
297 brush: &Brush,
298 stroke: Option<Stroke>,
299 placement: &ShapePlacement,
300) -> Option<ShapeDrawParams> {
301 let (draw_rect, stroke) =
302 stroked_draw_rect(local_rect, stroke, placement.layer_bounds, placement.layer)?;
303 Some(ShapeDrawParams {
304 stroke,
305 ..placement.fill(draw_rect, brush)
306 })
307}
308
309fn round_rect_shape_params(
310 local_rect: Rect,
311 brush: &Brush,
312 radii: CornerRadii,
313 stroke: Option<Stroke>,
314 placement: &ShapePlacement,
315) -> Option<ShapeDrawParams> {
316 let (draw_rect, stroke) =
317 stroked_draw_rect(local_rect, stroke, placement.layer_bounds, placement.layer)?;
318 let radii = scale_corner_radii(radii, layer_uniform_scale(placement.layer));
319 Some(ShapeDrawParams {
320 shape: Some(RoundedCornerShape::with_radii(radii)),
321 stroke,
322 ..placement.fill(draw_rect, brush)
323 })
324}
325
326fn arc_shape_params(
328 local_rect: Rect,
329 brush: &Brush,
330 arc: ArcGeometry,
331 placement: &ShapePlacement,
332) -> Option<ShapeDrawParams> {
333 if arc.is_degenerate() {
334 return None;
335 }
336 let scale = layer_uniform_scale(placement.layer);
337 Some(ShapeDrawParams {
338 arc: Some(arc.scaled_about(placement.place(arc.center), scale)),
339 ..placement.fill(placement.translate(local_rect), brush)
340 })
341}
342
343fn line_shape_params(
346 brush: &Brush,
347 start: Point,
348 end: Point,
349 stroke: Stroke,
350 placement: &ShapePlacement,
351) -> Option<ShapeDrawParams> {
352 let line = LineGeometry::new(start, end, stroke);
353 if line.is_degenerate() {
354 return None;
355 }
356 let scale = layer_uniform_scale(placement.layer);
357 Some(ShapeDrawParams {
358 line: Some(line.placed(placement.place(start), placement.place(end), scale)),
359 ..placement.fill(placement.translate(line.bounds()), brush)
360 })
361}
362
363fn trapezoid_shape_params(
366 brush_rect: Rect,
367 brush: &Brush,
368 trapezoid: Trapezoid,
369 placement: &ShapePlacement,
370) -> Option<ShapeDrawParams> {
371 if trapezoid.right <= trapezoid.left {
372 return None;
373 }
374 let place = |x: f32, y: f32| placement.place(Point::new(x, y));
375 let [top_left, top_right, bottom_left, bottom_right] = [
376 place(trapezoid.left, trapezoid.top[0]),
377 place(trapezoid.right, trapezoid.top[1]),
378 place(trapezoid.left, trapezoid.bottom[0]),
379 place(trapezoid.right, trapezoid.bottom[1]),
380 ];
381 Some(ShapeDrawParams {
382 trapezoid: Some(Trapezoid {
383 left: top_left.x,
384 right: top_right.x,
385 top: [top_left.y, top_right.y],
386 bottom: [bottom_left.y, bottom_right.y],
387 ..trapezoid
388 }),
389 ..placement.fill(placement.translate(brush_rect), brush)
390 })
391}
392
393pub fn emit_draw_primitive<S: DrawPrimitiveSink>(
398 primitive: &DrawPrimitive,
399 layer_bounds: Rect,
400 layer: &GraphicsLayer,
401 clip: Option<Rect>,
402 sink: &mut S,
403 blend_mode: Option<BlendMode>,
404 motion_context_animated: bool,
405) {
406 let placement = ShapePlacement {
407 layer_bounds,
408 layer,
409 clip,
410 blend_mode: blend_mode.unwrap_or(BlendMode::SrcOver),
411 motion_context_animated,
412 };
413 match primitive {
414 DrawPrimitive::Content | DrawPrimitive::Group(_) => {}
415 DrawPrimitive::Blend {
416 primitive,
417 blend_mode: nested,
418 } => emit_draw_primitive(
419 primitive,
420 layer_bounds,
421 layer,
422 clip,
423 sink,
424 blend_mode.or(Some(*nested)),
425 motion_context_animated,
426 ),
427 DrawPrimitive::Rect {
428 rect,
429 brush,
430 stroke,
431 } => {
432 if let Some(params) = rect_shape_params(*rect, brush, *stroke, &placement) {
433 sink.push_shape(params);
434 }
435 }
436 DrawPrimitive::RoundRect {
437 rect,
438 brush,
439 radii,
440 stroke,
441 } => {
442 if let Some(params) = round_rect_shape_params(*rect, brush, *radii, *stroke, &placement)
443 {
444 sink.push_shape(params);
445 }
446 }
447 DrawPrimitive::Arc {
448 rect,
449 brush,
450 center,
451 radius,
452 start_angle,
453 sweep_angle,
454 stroke,
455 inner_radius,
456 } => {
457 let (band_inner, band_outer, cap) = arc_band(*radius, *inner_radius, *stroke);
458 let arc = ArcGeometry::new(
459 *center,
460 band_inner,
461 band_outer,
462 *start_angle,
463 *sweep_angle,
464 cap,
465 );
466 if let Some(params) = arc_shape_params(*rect, brush, arc, &placement) {
467 sink.push_shape(params);
468 }
469 }
470 DrawPrimitive::Line {
471 brush,
472 start,
473 end,
474 stroke,
475 ..
476 } => {
477 if let Some(params) = line_shape_params(brush, *start, *end, *stroke, &placement) {
478 sink.push_shape(params);
479 }
480 }
481 DrawPrimitive::Trapezoid {
482 rect,
483 brush,
484 trapezoid,
485 } => {
486 if let Some(params) = trapezoid_shape_params(*rect, brush, *trapezoid, &placement) {
487 sink.push_shape(params);
488 }
489 }
490 DrawPrimitive::Image {
491 rect: local_rect,
492 image,
493 alpha,
494 color_filter,
495 sampling,
496 src_rect,
497 } => {
498 let draw_rect = local_rect.translate(layer_bounds.x, layer_bounds.y);
499 let local_rect = apply_layer_affine_to_rect(draw_rect, layer_bounds, layer);
500 let quad = apply_layer_to_quad(draw_rect, layer_bounds, layer);
501 sink.push_image(ImageDrawParams {
502 rect: quad_bounds(quad),
503 local_rect,
504 quad,
505 image: image.clone(),
506 alpha: (alpha * layer.alpha).clamp(0.0, 1.0),
507 color_filter: compose_color_filters(
508 color_filter.as_ref(),
509 layer.color_filter.as_ref(),
510 ),
511 sampling: *sampling,
512 clip,
513 src_rect: *src_rect,
514 blend_mode: blend_mode.unwrap_or(BlendMode::SrcOver),
515 motion_context_animated,
516 });
517 }
518 DrawPrimitive::Text(text) => {
519 if let Some(params) = text_draw_params((**text).clone(), layer_bounds, layer, clip) {
520 sink.push_text(params);
521 }
522 }
523 DrawPrimitive::Shadow(shadow_primitive) => {
524 sink.push_shadow(shadow_primitive, layer_bounds, layer, clip);
525 }
526 }
527}
528
529fn text_draw_params(
530 text: TextPrimitive,
531 layer_bounds: Rect,
532 layer: &GraphicsLayer,
533 clip: Option<Rect>,
534) -> Option<TextDrawParams> {
535 if text.text.is_empty() {
536 return None;
537 }
538 let layout = cranpose_text::TextLayout::clone(
540 &*text
541 .layout
542 .as_ref()?
543 .downcast::<cranpose_text::TextLayout>()?,
544 );
545 let draw_rect = text.rect.translate(layer_bounds.x, layer_bounds.y);
546 let rect = apply_layer_to_rect(draw_rect, layer_bounds, layer);
547 if !(rect.width > 0.0 && rect.height > 0.0) {
548 return None;
549 }
550 let scale = layer_uniform_scale(layer);
551 if !scale.is_finite() || scale <= 0.0 {
552 return None;
553 }
554 let (mut text_style, mut style_hash) = drawn_text_style(&text.style);
555 let color = match text.brush {
558 Brush::Solid(color) => apply_layer_to_color(color, layer),
559 gradient => {
560 let gradient = apply_layer_to_brush(gradient, layer);
561 let first = gradient
562 .colors()
563 .first()
564 .copied()
565 .unwrap_or(Color::TRANSPARENT);
566 let style = Arc::make_mut(&mut text_style);
567 style.span_style.brush = Some(gradient);
568 style_hash = style.render_hash();
569 first
570 }
571 };
572 if !text_style
573 .span_style
574 .brush
575 .as_ref()
576 .map_or(color.3 > 0.0, Brush::paints)
577 {
578 return None;
579 }
580
581 Some(TextDrawParams {
582 rect,
583 text: cranpose_ui::text::shared_plain_annotated_string(text.text.as_ref()),
584 layout,
585 color,
586 text_style,
587 style_hash,
588 font_size: text.style.resolved_font_size(),
589 scale,
590 clip,
591 })
592}
593
594#[cfg(test)]
595#[path = "tests/primitive_emit_tests.rs"]
596mod tests;