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