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