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