1use crate::{Brush, ColorFilter, ImageBitmap};
4use std::ops::AddAssign;
5
6#[derive(Clone, Copy, Debug, PartialEq, Default)]
7pub struct Point {
8 pub x: f32,
9 pub y: f32,
10}
11
12impl Point {
13 pub const fn new(x: f32, y: f32) -> Self {
14 Self { x, y }
15 }
16
17 pub const ZERO: Point = Point { x: 0.0, y: 0.0 };
18}
19
20#[derive(Clone, Copy, Debug, PartialEq, Default)]
21pub struct Size {
22 pub width: f32,
23 pub height: f32,
24}
25
26impl Size {
27 pub const fn new(width: f32, height: f32) -> Self {
28 Self { width, height }
29 }
30
31 pub const ZERO: Size = Size {
32 width: 0.0,
33 height: 0.0,
34 };
35}
36
37#[derive(Clone, Copy, Debug, PartialEq)]
38pub struct Rect {
39 pub x: f32,
40 pub y: f32,
41 pub width: f32,
42 pub height: f32,
43}
44
45impl Rect {
46 pub fn from_origin_size(origin: Point, size: Size) -> Self {
47 Self {
48 x: origin.x,
49 y: origin.y,
50 width: size.width,
51 height: size.height,
52 }
53 }
54
55 pub fn from_size(size: Size) -> Self {
56 Self {
57 x: 0.0,
58 y: 0.0,
59 width: size.width,
60 height: size.height,
61 }
62 }
63
64 pub fn translate(&self, dx: f32, dy: f32) -> Self {
65 Self {
66 x: self.x + dx,
67 y: self.y + dy,
68 width: self.width,
69 height: self.height,
70 }
71 }
72
73 pub fn contains(&self, x: f32, y: f32) -> bool {
74 x >= self.x && y >= self.y && x <= self.x + self.width && y <= self.y + self.height
75 }
76}
77
78#[derive(Clone, Copy, Debug, Default, PartialEq)]
80pub struct EdgeInsets {
81 pub left: f32,
82 pub top: f32,
83 pub right: f32,
84 pub bottom: f32,
85}
86
87impl EdgeInsets {
88 pub fn uniform(all: f32) -> Self {
89 Self {
90 left: all,
91 top: all,
92 right: all,
93 bottom: all,
94 }
95 }
96
97 pub fn horizontal(horizontal: f32) -> Self {
98 Self {
99 left: horizontal,
100 right: horizontal,
101 ..Self::default()
102 }
103 }
104
105 pub fn vertical(vertical: f32) -> Self {
106 Self {
107 top: vertical,
108 bottom: vertical,
109 ..Self::default()
110 }
111 }
112
113 pub fn symmetric(horizontal: f32, vertical: f32) -> Self {
114 Self {
115 left: horizontal,
116 right: horizontal,
117 top: vertical,
118 bottom: vertical,
119 }
120 }
121
122 pub fn from_components(left: f32, top: f32, right: f32, bottom: f32) -> Self {
123 Self {
124 left,
125 top,
126 right,
127 bottom,
128 }
129 }
130
131 pub fn is_zero(&self) -> bool {
132 self.left == 0.0 && self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0
133 }
134
135 pub fn horizontal_sum(&self) -> f32 {
136 self.left + self.right
137 }
138
139 pub fn vertical_sum(&self) -> f32 {
140 self.top + self.bottom
141 }
142}
143
144impl AddAssign for EdgeInsets {
145 fn add_assign(&mut self, rhs: Self) {
146 self.left += rhs.left;
147 self.top += rhs.top;
148 self.right += rhs.right;
149 self.bottom += rhs.bottom;
150 }
151}
152
153#[derive(Clone, Copy, Debug, Default, PartialEq)]
154pub struct CornerRadii {
155 pub top_left: f32,
156 pub top_right: f32,
157 pub bottom_right: f32,
158 pub bottom_left: f32,
159}
160
161impl CornerRadii {
162 pub fn uniform(radius: f32) -> Self {
163 Self {
164 top_left: radius,
165 top_right: radius,
166 bottom_right: radius,
167 bottom_left: radius,
168 }
169 }
170}
171
172#[derive(Clone, Copy, Debug, PartialEq)]
173pub struct RoundedCornerShape {
174 radii: CornerRadii,
175}
176
177impl RoundedCornerShape {
178 pub fn new(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {
179 Self {
180 radii: CornerRadii {
181 top_left,
182 top_right,
183 bottom_right,
184 bottom_left,
185 },
186 }
187 }
188
189 pub fn uniform(radius: f32) -> Self {
190 Self {
191 radii: CornerRadii::uniform(radius),
192 }
193 }
194
195 pub fn with_radii(radii: CornerRadii) -> Self {
196 Self { radii }
197 }
198
199 pub fn resolve(&self, width: f32, height: f32) -> CornerRadii {
200 let mut resolved = self.radii;
201 let max_width = (width / 2.0).max(0.0);
202 let max_height = (height / 2.0).max(0.0);
203 resolved.top_left = resolved.top_left.clamp(0.0, max_width).min(max_height);
204 resolved.top_right = resolved.top_right.clamp(0.0, max_width).min(max_height);
205 resolved.bottom_right = resolved.bottom_right.clamp(0.0, max_width).min(max_height);
206 resolved.bottom_left = resolved.bottom_left.clamp(0.0, max_width).min(max_height);
207 resolved
208 }
209
210 pub fn radii(&self) -> CornerRadii {
211 self.radii
212 }
213}
214
215#[derive(Clone, Copy, Debug, PartialEq)]
216pub struct GraphicsLayer {
217 pub alpha: f32,
218 pub scale: f32,
219 pub translation_x: f32,
220 pub translation_y: f32,
221}
222
223impl Default for GraphicsLayer {
224 fn default() -> Self {
225 Self {
226 alpha: 1.0,
227 scale: 1.0,
228 translation_x: 0.0,
229 translation_y: 0.0,
230 }
231 }
232}
233
234#[derive(Clone, Debug, PartialEq)]
235pub enum DrawPrimitive {
236 Rect {
237 rect: Rect,
238 brush: Brush,
239 },
240 RoundRect {
241 rect: Rect,
242 brush: Brush,
243 radii: CornerRadii,
244 },
245 Image {
246 rect: Rect,
247 image: ImageBitmap,
248 alpha: f32,
249 color_filter: Option<ColorFilter>,
250 src_rect: Option<Rect>,
254 },
255}
256
257pub trait DrawScope {
258 fn size(&self) -> Size;
259 fn draw_content(&self);
260 fn draw_rect(&mut self, brush: Brush);
261 fn draw_rect_at(&mut self, rect: Rect, brush: Brush);
263 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii);
264 fn draw_image(&mut self, image: ImageBitmap);
265 fn draw_image_at(
266 &mut self,
267 rect: Rect,
268 image: ImageBitmap,
269 alpha: f32,
270 color_filter: Option<ColorFilter>,
271 );
272 fn draw_image_src(
275 &mut self,
276 image: ImageBitmap,
277 src_rect: Rect,
278 dst_rect: Rect,
279 alpha: f32,
280 color_filter: Option<ColorFilter>,
281 );
282 fn into_primitives(self) -> Vec<DrawPrimitive>;
283}
284
285#[derive(Default)]
286pub struct DrawScopeDefault {
287 size: Size,
288 primitives: Vec<DrawPrimitive>,
289}
290
291impl DrawScopeDefault {
292 pub fn new(size: Size) -> Self {
293 Self {
294 size,
295 primitives: Vec::new(),
296 }
297 }
298}
299
300impl DrawScope for DrawScopeDefault {
301 fn size(&self) -> Size {
302 self.size
303 }
304
305 fn draw_content(&self) {}
306
307 fn draw_rect(&mut self, brush: Brush) {
308 self.primitives.push(DrawPrimitive::Rect {
309 rect: Rect::from_size(self.size),
310 brush,
311 });
312 }
313
314 fn draw_rect_at(&mut self, rect: Rect, brush: Brush) {
315 self.primitives.push(DrawPrimitive::Rect { rect, brush });
316 }
317
318 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii) {
319 self.primitives.push(DrawPrimitive::RoundRect {
320 rect: Rect::from_size(self.size),
321 brush,
322 radii,
323 });
324 }
325
326 fn draw_image(&mut self, image: ImageBitmap) {
327 self.primitives.push(DrawPrimitive::Image {
328 rect: Rect::from_size(self.size),
329 image,
330 alpha: 1.0,
331 color_filter: None,
332 src_rect: None,
333 });
334 }
335
336 fn draw_image_at(
337 &mut self,
338 rect: Rect,
339 image: ImageBitmap,
340 alpha: f32,
341 color_filter: Option<ColorFilter>,
342 ) {
343 self.primitives.push(DrawPrimitive::Image {
344 rect,
345 image,
346 alpha: alpha.clamp(0.0, 1.0),
347 color_filter,
348 src_rect: None,
349 });
350 }
351
352 fn draw_image_src(
353 &mut self,
354 image: ImageBitmap,
355 src_rect: Rect,
356 dst_rect: Rect,
357 alpha: f32,
358 color_filter: Option<ColorFilter>,
359 ) {
360 self.primitives.push(DrawPrimitive::Image {
361 rect: dst_rect,
362 image,
363 alpha: alpha.clamp(0.0, 1.0),
364 color_filter,
365 src_rect: Some(src_rect),
366 });
367 }
368
369 fn into_primitives(self) -> Vec<DrawPrimitive> {
370 self.primitives
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use super::*;
377 use crate::{Color, ImageBitmap};
378
379 #[test]
380 fn draw_image_uses_scope_size_as_default_rect() {
381 let mut scope = DrawScopeDefault::new(Size::new(40.0, 24.0));
382 let image = ImageBitmap::from_rgba8(2, 2, vec![255; 16]).expect("image");
383 scope.draw_image(image.clone());
384 let primitives = scope.into_primitives();
385 assert_eq!(primitives.len(), 1);
386 match &primitives[0] {
387 DrawPrimitive::Image {
388 rect,
389 image: actual,
390 alpha,
391 color_filter,
392 src_rect,
393 } => {
394 assert_eq!(*rect, Rect::from_size(Size::new(40.0, 24.0)));
395 assert_eq!(*actual, image);
396 assert_eq!(*alpha, 1.0);
397 assert!(color_filter.is_none());
398 assert!(src_rect.is_none());
399 }
400 other => panic!("expected image primitive, got {other:?}"),
401 }
402 }
403
404 #[test]
405 fn draw_image_src_stores_src_rect() {
406 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
407 let image = ImageBitmap::from_rgba8(64, 64, vec![255; 64 * 64 * 4]).expect("image");
408 let src = Rect {
409 x: 10.0,
410 y: 20.0,
411 width: 30.0,
412 height: 40.0,
413 };
414 let dst = Rect {
415 x: 0.0,
416 y: 0.0,
417 width: 60.0,
418 height: 80.0,
419 };
420 scope.draw_image_src(image.clone(), src, dst, 0.8, None);
421 let primitives = scope.into_primitives();
422 assert_eq!(primitives.len(), 1);
423 match &primitives[0] {
424 DrawPrimitive::Image {
425 rect,
426 image: actual,
427 alpha,
428 src_rect,
429 ..
430 } => {
431 assert_eq!(*rect, dst);
432 assert_eq!(*actual, image);
433 assert!((alpha - 0.8).abs() < 1e-5);
434 assert_eq!(*src_rect, Some(src));
435 }
436 other => panic!("expected image primitive, got {other:?}"),
437 }
438 }
439
440 #[test]
441 fn draw_image_at_clamps_alpha() {
442 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
443 let image = ImageBitmap::from_rgba8(1, 1, vec![255, 255, 255, 255]).expect("image");
444 scope.draw_image_at(
445 Rect::from_origin_size(Point::new(2.0, 3.0), Size::new(5.0, 6.0)),
446 image,
447 3.0,
448 Some(ColorFilter::Tint(Color::from_rgba_u8(128, 128, 255, 255))),
449 );
450 match &scope.into_primitives()[0] {
451 DrawPrimitive::Image { alpha, .. } => assert_eq!(*alpha, 1.0),
452 other => panic!("expected image primitive, got {other:?}"),
453 }
454 }
455}