Skip to main content

simple_render/ui/render/
rect.rs

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}