1use cranpose_ui_graphics::{
2 BlendMode, Brush, ColorFilter, DrawPrimitive, GraphicsLayer, ImageBitmap, Rect,
3 RoundedCornerShape, ShadowPrimitive,
4};
5
6use crate::graph::quad_bounds;
7use crate::layer_transform::{
8 apply_layer_affine_to_rect, apply_layer_to_quad, apply_layer_to_rect, layer_uniform_scale,
9};
10use crate::style_shared::{apply_layer_to_brush, compose_color_filters, scale_corner_radii};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum PrimitiveClipSpace {
14 Local,
15 LayerTransformed,
16}
17
18pub struct ShapeDrawParams {
19 pub rect: Rect,
20 pub local_rect: Rect,
21 pub quad: [[f32; 2]; 4],
22 pub brush: Brush,
23 pub shape: Option<RoundedCornerShape>,
24 pub clip: Option<Rect>,
25 pub blend_mode: BlendMode,
26}
27
28pub struct ImageDrawParams {
29 pub rect: Rect,
30 pub local_rect: Rect,
31 pub quad: [[f32; 2]; 4],
32 pub image: ImageBitmap,
33 pub alpha: f32,
34 pub color_filter: Option<ColorFilter>,
35 pub clip: Option<Rect>,
36 pub src_rect: Option<Rect>,
37 pub blend_mode: BlendMode,
38 pub motion_context_animated: bool,
39}
40
41pub trait DrawPrimitiveSink {
42 fn push_shape(&mut self, params: ShapeDrawParams);
43
44 fn push_image(&mut self, params: ImageDrawParams);
45
46 fn push_shadow(
47 &mut self,
48 shadow_primitive: ShadowPrimitive,
49 layer_bounds: Rect,
50 layer: &GraphicsLayer,
51 clip: Option<Rect>,
52 );
53}
54
55pub fn draw_shape_params_for_primitive(
56 primitive: DrawPrimitive,
57 layer_bounds: Rect,
58 layer: &GraphicsLayer,
59 clip: Option<Rect>,
60 blend_mode: BlendMode,
61) -> Option<ShapeDrawParams> {
62 struct SingleShapeSink {
63 shape: Option<ShapeDrawParams>,
64 }
65
66 impl DrawPrimitiveSink for SingleShapeSink {
67 fn push_shape(&mut self, params: ShapeDrawParams) {
68 if self.shape.is_none() {
69 self.shape = Some(params);
70 }
71 }
72
73 fn push_image(&mut self, _params: ImageDrawParams) {}
74
75 fn push_shadow(
76 &mut self,
77 _shadow_primitive: ShadowPrimitive,
78 _layer_bounds: Rect,
79 _layer: &GraphicsLayer,
80 _clip: Option<Rect>,
81 ) {
82 }
83 }
84
85 let mut sink = SingleShapeSink { shape: None };
86 emit_draw_primitive(
87 primitive,
88 layer_bounds,
89 layer,
90 clip,
91 &mut sink,
92 Some(blend_mode),
93 false,
94 );
95 sink.shape
96}
97
98pub fn resolve_clip(parent_clip: Option<Rect>, requested_clip: Option<Rect>) -> Option<Rect> {
99 match (parent_clip, requested_clip) {
100 (Some(parent), Some(current)) => parent.intersect(current),
101 (Some(parent), None) => Some(parent),
102 (None, Some(current)) => Some(current),
103 (None, None) => None,
104 }
105}
106
107pub fn resolve_primitive_clip(
108 local_clip: Option<Rect>,
109 layer_bounds: Rect,
110 layer: &GraphicsLayer,
111 parent_clip: Option<Rect>,
112 clip_space: PrimitiveClipSpace,
113) -> Option<Rect> {
114 let Some(local_clip) = local_clip else {
115 return parent_clip;
116 };
117 let clip_rect = Rect {
118 x: layer_bounds.x + local_clip.x,
119 y: layer_bounds.y + local_clip.y,
120 width: local_clip.width,
121 height: local_clip.height,
122 };
123 let requested_clip = match clip_space {
124 PrimitiveClipSpace::Local => clip_rect,
125 PrimitiveClipSpace::LayerTransformed => apply_layer_to_rect(clip_rect, layer_bounds, layer),
126 };
127 resolve_clip(parent_clip, Some(requested_clip))
128}
129
130pub fn emit_draw_primitive<S: DrawPrimitiveSink>(
131 primitive: DrawPrimitive,
132 layer_bounds: Rect,
133 layer: &GraphicsLayer,
134 clip: Option<Rect>,
135 sink: &mut S,
136 blend_mode: Option<BlendMode>,
137 motion_context_animated: bool,
138) {
139 match primitive {
140 DrawPrimitive::Content => {}
141 DrawPrimitive::Blend {
142 primitive,
143 blend_mode: nested,
144 } => emit_draw_primitive(
145 *primitive,
146 layer_bounds,
147 layer,
148 clip,
149 sink,
150 blend_mode.or(Some(nested)),
151 motion_context_animated,
152 ),
153 DrawPrimitive::Rect {
154 rect: local_rect,
155 brush,
156 } => {
157 let draw_rect = local_rect.translate(layer_bounds.x, layer_bounds.y);
158 let local_rect = apply_layer_affine_to_rect(draw_rect, layer_bounds, layer);
159 let quad = apply_layer_to_quad(draw_rect, layer_bounds, layer);
160 sink.push_shape(ShapeDrawParams {
161 rect: quad_bounds(quad),
162 local_rect,
163 quad,
164 brush: apply_layer_to_brush(brush, layer),
165 shape: None,
166 clip,
167 blend_mode: blend_mode.unwrap_or(BlendMode::SrcOver),
168 });
169 }
170 DrawPrimitive::RoundRect {
171 rect: local_rect,
172 brush,
173 radii,
174 } => {
175 let draw_rect = local_rect.translate(layer_bounds.x, layer_bounds.y);
176 let local_rect = apply_layer_affine_to_rect(draw_rect, layer_bounds, layer);
177 let quad = apply_layer_to_quad(draw_rect, layer_bounds, layer);
178 let shape = RoundedCornerShape::with_radii(scale_corner_radii(
179 radii,
180 layer_uniform_scale(layer),
181 ));
182 sink.push_shape(ShapeDrawParams {
183 rect: quad_bounds(quad),
184 local_rect,
185 quad,
186 brush: apply_layer_to_brush(brush, layer),
187 shape: Some(shape),
188 clip,
189 blend_mode: blend_mode.unwrap_or(BlendMode::SrcOver),
190 });
191 }
192 DrawPrimitive::Image {
193 rect: local_rect,
194 image,
195 alpha,
196 color_filter,
197 src_rect,
198 } => {
199 let draw_rect = local_rect.translate(layer_bounds.x, layer_bounds.y);
200 let local_rect = apply_layer_affine_to_rect(draw_rect, layer_bounds, layer);
201 let quad = apply_layer_to_quad(draw_rect, layer_bounds, layer);
202 sink.push_image(ImageDrawParams {
203 rect: quad_bounds(quad),
204 local_rect,
205 quad,
206 image,
207 alpha: (alpha * layer.alpha).clamp(0.0, 1.0),
208 color_filter: compose_color_filters(color_filter, layer.color_filter),
209 clip,
210 src_rect,
211 blend_mode: blend_mode.unwrap_or(BlendMode::SrcOver),
212 motion_context_animated,
213 });
214 }
215 DrawPrimitive::Shadow(shadow_primitive) => {
216 sink.push_shadow(shadow_primitive, layer_bounds, layer, clip);
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use cranpose_ui_graphics::{Brush, Color, CornerRadii};
225
226 #[test]
227 fn draw_shape_params_for_primitive_returns_transformed_rect_shape() {
228 let shape = draw_shape_params_for_primitive(
229 DrawPrimitive::Rect {
230 rect: Rect {
231 x: 2.0,
232 y: 3.0,
233 width: 8.0,
234 height: 5.0,
235 },
236 brush: Brush::solid(Color::WHITE),
237 },
238 Rect {
239 x: 10.0,
240 y: 20.0,
241 width: 40.0,
242 height: 30.0,
243 },
244 &GraphicsLayer::default(),
245 None,
246 BlendMode::SrcOver,
247 )
248 .expect("rect shape");
249
250 assert_eq!(
251 shape.rect,
252 Rect {
253 x: 12.0,
254 y: 23.0,
255 width: 8.0,
256 height: 5.0,
257 }
258 );
259 assert!(shape.shape.is_none());
260 }
261
262 #[test]
263 fn draw_shape_params_for_primitive_resolves_blended_round_rect() {
264 let shape = draw_shape_params_for_primitive(
265 DrawPrimitive::Blend {
266 primitive: Box::new(DrawPrimitive::RoundRect {
267 rect: Rect {
268 x: 1.0,
269 y: 1.0,
270 width: 10.0,
271 height: 6.0,
272 },
273 brush: Brush::solid(Color::BLACK),
274 radii: CornerRadii::uniform(4.0),
275 }),
276 blend_mode: BlendMode::DstOut,
277 },
278 Rect::from_size(cranpose_ui_graphics::Size {
279 width: 20.0,
280 height: 20.0,
281 }),
282 &GraphicsLayer::default(),
283 None,
284 BlendMode::SrcOver,
285 )
286 .expect("round rect shape");
287
288 assert_eq!(shape.blend_mode, BlendMode::SrcOver);
289 assert!(shape.shape.is_some());
290 }
291
292 #[test]
293 fn draw_shape_params_for_primitive_rejects_non_shape_primitives() {
294 assert!(draw_shape_params_for_primitive(
295 DrawPrimitive::Image {
296 rect: Rect::from_size(cranpose_ui_graphics::Size {
297 width: 4.0,
298 height: 4.0,
299 }),
300 image: cranpose_ui_graphics::ImageBitmap::from_rgba8(
301 1,
302 1,
303 vec![255, 255, 255, 255],
304 )
305 .expect("image"),
306 alpha: 1.0,
307 color_filter: None,
308 src_rect: None,
309 },
310 Rect::from_size(cranpose_ui_graphics::Size {
311 width: 10.0,
312 height: 10.0,
313 }),
314 &GraphicsLayer::default(),
315 None,
316 BlendMode::SrcOver,
317 )
318 .is_none());
319 }
320}