1use super::*;
2
3#[derive(Clone)]
4pub struct Rect {
5 pub(super) layer_options: LayerOptions,
6 pub(super) width: Length,
7 pub(super) height: Length,
8 pub(super) min_width: u32,
9 pub(super) min_height: u32,
10 pub(super) max_width: Option<u32>,
11 pub(super) max_height: Option<u32>,
12 pub(super) fill: u32,
13 pub(super) direction: Direction,
14 pub(super) align: Align,
15 pub(super) justify: Align,
16 pub(super) overflow: Overflow,
17 pub(super) position: Position,
18 pub(super) inset: Inset,
19 pub(super) padding: Spacing,
20 pub(super) gap: u32,
21 pub(super) style: Style,
22 pub(super) content: Option<Content>,
23 pub(super) children: Vec<Rect>,
24}
25
26pub type Ui = Rect;
27
28#[derive(Clone, Copy, Debug)]
29struct VisualState {
30 scale: f32,
31 translate_x: f32,
32 translate_y: f32,
33}
34
35impl VisualState {
36 const IDENTITY: Self = Self {
37 scale: 1.0,
38 translate_x: 0.0,
39 translate_y: 0.0,
40 };
41
42 fn then_element(self, transform: PaintTransform, origin: Bounds) -> Self {
43 let scale = sanitize_scale(transform.scale);
44 let origin_x = origin.x as f32;
45 let origin_y = origin.y as f32;
46 let local_translate_x = origin_x + transform.translate_x as f32 - origin_x * scale;
47 let local_translate_y = origin_y + transform.translate_y as f32 - origin_y * scale;
48
49 Self {
50 scale: self.scale * scale,
51 translate_x: self.translate_x + self.scale * local_translate_x,
52 translate_y: self.translate_y + self.scale * local_translate_y,
53 }
54 }
55
56 fn bounds(self, bounds: Bounds) -> Option<Bounds> {
57 if self.scale <= 0.0 {
58 return None;
59 }
60
61 let left = (bounds.x as f32 * self.scale + self.translate_x).floor();
62 let top = (bounds.y as f32 * self.scale + self.translate_y).floor();
63 let right = (bounds.right() as f32 * self.scale + self.translate_x).ceil();
64 let bottom = (bounds.bottom() as f32 * self.scale + self.translate_y).ceil();
65 signed_bounds(left, top, right, bottom)
66 }
67
68 fn radii(self, radii: CornerRadius) -> CornerRadius {
69 scale_corner_radius(radii, self.scale)
70 }
71
72 fn border_widths(self, widths: BorderWidth) -> BorderWidth {
73 scale_border_widths(widths, self.scale)
74 }
75}
76
77impl Default for Rect {
78 fn default() -> Self {
79 Self {
80 layer_options: LayerOptions::default(),
81 width: Length::Fit,
82 height: Length::Fit,
83 min_width: 0,
84 min_height: 0,
85 max_width: None,
86 max_height: None,
87 fill: 1,
88 direction: Direction::Row,
89 align: Align::Start,
90 justify: Align::Start,
91 overflow: Overflow::Clip,
92 position: Position::Flow,
93 inset: Inset::ZERO,
94 padding: Spacing::ZERO,
95 gap: 0,
96 style: Style::default(),
97 content: None,
98 children: Vec::new(),
99 }
100 }
101}
102
103impl Rect {
104 pub fn new(layout: RectLayout) -> Self {
105 Self::default().with_layout(layout)
106 }
107
108 pub fn empty() -> Self {
109 Self::default()
110 }
111
112 pub fn layout(layout: RectLayout) -> Self {
113 Self::new(layout)
114 }
115
116 pub fn styled(style: RectStyle) -> Self {
117 Self::empty().style(style)
118 }
119
120 pub fn with_layout(mut self, layout: RectLayout) -> Self {
121 if let Some(surface) = layout.surface {
122 self.layer_options = surface.into();
123 }
124 self.width = layout.width;
125 self.height = layout.height;
126 self.min_width = layout.min_width;
127 self.min_height = layout.min_height;
128 self.max_width = layout.max_width;
129 self.max_height = layout.max_height;
130 self.fill = layout.fill;
131 self.direction = layout.direction;
132 self.align = layout.align;
133 self.justify = layout.justify;
134 self.overflow = layout.overflow;
135 self.position = layout.position;
136 self.inset = layout.inset;
137 self.padding = layout.padding;
138 self.gap = layout.gap;
139 self.style = layout.style;
140 if !layout.transform.is_identity() {
141 self.style.transform = layout.transform;
142 }
143 self.content = layout.content;
144 self
145 }
146
147 pub fn style(mut self, style: RectStyle) -> Self {
148 self.style = style;
149 self
150 }
151
152 pub fn width(mut self, width: Length) -> Self {
153 self.width = width;
154 self
155 }
156
157 pub fn height(mut self, height: Length) -> Self {
158 self.height = height;
159 self
160 }
161
162 pub fn size(mut self, width: Length, height: Length) -> Self {
163 self.width = width;
164 self.height = height;
165 self
166 }
167
168 pub fn min_width(mut self, min_width: u32) -> Self {
169 self.min_width = min_width;
170 self
171 }
172
173 pub fn min_height(mut self, min_height: u32) -> Self {
174 self.min_height = min_height;
175 self
176 }
177
178 pub fn max_width(mut self, max_width: impl Into<Option<u32>>) -> Self {
179 self.max_width = max_width.into();
180 self
181 }
182
183 pub fn max_height(mut self, max_height: impl Into<Option<u32>>) -> Self {
184 self.max_height = max_height.into();
185 self
186 }
187
188 pub fn fill(mut self, fill: u32) -> Self {
189 self.fill = fill;
190 self
191 }
192
193 pub fn direction(mut self, direction: Direction) -> Self {
194 self.direction = direction;
195 self
196 }
197
198 pub fn align(mut self, align: Align) -> Self {
199 self.align = align;
200 self
201 }
202
203 pub fn justify(mut self, justify: Align) -> Self {
204 self.justify = justify;
205 self
206 }
207
208 pub fn overflow(mut self, overflow: Overflow) -> Self {
209 self.overflow = overflow;
210 self
211 }
212
213 pub fn position(mut self, position: Position) -> Self {
214 self.position = position;
215 self
216 }
217
218 pub fn inset(mut self, inset: Inset) -> Self {
219 self.inset = inset;
220 self
221 }
222
223 pub fn absolute(mut self, inset: Inset) -> Self {
224 self.position = Position::Absolute;
225 self.inset = inset;
226 self
227 }
228
229 pub fn padding(mut self, padding: Spacing) -> Self {
230 self.padding = padding;
231 self
232 }
233
234 pub fn gap(mut self, gap: u32) -> Self {
235 self.gap = gap;
236 self
237 }
238
239 pub fn background(mut self, background: impl Into<Paint>) -> Self {
240 self.style.background = Some(background.into());
241 self
242 }
243
244 pub fn border(mut self, border: Border) -> Self {
245 self.style.border = Some(border);
246 self
247 }
248
249 pub fn corner_radius(mut self, radius: u32) -> Self {
250 self.style.corner_radius = radius;
251 self
252 }
253
254 pub fn corner_radii(mut self, radii: CornerRadius) -> Self {
255 self.style.corner_radii = radii;
256 self
257 }
258
259 pub fn gradient(mut self, gradient: GradientDirection) -> Self {
260 self.style.gradient = gradient;
261 self
262 }
263
264 pub fn opacity(mut self, opacity: f32) -> Self {
265 self.style.opacity = opacity;
266 self
267 }
268
269 pub fn anti_alias(mut self, anti_alias: AntiAlias) -> Self {
270 self.style.anti_alias = anti_alias;
271 self
272 }
273
274 pub fn transform(mut self, transform: PaintTransform) -> Self {
275 self.style.transform = transform;
276 self
277 }
278
279 pub fn translate(mut self, x: i32, y: i32) -> Self {
280 self.style.transform.translate_x = x;
281 self.style.transform.translate_y = y;
282 self
283 }
284
285 pub fn scale(mut self, scale: f32) -> Self {
286 self.style.transform.scale = scale;
287 self
288 }
289
290 pub fn content(mut self, content: impl Into<Content>) -> Self {
291 self.content = Some(content.into());
292 self
293 }
294
295 pub fn with_surface(mut self, surface: Surface) -> Self {
296 self.layer_options = surface.into();
297 self
298 }
299
300 pub fn begin(_: Bounds) -> Self {
301 Self::default()
302 }
303
304 pub fn build(bounds: Bounds, content: impl FnOnce(&mut Self)) -> Self {
305 let mut ui = Self::begin(bounds);
306 content(&mut ui);
307 ui
308 }
309
310 pub fn child(mut self, child: Rect) -> Self {
311 self.children.push(child);
312 self
313 }
314
315 pub fn children(mut self, children: impl IntoIterator<Item = Rect>) -> Self {
316 self.children.extend(children);
317 self
318 }
319
320 pub fn draw(self) -> wayland::Result<()> {
321 let options = self.layer_options.clone();
322 options.show(UiRenderer {
323 root: self,
324 fonts: LazyFontCtx::new(),
325 })
326 }
327
328 pub fn draw_with_commands(self, receiver: wayland::RenderReceiver) -> wayland::Result<()> {
329 let options = self.layer_options.clone();
330 options.show_with_commands(
331 UiRenderer {
332 root: self,
333 fonts: LazyFontCtx::new(),
334 },
335 receiver,
336 )
337 }
338
339 pub fn commands(&self, bounds: Bounds) -> Vec<DrawCommand> {
340 let mut fonts = FontCtx::new();
341 self.commands_with_fonts(bounds, &mut fonts)
342 }
343
344 pub fn commands_with_fonts(&self, bounds: Bounds, fonts: &mut FontCtx) -> Vec<DrawCommand> {
345 let mut commands = Vec::new();
346 Self::visit_layout(
347 self,
348 bounds,
349 Clip::rect(bounds),
350 VisualState::IDENTITY,
351 1.0,
352 None,
353 fonts,
354 &mut |command, _| {
355 commands.push(command.to_owned());
356 },
357 );
358 fonts.trim_scratch();
359 commands
360 }
361
362 pub fn visual_bounds(&self, bounds: Bounds) -> Option<Bounds> {
363 let mut fonts = FontCtx::new();
364 self.visual_bounds_with_fonts(bounds, &mut fonts)
365 }
366
367 pub fn visual_bounds_with_fonts(&self, bounds: Bounds, fonts: &mut FontCtx) -> Option<Bounds> {
368 let mut visual_bounds: Option<Bounds> = None;
369 Self::visit_layout(
370 self,
371 bounds,
372 Clip::rect(bounds),
373 VisualState::IDENTITY,
374 1.0,
375 None,
376 fonts,
377 &mut |command, _| {
378 let bounds = command.rect();
379 visual_bounds = Some(match visual_bounds {
380 Some(current) => current.union(bounds),
381 None => bounds,
382 });
383 },
384 );
385 fonts.trim_scratch();
386 visual_bounds
387 }
388
389 pub fn measure(&self, available_width: u32, available_height: u32) -> MeasuredSize {
390 let mut fonts = FontCtx::new();
391 self.measure_with_fonts(available_width, available_height, &mut fonts)
392 }
393
394 pub fn measure_with_fonts(
395 &self,
396 available_width: u32,
397 available_height: u32,
398 fonts: &mut FontCtx,
399 ) -> MeasuredSize {
400 let measured = measure_element(self, fonts, available_width, available_height);
401 fonts.trim_scratch();
402 measured.into()
403 }
404
405 pub fn hit_test(&self, bounds: Bounds, x: f64, y: f64) -> Option<Hit> {
406 let mut fonts = FontCtx::new();
407 self.hit_test_with_fonts(bounds, x, y, &mut fonts)
408 }
409
410 pub fn hit_test_with_fonts(
411 &self,
412 bounds: Bounds,
413 x: f64,
414 y: f64,
415 fonts: &mut FontCtx,
416 ) -> Option<Hit> {
417 let mut path = Vec::new();
418 self.hit_test_path_with_fonts(bounds, x, y, fonts, &mut path)
419 .map(|bounds| Hit { path, bounds })
420 }
421
422 pub fn hit_test_path(
423 &self,
424 bounds: Bounds,
425 x: f64,
426 y: f64,
427 path: &mut Vec<usize>,
428 ) -> Option<Bounds> {
429 let mut fonts = FontCtx::new();
430 self.hit_test_path_with_fonts(bounds, x, y, &mut fonts, path)
431 }
432
433 pub fn hit_test_path_with_fonts(
434 &self,
435 bounds: Bounds,
436 x: f64,
437 y: f64,
438 fonts: &mut FontCtx,
439 path: &mut Vec<usize>,
440 ) -> Option<Bounds> {
441 path.clear();
442 let Some((x, y)) = hit_point(x, y) else {
443 fonts.trim_scratch();
444 return None;
445 };
446
447 let mut current_path = Vec::new();
448 let bounds = Self::hit_test_layout(
449 self,
450 bounds,
451 Clip::rect(bounds),
452 VisualState::IDENTITY,
453 None,
454 fonts,
455 x,
456 y,
457 &mut current_path,
458 path,
459 );
460 fonts.trim_scratch();
461 bounds
462 }
463
464 pub fn paint(&mut self, canvas: &mut Canvas<'_>) {
465 let mut fonts = FontCtx::new();
466 self.paint_with_fonts(canvas, &mut fonts);
467 }
468
469 pub fn paint_with_fonts(&mut self, canvas: &mut Canvas<'_>, fonts: &mut FontCtx) {
470 self.paint_scaled_with_fonts(canvas, fonts, canvas.width(), canvas.height(), 1);
471 }
472
473 #[allow(clippy::too_many_arguments)]
474 pub fn paint_bgra_with_fonts(
475 &mut self,
476 pixels: &mut [u8],
477 buffer_width: u32,
478 buffer_height: u32,
479 stride: u32,
480 logical_width: u32,
481 logical_height: u32,
482 scale: u32,
483 fonts: &mut FontCtx,
484 ) -> Option<DamageRect> {
485 self.paint_bgra_viewport_with_fonts(
486 pixels,
487 buffer_width,
488 buffer_height,
489 stride,
490 logical_width,
491 logical_height,
492 0,
493 0,
494 scale,
495 fonts,
496 )
497 }
498
499 #[allow(clippy::too_many_arguments)]
500 pub fn paint_bgra_viewport_with_fonts(
501 &mut self,
502 pixels: &mut [u8],
503 buffer_width: u32,
504 buffer_height: u32,
505 stride: u32,
506 logical_width: u32,
507 logical_height: u32,
508 viewport_x: i32,
509 viewport_y: i32,
510 scale: u32,
511 fonts: &mut FontCtx,
512 ) -> Option<DamageRect> {
513 let mut canvas =
514 Canvas::from_bgra_pixels(pixels, buffer_width, buffer_height, stride, scale)?;
515 self.paint_viewport_with_fonts(
516 &mut canvas,
517 fonts,
518 logical_width,
519 logical_height,
520 viewport_x,
521 viewport_y,
522 scale,
523 );
524 canvas.damage()
525 }
526
527 #[allow(clippy::too_many_arguments)]
528 pub fn paint_bgra_transformed_with_fonts(
529 &mut self,
530 pixels: &mut [u8],
531 buffer_width: u32,
532 buffer_height: u32,
533 stride: u32,
534 logical_width: u32,
535 logical_height: u32,
536 scale: u32,
537 transform: PaintTransform,
538 fonts: &mut FontCtx,
539 ) -> Option<DamageRect> {
540 self.paint_bgra_transformed_viewport_with_fonts(
541 pixels,
542 buffer_width,
543 buffer_height,
544 stride,
545 logical_width,
546 logical_height,
547 0,
548 0,
549 scale,
550 transform,
551 fonts,
552 )
553 }
554
555 #[allow(clippy::too_many_arguments)]
556 pub fn paint_bgra_transformed_viewport_with_fonts(
557 &mut self,
558 pixels: &mut [u8],
559 buffer_width: u32,
560 buffer_height: u32,
561 stride: u32,
562 logical_width: u32,
563 logical_height: u32,
564 viewport_x: i32,
565 viewport_y: i32,
566 scale: u32,
567 transform: PaintTransform,
568 fonts: &mut FontCtx,
569 ) -> Option<DamageRect> {
570 let mut canvas =
571 Canvas::from_bgra_pixels(pixels, buffer_width, buffer_height, stride, scale)?;
572 self.paint_transformed_viewport_with_fonts(
573 &mut canvas,
574 fonts,
575 logical_width,
576 logical_height,
577 viewport_x,
578 viewport_y,
579 scale,
580 transform,
581 );
582 canvas.damage()
583 }
584
585 #[allow(clippy::too_many_arguments)]
586 pub fn paint_bgra(
587 &mut self,
588 pixels: &mut [u8],
589 buffer_width: u32,
590 buffer_height: u32,
591 stride: u32,
592 logical_width: u32,
593 logical_height: u32,
594 scale: u32,
595 ) -> Option<DamageRect> {
596 let mut fonts = FontCtx::new();
597 self.paint_bgra_with_fonts(
598 pixels,
599 buffer_width,
600 buffer_height,
601 stride,
602 logical_width,
603 logical_height,
604 scale,
605 &mut fonts,
606 )
607 }
608
609 #[allow(clippy::too_many_arguments)]
610 pub fn paint_bgra_transformed(
611 &mut self,
612 pixels: &mut [u8],
613 buffer_width: u32,
614 buffer_height: u32,
615 stride: u32,
616 logical_width: u32,
617 logical_height: u32,
618 scale: u32,
619 transform: PaintTransform,
620 ) -> Option<DamageRect> {
621 let mut fonts = FontCtx::new();
622 self.paint_bgra_transformed_with_fonts(
623 pixels,
624 buffer_width,
625 buffer_height,
626 stride,
627 logical_width,
628 logical_height,
629 scale,
630 transform,
631 &mut fonts,
632 )
633 }
634
635 #[allow(clippy::too_many_arguments)]
636 pub fn paint_bgra_transformed_viewport(
637 &mut self,
638 pixels: &mut [u8],
639 buffer_width: u32,
640 buffer_height: u32,
641 stride: u32,
642 logical_width: u32,
643 logical_height: u32,
644 viewport_x: i32,
645 viewport_y: i32,
646 scale: u32,
647 transform: PaintTransform,
648 ) -> Option<DamageRect> {
649 let mut fonts = FontCtx::new();
650 self.paint_bgra_transformed_viewport_with_fonts(
651 pixels,
652 buffer_width,
653 buffer_height,
654 stride,
655 logical_width,
656 logical_height,
657 viewport_x,
658 viewport_y,
659 scale,
660 transform,
661 &mut fonts,
662 )
663 }
664
665 #[allow(clippy::too_many_arguments)]
666 pub fn paint_bgra_viewport(
667 &mut self,
668 pixels: &mut [u8],
669 buffer_width: u32,
670 buffer_height: u32,
671 stride: u32,
672 logical_width: u32,
673 logical_height: u32,
674 viewport_x: i32,
675 viewport_y: i32,
676 scale: u32,
677 ) -> Option<DamageRect> {
678 let mut fonts = FontCtx::new();
679 self.paint_bgra_viewport_with_fonts(
680 pixels,
681 buffer_width,
682 buffer_height,
683 stride,
684 logical_width,
685 logical_height,
686 viewport_x,
687 viewport_y,
688 scale,
689 &mut fonts,
690 )
691 }
692
693 pub fn paint_scaled_with_fonts(
694 &mut self,
695 canvas: &mut Canvas<'_>,
696 fonts: &mut FontCtx,
697 width: u32,
698 height: u32,
699 scale: u32,
700 ) {
701 self.paint_viewport_with_fonts(canvas, fonts, width, height, 0, 0, scale);
702 }
703
704 #[allow(clippy::too_many_arguments)]
705 pub fn paint_transformed_with_fonts(
706 &mut self,
707 canvas: &mut Canvas<'_>,
708 fonts: &mut FontCtx,
709 width: u32,
710 height: u32,
711 scale: u32,
712 transform: PaintTransform,
713 ) {
714 self.paint_transformed_viewport_with_fonts(
715 canvas, fonts, width, height, 0, 0, scale, transform,
716 );
717 }
718
719 #[allow(clippy::too_many_arguments)]
720 pub fn paint_viewport_with_fonts(
721 &mut self,
722 canvas: &mut Canvas<'_>,
723 fonts: &mut FontCtx,
724 width: u32,
725 height: u32,
726 viewport_x: i32,
727 viewport_y: i32,
728 scale: u32,
729 ) {
730 let scale = scale.max(1);
731 let offset = PaintOffset {
732 x: scale_i32(viewport_x, scale),
733 y: scale_i32(viewport_y, scale),
734 };
735 let bounds = Bounds::new(0, 0, width, height);
736 Self::visit_layout(
737 self,
738 bounds,
739 Clip::rect(bounds),
740 VisualState::IDENTITY,
741 1.0,
742 None,
743 fonts,
744 &mut |command, fonts| {
745 paint_scaled_command_with_offset(canvas, fonts, command, scale, offset);
746 },
747 );
748 fonts.trim_scratch();
749 }
750
751 #[allow(clippy::too_many_arguments)]
752 pub fn paint_transformed_viewport_with_fonts(
753 &mut self,
754 canvas: &mut Canvas<'_>,
755 fonts: &mut FontCtx,
756 width: u32,
757 height: u32,
758 viewport_x: i32,
759 viewport_y: i32,
760 scale: u32,
761 transform: PaintTransform,
762 ) {
763 let scale = scale.max(1);
764 let visual_scale = if transform.scale.is_finite() {
765 transform.scale.max(0.0)
766 } else {
767 0.0
768 };
769 if visual_scale <= 0.0 {
770 fonts.trim_scratch();
771 return;
772 }
773 if transform.is_identity() {
774 self.paint_viewport_with_fonts(
775 canvas, fonts, width, height, viewport_x, viewport_y, scale,
776 );
777 return;
778 }
779
780 let total_scale = scale as f32 * visual_scale;
781 let offset = PaintOffset {
782 x: scale_i32_f32(viewport_x, total_scale).saturating_sub(transform.translate_x),
783 y: scale_i32_f32(viewport_y, total_scale).saturating_sub(transform.translate_y),
784 };
785 let bounds = Bounds::new(0, 0, width, height);
786 Self::visit_layout(
787 self,
788 bounds,
789 Clip::rect(bounds),
790 VisualState::IDENTITY,
791 1.0,
792 None,
793 fonts,
794 &mut |command, fonts| {
795 paint_scaled_f32_command_with_offset(canvas, fonts, command, total_scale, offset);
796 },
797 );
798 fonts.trim_scratch();
799 }
800
801 pub fn render(&mut self, canvas: &mut Canvas<'_>) {
802 self.paint(canvas);
803 }
804
805 fn visit_layout(
806 element: &Rect,
807 bounds: Bounds,
808 clip: Clip,
809 state: VisualState,
810 opacity: f32,
811 premeasured: Option<Size>,
812 fonts: &mut FontCtx,
813 visit: &mut dyn FnMut(PaintCommand<'_>, &mut FontCtx),
814 ) {
815 let measured = if element_needs_measure(element) {
816 premeasured
817 .unwrap_or_else(|| measure_element(element, fonts, bounds.width, bounds.height))
818 } else {
819 Size::default()
820 };
821 let width = resolve_length(
822 element.width,
823 bounds.width,
824 measured.width,
825 element.min_width,
826 element.max_width,
827 );
828 let height = resolve_length(
829 element.height,
830 bounds.height,
831 measured.height,
832 element.min_height,
833 element.max_height,
834 );
835 let rect = Bounds {
836 x: bounds.x,
837 y: bounds.y,
838 width,
839 height,
840 };
841 let state = state.then_element(element.style.transform, rect);
842 let visual_rect = state.bounds(rect);
843 let own_clip = visual_rect.and_then(|rect| clip.intersect_bounds(rect));
844 let opacity = multiply_opacity(opacity, element.style.opacity);
845 let radii = state.radii(element_corner_radii(element));
846
847 if let (Some(rect), Some(clip), Some(paint)) =
848 (visual_rect, own_clip, element.style.background.as_ref())
849 {
850 visit(
851 PaintCommand::Rect {
852 rect,
853 clip,
854 opacity,
855 paint,
856 gradient: element.style.gradient,
857 radii,
858 anti_alias: element.style.anti_alias,
859 },
860 fonts,
861 );
862 }
863 if let (Some(rect), Some(clip), Some(border)) =
864 (visual_rect, own_clip, element.style.border.as_ref())
865 {
866 visit(
867 PaintCommand::Border {
868 rect,
869 clip,
870 opacity,
871 paint: &border.color,
872 gradient: border.gradient,
873 widths: state.border_widths(border_widths(border)),
874 radii,
875 anti_alias: element.style.anti_alias,
876 },
877 fonts,
878 );
879 }
880 let content_rect = rect.inset(element.padding);
881 let visual_content_rect = state.bounds(content_rect);
882 if let (Some(rect), Some(clip), Some(content)) =
883 (visual_content_rect, own_clip, element.content.as_ref())
884 {
885 match content {
886 Content::Text(text) => visit(
887 PaintCommand::Text {
888 rect,
889 clip,
890 opacity,
891 scale: state.scale,
892 text,
893 },
894 fonts,
895 ),
896 Content::RichText(text) => visit(
897 PaintCommand::RichText {
898 rect,
899 clip,
900 opacity,
901 scale: state.scale,
902 text,
903 },
904 fonts,
905 ),
906 Content::Image(image) => visit(
907 PaintCommand::Image {
908 rect,
909 clip,
910 opacity,
911 image,
912 },
913 fonts,
914 ),
915 }
916 }
917
918 if element.children.is_empty() {
919 return;
920 }
921
922 let child_clip = match element.overflow {
923 Overflow::Clip => {
924 let (Some(clip), Some(content_rect)) = (own_clip, visual_content_rect) else {
925 return;
926 };
927 let Some(child_clip) =
928 clip.with_rounded_rect(content_rect, state.radii(content_clip_radii(element)))
929 else {
930 return;
931 };
932 child_clip
933 }
934 Overflow::Visible => clip,
935 };
936 layout_children(
937 element,
938 content_rect,
939 fonts,
940 |_, child, rect, measured, fonts| {
941 Self::visit_layout(
942 child,
943 rect,
944 child_clip,
945 state,
946 opacity,
947 Some(measured),
948 fonts,
949 visit,
950 );
951 },
952 );
953 }
954
955 #[allow(clippy::too_many_arguments)]
956 fn hit_test_layout(
957 element: &Rect,
958 bounds: Bounds,
959 clip: Clip,
960 state: VisualState,
961 premeasured: Option<Size>,
962 fonts: &mut FontCtx,
963 x: u32,
964 y: u32,
965 current_path: &mut Vec<usize>,
966 hit_path: &mut Vec<usize>,
967 ) -> Option<Bounds> {
968 let measured = if element_needs_measure(element) {
969 premeasured
970 .unwrap_or_else(|| measure_element(element, fonts, bounds.width, bounds.height))
971 } else {
972 Size::default()
973 };
974 let width = resolve_length(
975 element.width,
976 bounds.width,
977 measured.width,
978 element.min_width,
979 element.max_width,
980 );
981 let height = resolve_length(
982 element.height,
983 bounds.height,
984 measured.height,
985 element.min_height,
986 element.max_height,
987 );
988 let rect = Bounds {
989 x: bounds.x,
990 y: bounds.y,
991 width,
992 height,
993 };
994 let state = state.then_element(element.style.transform, rect);
995 let visual_rect = state.bounds(rect)?;
996 let clip = clip.intersect_bounds(visual_rect)?;
997 let content_rect = rect.inset(element.padding);
998 let visual_content_rect = state.bounds(content_rect)?;
999 let child_clip = match element.overflow {
1000 Overflow::Clip => clip.with_rounded_rect(
1001 visual_content_rect,
1002 state.radii(content_clip_radii(element)),
1003 )?,
1004 Overflow::Visible => clip,
1005 };
1006
1007 let mut hit = None;
1008 layout_children(
1009 element,
1010 content_rect,
1011 fonts,
1012 |index, child, rect, measured, fonts| {
1013 current_path.push(index);
1014 if let Some(bounds) = Self::hit_test_layout(
1015 child,
1016 rect,
1017 child_clip,
1018 state,
1019 Some(measured),
1020 fonts,
1021 x,
1022 y,
1023 current_path,
1024 hit_path,
1025 ) {
1026 hit = Some(bounds);
1027 }
1028 current_path.pop();
1029 },
1030 );
1031
1032 if let Some(bounds) = hit {
1033 return Some(bounds);
1034 }
1035
1036 let hit_clip =
1037 clip.with_rounded_rect(visual_rect, state.radii(element_corner_radii(element)))?;
1038 if hit_clip.contains(x, y) {
1039 hit_path.clear();
1040 hit_path.extend_from_slice(current_path);
1041 Some(visual_rect)
1042 } else {
1043 None
1044 }
1045 }
1046}
1047
1048fn signed_bounds(left: f32, top: f32, right: f32, bottom: f32) -> Option<Bounds> {
1049 if !left.is_finite()
1050 || !top.is_finite()
1051 || !right.is_finite()
1052 || !bottom.is_finite()
1053 || right <= left
1054 || bottom <= top
1055 {
1056 return None;
1057 }
1058
1059 let left = left.max(0.0).min(u32::MAX as f32) as u32;
1060 let top = top.max(0.0).min(u32::MAX as f32) as u32;
1061 let right = right.max(0.0).min(u32::MAX as f32) as u32;
1062 let bottom = bottom.max(0.0).min(u32::MAX as f32) as u32;
1063 let width = right.saturating_sub(left);
1064 let height = bottom.saturating_sub(top);
1065 (width > 0 && height > 0).then_some(Bounds {
1066 x: left,
1067 y: top,
1068 width,
1069 height,
1070 })
1071}
1072
1073fn scale_corner_radius(radii: CornerRadius, scale: f32) -> CornerRadius {
1074 CornerRadius {
1075 top_left: scale_u32_f32(radii.top_left, scale),
1076 top_right: scale_u32_f32(radii.top_right, scale),
1077 bottom_right: scale_u32_f32(radii.bottom_right, scale),
1078 bottom_left: scale_u32_f32(radii.bottom_left, scale),
1079 }
1080}
1081
1082fn scale_border_widths(widths: BorderWidth, scale: f32) -> BorderWidth {
1083 BorderWidth {
1084 top: scale_u32_f32(widths.top, scale),
1085 right: scale_u32_f32(widths.right, scale),
1086 bottom: scale_u32_f32(widths.bottom, scale),
1087 left: scale_u32_f32(widths.left, scale),
1088 }
1089}
1090
1091fn scale_u32_f32(value: u32, scale: f32) -> u32 {
1092 let scaled = f64::from(value) * f64::from(scale);
1093 if !scaled.is_finite() || scaled <= 0.0 {
1094 0
1095 } else {
1096 scaled.round().min(f64::from(u32::MAX)) as u32
1097 }
1098}
1099
1100fn hit_point(x: f64, y: f64) -> Option<(u32, u32)> {
1101 if !x.is_finite() || !y.is_finite() || x < 0.0 || y < 0.0 {
1102 return None;
1103 }
1104 let x = x.floor();
1105 let y = y.floor();
1106 if x > u32::MAX as f64 || y > u32::MAX as f64 {
1107 return None;
1108 }
1109 Some((x as u32, y as u32))
1110}