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 transform(mut self, transform: PaintTransform) -> Self {
270 self.style.transform = transform;
271 self
272 }
273
274 pub fn translate(mut self, x: i32, y: i32) -> Self {
275 self.style.transform.translate_x = x;
276 self.style.transform.translate_y = y;
277 self
278 }
279
280 pub fn scale(mut self, scale: f32) -> Self {
281 self.style.transform.scale = scale;
282 self
283 }
284
285 pub fn content(mut self, content: impl Into<Content>) -> Self {
286 self.content = Some(content.into());
287 self
288 }
289
290 pub fn with_surface(mut self, surface: Surface) -> Self {
291 self.layer_options = surface.into();
292 self
293 }
294
295 pub fn begin(_: Bounds) -> Self {
296 Self::default()
297 }
298
299 pub fn build(bounds: Bounds, content: impl FnOnce(&mut Self)) -> Self {
300 let mut ui = Self::begin(bounds);
301 content(&mut ui);
302 ui
303 }
304
305 pub fn child(mut self, child: Rect) -> Self {
306 self.children.push(child);
307 self
308 }
309
310 pub fn children(mut self, children: impl IntoIterator<Item = Rect>) -> Self {
311 self.children.extend(children);
312 self
313 }
314
315 pub fn draw(self) -> wayland::Result<()> {
316 let options = self.layer_options.clone();
317 options.show(UiRenderer {
318 root: self,
319 fonts: LazyFontCtx::new(),
320 })
321 }
322
323 pub fn draw_with_commands(self, receiver: wayland::RenderReceiver) -> wayland::Result<()> {
324 let options = self.layer_options.clone();
325 options.show_with_commands(
326 UiRenderer {
327 root: self,
328 fonts: LazyFontCtx::new(),
329 },
330 receiver,
331 )
332 }
333
334 pub fn commands(&self, bounds: Bounds) -> Vec<DrawCommand> {
335 let mut fonts = FontCtx::new();
336 self.commands_with_fonts(bounds, &mut fonts)
337 }
338
339 pub fn commands_with_fonts(&self, bounds: Bounds, fonts: &mut FontCtx) -> Vec<DrawCommand> {
340 let mut commands = Vec::new();
341 Self::visit_layout(
342 self,
343 bounds,
344 Clip::rect(bounds),
345 VisualState::IDENTITY,
346 1.0,
347 None,
348 fonts,
349 &mut |command, _| {
350 commands.push(command.to_owned());
351 },
352 );
353 fonts.trim_scratch();
354 commands
355 }
356
357 pub fn visual_bounds(&self, bounds: Bounds) -> Option<Bounds> {
358 let mut fonts = FontCtx::new();
359 self.visual_bounds_with_fonts(bounds, &mut fonts)
360 }
361
362 pub fn visual_bounds_with_fonts(&self, bounds: Bounds, fonts: &mut FontCtx) -> Option<Bounds> {
363 let mut visual_bounds: Option<Bounds> = None;
364 Self::visit_layout(
365 self,
366 bounds,
367 Clip::rect(bounds),
368 VisualState::IDENTITY,
369 1.0,
370 None,
371 fonts,
372 &mut |command, _| {
373 let bounds = command.rect();
374 visual_bounds = Some(match visual_bounds {
375 Some(current) => current.union(bounds),
376 None => bounds,
377 });
378 },
379 );
380 fonts.trim_scratch();
381 visual_bounds
382 }
383
384 pub fn measure(&self, available_width: u32, available_height: u32) -> MeasuredSize {
385 let mut fonts = FontCtx::new();
386 self.measure_with_fonts(available_width, available_height, &mut fonts)
387 }
388
389 pub fn measure_with_fonts(
390 &self,
391 available_width: u32,
392 available_height: u32,
393 fonts: &mut FontCtx,
394 ) -> MeasuredSize {
395 let measured = measure_element(self, fonts, available_width, available_height);
396 fonts.trim_scratch();
397 measured.into()
398 }
399
400 pub fn hit_test(&self, bounds: Bounds, x: f64, y: f64) -> Option<Hit> {
401 let mut fonts = FontCtx::new();
402 self.hit_test_with_fonts(bounds, x, y, &mut fonts)
403 }
404
405 pub fn hit_test_with_fonts(
406 &self,
407 bounds: Bounds,
408 x: f64,
409 y: f64,
410 fonts: &mut FontCtx,
411 ) -> Option<Hit> {
412 let mut path = Vec::new();
413 self.hit_test_path_with_fonts(bounds, x, y, fonts, &mut path)
414 .map(|bounds| Hit { path, bounds })
415 }
416
417 pub fn hit_test_path(
418 &self,
419 bounds: Bounds,
420 x: f64,
421 y: f64,
422 path: &mut Vec<usize>,
423 ) -> Option<Bounds> {
424 let mut fonts = FontCtx::new();
425 self.hit_test_path_with_fonts(bounds, x, y, &mut fonts, path)
426 }
427
428 pub fn hit_test_path_with_fonts(
429 &self,
430 bounds: Bounds,
431 x: f64,
432 y: f64,
433 fonts: &mut FontCtx,
434 path: &mut Vec<usize>,
435 ) -> Option<Bounds> {
436 path.clear();
437 let Some((x, y)) = hit_point(x, y) else {
438 fonts.trim_scratch();
439 return None;
440 };
441
442 let mut current_path = Vec::new();
443 let bounds = Self::hit_test_layout(
444 self,
445 bounds,
446 Clip::rect(bounds),
447 VisualState::IDENTITY,
448 None,
449 fonts,
450 x,
451 y,
452 &mut current_path,
453 path,
454 );
455 fonts.trim_scratch();
456 bounds
457 }
458
459 pub fn paint(&mut self, canvas: &mut Canvas<'_>) {
460 let mut fonts = FontCtx::new();
461 self.paint_with_fonts(canvas, &mut fonts);
462 }
463
464 pub fn paint_with_fonts(&mut self, canvas: &mut Canvas<'_>, fonts: &mut FontCtx) {
465 self.paint_scaled_with_fonts(canvas, fonts, canvas.width(), canvas.height(), 1);
466 }
467
468 #[allow(clippy::too_many_arguments)]
469 pub fn paint_bgra_with_fonts(
470 &mut self,
471 pixels: &mut [u8],
472 buffer_width: u32,
473 buffer_height: u32,
474 stride: u32,
475 logical_width: u32,
476 logical_height: u32,
477 scale: u32,
478 fonts: &mut FontCtx,
479 ) -> Option<DamageRect> {
480 self.paint_bgra_viewport_with_fonts(
481 pixels,
482 buffer_width,
483 buffer_height,
484 stride,
485 logical_width,
486 logical_height,
487 0,
488 0,
489 scale,
490 fonts,
491 )
492 }
493
494 #[allow(clippy::too_many_arguments)]
495 pub fn paint_bgra_viewport_with_fonts(
496 &mut self,
497 pixels: &mut [u8],
498 buffer_width: u32,
499 buffer_height: u32,
500 stride: u32,
501 logical_width: u32,
502 logical_height: u32,
503 viewport_x: i32,
504 viewport_y: i32,
505 scale: u32,
506 fonts: &mut FontCtx,
507 ) -> Option<DamageRect> {
508 let mut canvas =
509 Canvas::from_bgra_pixels(pixels, buffer_width, buffer_height, stride, scale)?;
510 self.paint_viewport_with_fonts(
511 &mut canvas,
512 fonts,
513 logical_width,
514 logical_height,
515 viewport_x,
516 viewport_y,
517 scale,
518 );
519 canvas.damage()
520 }
521
522 #[allow(clippy::too_many_arguments)]
523 pub fn paint_bgra_transformed_with_fonts(
524 &mut self,
525 pixels: &mut [u8],
526 buffer_width: u32,
527 buffer_height: u32,
528 stride: u32,
529 logical_width: u32,
530 logical_height: u32,
531 scale: u32,
532 transform: PaintTransform,
533 fonts: &mut FontCtx,
534 ) -> Option<DamageRect> {
535 self.paint_bgra_transformed_viewport_with_fonts(
536 pixels,
537 buffer_width,
538 buffer_height,
539 stride,
540 logical_width,
541 logical_height,
542 0,
543 0,
544 scale,
545 transform,
546 fonts,
547 )
548 }
549
550 #[allow(clippy::too_many_arguments)]
551 pub fn paint_bgra_transformed_viewport_with_fonts(
552 &mut self,
553 pixels: &mut [u8],
554 buffer_width: u32,
555 buffer_height: u32,
556 stride: u32,
557 logical_width: u32,
558 logical_height: u32,
559 viewport_x: i32,
560 viewport_y: i32,
561 scale: u32,
562 transform: PaintTransform,
563 fonts: &mut FontCtx,
564 ) -> Option<DamageRect> {
565 let mut canvas =
566 Canvas::from_bgra_pixels(pixels, buffer_width, buffer_height, stride, scale)?;
567 self.paint_transformed_viewport_with_fonts(
568 &mut canvas,
569 fonts,
570 logical_width,
571 logical_height,
572 viewport_x,
573 viewport_y,
574 scale,
575 transform,
576 );
577 canvas.damage()
578 }
579
580 #[allow(clippy::too_many_arguments)]
581 pub fn paint_bgra(
582 &mut self,
583 pixels: &mut [u8],
584 buffer_width: u32,
585 buffer_height: u32,
586 stride: u32,
587 logical_width: u32,
588 logical_height: u32,
589 scale: u32,
590 ) -> Option<DamageRect> {
591 let mut fonts = FontCtx::new();
592 self.paint_bgra_with_fonts(
593 pixels,
594 buffer_width,
595 buffer_height,
596 stride,
597 logical_width,
598 logical_height,
599 scale,
600 &mut fonts,
601 )
602 }
603
604 #[allow(clippy::too_many_arguments)]
605 pub fn paint_bgra_transformed(
606 &mut self,
607 pixels: &mut [u8],
608 buffer_width: u32,
609 buffer_height: u32,
610 stride: u32,
611 logical_width: u32,
612 logical_height: u32,
613 scale: u32,
614 transform: PaintTransform,
615 ) -> Option<DamageRect> {
616 let mut fonts = FontCtx::new();
617 self.paint_bgra_transformed_with_fonts(
618 pixels,
619 buffer_width,
620 buffer_height,
621 stride,
622 logical_width,
623 logical_height,
624 scale,
625 transform,
626 &mut fonts,
627 )
628 }
629
630 #[allow(clippy::too_many_arguments)]
631 pub fn paint_bgra_transformed_viewport(
632 &mut self,
633 pixels: &mut [u8],
634 buffer_width: u32,
635 buffer_height: u32,
636 stride: u32,
637 logical_width: u32,
638 logical_height: u32,
639 viewport_x: i32,
640 viewport_y: i32,
641 scale: u32,
642 transform: PaintTransform,
643 ) -> Option<DamageRect> {
644 let mut fonts = FontCtx::new();
645 self.paint_bgra_transformed_viewport_with_fonts(
646 pixels,
647 buffer_width,
648 buffer_height,
649 stride,
650 logical_width,
651 logical_height,
652 viewport_x,
653 viewport_y,
654 scale,
655 transform,
656 &mut fonts,
657 )
658 }
659
660 #[allow(clippy::too_many_arguments)]
661 pub fn paint_bgra_viewport(
662 &mut self,
663 pixels: &mut [u8],
664 buffer_width: u32,
665 buffer_height: u32,
666 stride: u32,
667 logical_width: u32,
668 logical_height: u32,
669 viewport_x: i32,
670 viewport_y: i32,
671 scale: u32,
672 ) -> Option<DamageRect> {
673 let mut fonts = FontCtx::new();
674 self.paint_bgra_viewport_with_fonts(
675 pixels,
676 buffer_width,
677 buffer_height,
678 stride,
679 logical_width,
680 logical_height,
681 viewport_x,
682 viewport_y,
683 scale,
684 &mut fonts,
685 )
686 }
687
688 pub fn paint_scaled_with_fonts(
689 &mut self,
690 canvas: &mut Canvas<'_>,
691 fonts: &mut FontCtx,
692 width: u32,
693 height: u32,
694 scale: u32,
695 ) {
696 self.paint_viewport_with_fonts(canvas, fonts, width, height, 0, 0, scale);
697 }
698
699 #[allow(clippy::too_many_arguments)]
700 pub fn paint_transformed_with_fonts(
701 &mut self,
702 canvas: &mut Canvas<'_>,
703 fonts: &mut FontCtx,
704 width: u32,
705 height: u32,
706 scale: u32,
707 transform: PaintTransform,
708 ) {
709 self.paint_transformed_viewport_with_fonts(
710 canvas, fonts, width, height, 0, 0, scale, transform,
711 );
712 }
713
714 #[allow(clippy::too_many_arguments)]
715 pub fn paint_viewport_with_fonts(
716 &mut self,
717 canvas: &mut Canvas<'_>,
718 fonts: &mut FontCtx,
719 width: u32,
720 height: u32,
721 viewport_x: i32,
722 viewport_y: i32,
723 scale: u32,
724 ) {
725 let scale = scale.max(1);
726 let offset = PaintOffset {
727 x: scale_i32(viewport_x, scale),
728 y: scale_i32(viewport_y, scale),
729 };
730 let bounds = Bounds::new(0, 0, width, height);
731 Self::visit_layout(
732 self,
733 bounds,
734 Clip::rect(bounds),
735 VisualState::IDENTITY,
736 1.0,
737 None,
738 fonts,
739 &mut |command, fonts| {
740 paint_scaled_command_with_offset(canvas, fonts, command, scale, offset);
741 },
742 );
743 fonts.trim_scratch();
744 }
745
746 #[allow(clippy::too_many_arguments)]
747 pub fn paint_transformed_viewport_with_fonts(
748 &mut self,
749 canvas: &mut Canvas<'_>,
750 fonts: &mut FontCtx,
751 width: u32,
752 height: u32,
753 viewport_x: i32,
754 viewport_y: i32,
755 scale: u32,
756 transform: PaintTransform,
757 ) {
758 let scale = scale.max(1);
759 let visual_scale = if transform.scale.is_finite() {
760 transform.scale.max(0.0)
761 } else {
762 0.0
763 };
764 if visual_scale <= 0.0 {
765 fonts.trim_scratch();
766 return;
767 }
768 if transform.is_identity() {
769 self.paint_viewport_with_fonts(
770 canvas, fonts, width, height, viewport_x, viewport_y, scale,
771 );
772 return;
773 }
774
775 let total_scale = scale as f32 * visual_scale;
776 let offset = PaintOffset {
777 x: scale_i32_f32(viewport_x, total_scale).saturating_sub(transform.translate_x),
778 y: scale_i32_f32(viewport_y, total_scale).saturating_sub(transform.translate_y),
779 };
780 let bounds = Bounds::new(0, 0, width, height);
781 Self::visit_layout(
782 self,
783 bounds,
784 Clip::rect(bounds),
785 VisualState::IDENTITY,
786 1.0,
787 None,
788 fonts,
789 &mut |command, fonts| {
790 paint_scaled_f32_command_with_offset(canvas, fonts, command, total_scale, offset);
791 },
792 );
793 fonts.trim_scratch();
794 }
795
796 pub fn render(&mut self, canvas: &mut Canvas<'_>) {
797 self.paint(canvas);
798 }
799
800 fn visit_layout(
801 element: &Rect,
802 bounds: Bounds,
803 clip: Clip,
804 state: VisualState,
805 opacity: f32,
806 premeasured: Option<Size>,
807 fonts: &mut FontCtx,
808 visit: &mut dyn FnMut(PaintCommand<'_>, &mut FontCtx),
809 ) {
810 let measured = if element_needs_measure(element) {
811 premeasured
812 .unwrap_or_else(|| measure_element(element, fonts, bounds.width, bounds.height))
813 } else {
814 Size::default()
815 };
816 let width = resolve_length(
817 element.width,
818 bounds.width,
819 measured.width,
820 element.min_width,
821 element.max_width,
822 );
823 let height = resolve_length(
824 element.height,
825 bounds.height,
826 measured.height,
827 element.min_height,
828 element.max_height,
829 );
830 let rect = Bounds {
831 x: bounds.x,
832 y: bounds.y,
833 width,
834 height,
835 };
836 let state = state.then_element(element.style.transform, rect);
837 let visual_rect = state.bounds(rect);
838 let own_clip = visual_rect.and_then(|rect| clip.intersect_bounds(rect));
839 let opacity = multiply_opacity(opacity, element.style.opacity);
840 let radii = state.radii(element_corner_radii(element));
841
842 if let (Some(rect), Some(clip), Some(paint)) =
843 (visual_rect, own_clip, element.style.background.as_ref())
844 {
845 visit(
846 PaintCommand::Rect {
847 rect,
848 clip,
849 opacity,
850 paint,
851 gradient: element.style.gradient,
852 radii,
853 },
854 fonts,
855 );
856 }
857 if let (Some(rect), Some(clip), Some(border)) =
858 (visual_rect, own_clip, element.style.border.as_ref())
859 {
860 visit(
861 PaintCommand::Border {
862 rect,
863 clip,
864 opacity,
865 paint: &border.color,
866 gradient: border.gradient,
867 widths: state.border_widths(border_widths(border)),
868 radii,
869 },
870 fonts,
871 );
872 }
873 let content_rect = rect.inset(element.padding);
874 let visual_content_rect = state.bounds(content_rect);
875 if let (Some(rect), Some(clip), Some(content)) =
876 (visual_content_rect, own_clip, element.content.as_ref())
877 {
878 match content {
879 Content::Text(text) => visit(
880 PaintCommand::Text {
881 rect,
882 clip,
883 opacity,
884 scale: state.scale,
885 text,
886 },
887 fonts,
888 ),
889 Content::RichText(text) => visit(
890 PaintCommand::RichText {
891 rect,
892 clip,
893 opacity,
894 scale: state.scale,
895 text,
896 },
897 fonts,
898 ),
899 Content::Image(image) => visit(
900 PaintCommand::Image {
901 rect,
902 clip,
903 opacity,
904 image,
905 },
906 fonts,
907 ),
908 }
909 }
910
911 if element.children.is_empty() {
912 return;
913 }
914
915 let child_clip = match element.overflow {
916 Overflow::Clip => {
917 let (Some(clip), Some(content_rect)) = (own_clip, visual_content_rect) else {
918 return;
919 };
920 let Some(child_clip) =
921 clip.with_rounded_rect(content_rect, state.radii(content_clip_radii(element)))
922 else {
923 return;
924 };
925 child_clip
926 }
927 Overflow::Visible => clip,
928 };
929 layout_children(
930 element,
931 content_rect,
932 fonts,
933 |_, child, rect, measured, fonts| {
934 Self::visit_layout(
935 child,
936 rect,
937 child_clip,
938 state,
939 opacity,
940 Some(measured),
941 fonts,
942 visit,
943 );
944 },
945 );
946 }
947
948 #[allow(clippy::too_many_arguments)]
949 fn hit_test_layout(
950 element: &Rect,
951 bounds: Bounds,
952 clip: Clip,
953 state: VisualState,
954 premeasured: Option<Size>,
955 fonts: &mut FontCtx,
956 x: u32,
957 y: u32,
958 current_path: &mut Vec<usize>,
959 hit_path: &mut Vec<usize>,
960 ) -> Option<Bounds> {
961 let measured = if element_needs_measure(element) {
962 premeasured
963 .unwrap_or_else(|| measure_element(element, fonts, bounds.width, bounds.height))
964 } else {
965 Size::default()
966 };
967 let width = resolve_length(
968 element.width,
969 bounds.width,
970 measured.width,
971 element.min_width,
972 element.max_width,
973 );
974 let height = resolve_length(
975 element.height,
976 bounds.height,
977 measured.height,
978 element.min_height,
979 element.max_height,
980 );
981 let rect = Bounds {
982 x: bounds.x,
983 y: bounds.y,
984 width,
985 height,
986 };
987 let state = state.then_element(element.style.transform, rect);
988 let visual_rect = state.bounds(rect)?;
989 let clip = clip.intersect_bounds(visual_rect)?;
990 let content_rect = rect.inset(element.padding);
991 let visual_content_rect = state.bounds(content_rect)?;
992 let child_clip = match element.overflow {
993 Overflow::Clip => clip.with_rounded_rect(
994 visual_content_rect,
995 state.radii(content_clip_radii(element)),
996 )?,
997 Overflow::Visible => clip,
998 };
999
1000 let mut hit = None;
1001 layout_children(
1002 element,
1003 content_rect,
1004 fonts,
1005 |index, child, rect, measured, fonts| {
1006 current_path.push(index);
1007 if let Some(bounds) = Self::hit_test_layout(
1008 child,
1009 rect,
1010 child_clip,
1011 state,
1012 Some(measured),
1013 fonts,
1014 x,
1015 y,
1016 current_path,
1017 hit_path,
1018 ) {
1019 hit = Some(bounds);
1020 }
1021 current_path.pop();
1022 },
1023 );
1024
1025 if let Some(bounds) = hit {
1026 return Some(bounds);
1027 }
1028
1029 let hit_clip =
1030 clip.with_rounded_rect(visual_rect, state.radii(element_corner_radii(element)))?;
1031 if hit_clip.contains(x, y) {
1032 hit_path.clear();
1033 hit_path.extend_from_slice(current_path);
1034 Some(visual_rect)
1035 } else {
1036 None
1037 }
1038 }
1039}
1040
1041fn signed_bounds(left: f32, top: f32, right: f32, bottom: f32) -> Option<Bounds> {
1042 if !left.is_finite()
1043 || !top.is_finite()
1044 || !right.is_finite()
1045 || !bottom.is_finite()
1046 || right <= left
1047 || bottom <= top
1048 {
1049 return None;
1050 }
1051
1052 let left = left.max(0.0).min(u32::MAX as f32) as u32;
1053 let top = top.max(0.0).min(u32::MAX as f32) as u32;
1054 let right = right.max(0.0).min(u32::MAX as f32) as u32;
1055 let bottom = bottom.max(0.0).min(u32::MAX as f32) as u32;
1056 let width = right.saturating_sub(left);
1057 let height = bottom.saturating_sub(top);
1058 (width > 0 && height > 0).then_some(Bounds {
1059 x: left,
1060 y: top,
1061 width,
1062 height,
1063 })
1064}
1065
1066fn scale_corner_radius(radii: CornerRadius, scale: f32) -> CornerRadius {
1067 CornerRadius {
1068 top_left: scale_u32_f32(radii.top_left, scale),
1069 top_right: scale_u32_f32(radii.top_right, scale),
1070 bottom_right: scale_u32_f32(radii.bottom_right, scale),
1071 bottom_left: scale_u32_f32(radii.bottom_left, scale),
1072 }
1073}
1074
1075fn scale_border_widths(widths: BorderWidth, scale: f32) -> BorderWidth {
1076 BorderWidth {
1077 top: scale_u32_f32(widths.top, scale),
1078 right: scale_u32_f32(widths.right, scale),
1079 bottom: scale_u32_f32(widths.bottom, scale),
1080 left: scale_u32_f32(widths.left, scale),
1081 }
1082}
1083
1084fn scale_u32_f32(value: u32, scale: f32) -> u32 {
1085 let scaled = f64::from(value) * f64::from(scale);
1086 if !scaled.is_finite() || scaled <= 0.0 {
1087 0
1088 } else {
1089 scaled.round().min(f64::from(u32::MAX)) as u32
1090 }
1091}
1092
1093fn hit_point(x: f64, y: f64) -> Option<(u32, u32)> {
1094 if !x.is_finite() || !y.is_finite() || x < 0.0 || y < 0.0 {
1095 return None;
1096 }
1097 let x = x.floor();
1098 let y = y.floor();
1099 if x > u32::MAX as f64 || y > u32::MAX as f64 {
1100 return None;
1101 }
1102 Some((x as u32, y as u32))
1103}