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 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}