1use crate::Corners4;
2use crate::{
3 ChromeRefinement, ColorRef, Edges4, Items, Justify, LayoutRefinement, LengthRefinement,
4 MarginEdge, MetricRef, Radius, SignedMetricRef, Space,
5};
6use fret_core::{
7 FontId, FontWeight, Px, SemanticsRole, TextLineHeightPolicy, TextOverflow, TextWrap,
8};
9use fret_ui::element::{AnyElement, CrossAlign, ScrollAxis, SemanticsDecoration, TextInkOverflow};
10use fret_ui::scroll::ScrollHandle;
11use fret_ui::{ElementContext, ElementContextAccess, UiHost};
12use std::panic::Location;
13use std::sync::Arc;
14
15#[derive(Debug, Clone, Default)]
21pub struct UiPatch {
22 pub chrome: ChromeRefinement,
23 pub layout: LayoutRefinement,
24}
25
26impl UiPatch {
27 pub fn merge(mut self, other: UiPatch) -> Self {
28 self.chrome = self.chrome.merge(other.chrome);
29 self.layout = self.layout.merge(other.layout);
30 self
31 }
32}
33
34pub trait UiPatchTarget: Sized {
38 fn apply_ui_patch(self, patch: UiPatch) -> Self;
43}
44
45pub trait UiSupportsChrome {}
47
48pub trait UiSupportsLayout {}
50
51pub trait IntoUiElement<H: UiHost>: Sized {
58 #[track_caller]
59 fn into_element(self, cx: &mut ElementContext<'_, H>) -> AnyElement;
60}
61
62pub trait IntoUiElementInExt<H: UiHost>: IntoUiElement<H> + Sized {
65 #[track_caller]
66 fn into_element_in<'a, Cx>(self, cx: &mut Cx) -> AnyElement
67 where
68 Cx: ElementContextAccess<'a, H>,
69 H: 'a,
70 {
71 self.into_element(cx.elements())
72 }
73}
74
75impl<H: UiHost, T> IntoUiElementInExt<H> for T where T: IntoUiElement<H> {}
76
77impl<H: UiHost> IntoUiElement<H> for AnyElement {
78 #[track_caller]
79 fn into_element(self, _cx: &mut ElementContext<'_, H>) -> AnyElement {
80 self
81 }
82}
83
84#[derive(Debug, Clone)]
86pub struct UiBuilder<T> {
87 inner: T,
88 patch: UiPatch,
89 semantics: Option<SemanticsDecoration>,
90 key_context: Option<Arc<str>>,
91}
92
93impl<T> UiBuilder<T> {
94 pub fn new(inner: T) -> Self {
95 Self {
96 inner,
97 patch: UiPatch::default(),
98 semantics: None,
99 key_context: None,
100 }
101 }
102
103 pub fn semantics(mut self, decoration: SemanticsDecoration) -> Self {
104 self.semantics = Some(match self.semantics.take() {
105 Some(existing) => existing.merge(decoration),
106 None => decoration,
107 });
108 self
109 }
110
111 pub fn test_id(self, test_id: impl Into<Arc<str>>) -> Self {
112 self.semantics(SemanticsDecoration::default().test_id(test_id))
113 }
114
115 pub fn a11y_role(self, role: SemanticsRole) -> Self {
116 self.semantics(SemanticsDecoration::default().role(role))
117 }
118
119 pub fn role(self, role: SemanticsRole) -> Self {
120 self.a11y_role(role)
121 }
122
123 pub fn a11y_label(self, label: impl Into<Arc<str>>) -> Self {
124 self.semantics(SemanticsDecoration::default().label(label))
125 }
126
127 pub fn key_context(mut self, key_context: impl Into<Arc<str>>) -> Self {
128 self.key_context = Some(key_context.into());
129 self
130 }
131
132 pub fn style(mut self, style: ChromeRefinement) -> Self
133 where
134 T: UiSupportsChrome,
135 {
136 self.patch.chrome = self.patch.chrome.merge(style);
137 self
138 }
139
140 pub fn layout(mut self, layout: LayoutRefinement) -> Self
141 where
142 T: UiSupportsLayout,
143 {
144 self.patch.layout = self.patch.layout.merge(layout);
145 self
146 }
147
148 pub fn style_with(self, f: impl FnOnce(ChromeRefinement) -> ChromeRefinement) -> Self
149 where
150 T: UiSupportsChrome,
151 {
152 self.style(f(ChromeRefinement::default()))
153 }
154
155 pub fn layout_with(self, f: impl FnOnce(LayoutRefinement) -> LayoutRefinement) -> Self
156 where
157 T: UiSupportsLayout,
158 {
159 self.layout(f(LayoutRefinement::default()))
160 }
161}
162
163macro_rules! forward_style_noargs {
164 ($($name:ident),+ $(,)?) => {
165 $(
166 pub fn $name(self) -> Self {
167 self.style_with(|c| c.$name())
168 }
169 )+
170 };
171}
172
173macro_rules! forward_layout_noargs {
174 ($($name:ident),+ $(,)?) => {
175 $(
176 pub fn $name(self) -> Self {
177 self.layout_with(|l| l.$name())
178 }
179 )+
180 };
181}
182
183impl UiBuilder<crate::ui::TextBox> {
184 pub fn selectable(mut self, selectable: bool) -> Self {
190 self.inner.selectable = selectable;
191 self
192 }
193
194 pub fn selectable_on(self) -> Self {
196 self.selectable(true)
197 }
198
199 pub fn selectable_off(self) -> Self {
201 self.selectable(false)
202 }
203
204 pub fn text_xs(mut self) -> Self {
205 self.inner.preset = crate::ui::TextPreset::Xs;
206 self.inner.wrap = TextWrap::Word;
207 self
208 }
209
210 pub fn text_sm(mut self) -> Self {
211 self.inner.preset = crate::ui::TextPreset::Sm;
212 self.inner.wrap = TextWrap::Word;
213 self
214 }
215
216 pub fn text_base(mut self) -> Self {
217 self.inner.preset = crate::ui::TextPreset::Base;
218 self.inner.wrap = TextWrap::Word;
219 self
220 }
221
222 pub fn text_prose(mut self) -> Self {
223 self.inner.preset = crate::ui::TextPreset::Prose;
224 self.inner.wrap = TextWrap::Word;
225 self
226 }
227
228 pub fn font_weight(mut self, weight: FontWeight) -> Self {
229 self.inner.weight_override = Some(weight);
230 self
231 }
232
233 pub fn font(mut self, font: FontId) -> Self {
234 self.inner.font_override = Some(font);
235 self
236 }
237
238 pub fn font_feature(mut self, tag: impl Into<String>, value: u32) -> Self {
239 self.inner
240 .features_override
241 .push(fret_core::TextFontFeatureSetting {
242 tag: tag.into().into(),
243 value,
244 });
245 self
246 }
247
248 pub fn font_axis(mut self, tag: impl Into<String>, value: f32) -> Self {
249 self.inner
250 .axes_override
251 .push(fret_core::TextFontAxisSetting {
252 tag: tag.into().into(),
253 value,
254 });
255 self
256 }
257
258 pub fn tabular_nums(self) -> Self {
260 self.font_feature("tnum", 1)
261 }
262
263 pub fn proportional_nums(self) -> Self {
265 self.font_feature("pnum", 1)
266 }
267
268 pub fn lining_nums(self) -> Self {
270 self.font_feature("lnum", 1)
271 }
272
273 pub fn oldstyle_nums(self) -> Self {
275 self.font_feature("onum", 1)
276 }
277
278 pub fn slashed_zero(self) -> Self {
280 self.font_feature("zero", 1)
281 }
282
283 pub fn ordinal(self) -> Self {
285 self.font_feature("ordn", 1)
286 }
287
288 pub fn diagonal_fractions(self) -> Self {
290 self.font_feature("frac", 1)
291 }
292
293 pub fn stacked_fractions(self) -> Self {
295 self.font_feature("afrc", 1)
296 }
297
298 pub fn font_ui(self) -> Self {
299 self.font(FontId::ui())
300 }
301
302 pub fn font_monospace(self) -> Self {
303 self.font(FontId::monospace())
304 }
305
306 pub fn font_normal(self) -> Self {
307 self.font_weight(FontWeight::NORMAL)
308 }
309
310 pub fn font_medium(self) -> Self {
311 self.font_weight(FontWeight::MEDIUM)
312 }
313
314 pub fn font_semibold(self) -> Self {
315 self.font_weight(FontWeight::SEMIBOLD)
316 }
317
318 pub fn font_bold(self) -> Self {
319 self.font_weight(FontWeight::BOLD)
320 }
321
322 pub fn text_color(mut self, color: ColorRef) -> Self {
323 self.inner.color_override = Some(color);
324 self
325 }
326
327 pub fn text_size_px(mut self, size: Px) -> Self {
328 self.inner.size_override = Some(size);
329 self
330 }
331
332 pub fn line_height_px(mut self, height: Px) -> Self {
333 self.inner.line_height_override = Some(height);
334 if self.inner.line_height_policy_override.is_none() {
335 self.inner.line_height_policy_override = Some(TextLineHeightPolicy::FixedFromStyle);
336 }
337 self
338 }
339
340 pub fn line_height_em(mut self, line_height_em: f32) -> Self {
341 self.inner.line_height_em_override = Some(line_height_em);
342 if self.inner.line_height_policy_override.is_none() {
343 self.inner.line_height_policy_override = Some(TextLineHeightPolicy::FixedFromStyle);
344 }
345 self
346 }
347
348 pub fn line_height_preset(mut self, preset: crate::ui::TextLineHeightPreset) -> Self {
349 self.inner.line_height_em_override = Some(preset.em());
350 self.inner.line_height_policy_override = Some(TextLineHeightPolicy::FixedFromStyle);
351 self
352 }
353
354 pub fn line_height_policy(mut self, policy: TextLineHeightPolicy) -> Self {
355 self.inner.line_height_policy_override = Some(policy);
356 self
357 }
358
359 pub fn control(self) -> Self {
360 self.line_height_policy(TextLineHeightPolicy::FixedFromStyle)
361 }
362
363 pub fn content(self) -> Self {
364 self.line_height_policy(TextLineHeightPolicy::ExpandToFit)
365 }
366
367 pub fn fixed_line_box_px(mut self, height: Px) -> Self {
373 self.inner.line_height_policy_override = Some(TextLineHeightPolicy::FixedFromStyle);
374 self.line_height_px(height).h_px(height)
375 }
376
377 pub fn letter_spacing_em(mut self, letter_spacing_em: f32) -> Self {
378 self.inner.letter_spacing_em_override = Some(letter_spacing_em);
379 self
380 }
381
382 pub fn wrap(mut self, wrap: TextWrap) -> Self {
383 self.inner.wrap = wrap;
384 self
385 }
386
387 pub fn overflow(mut self, overflow: TextOverflow) -> Self {
388 self.inner.overflow = overflow;
389 self
390 }
391
392 pub fn ink_overflow(mut self, ink_overflow: TextInkOverflow) -> Self {
393 self.inner.ink_overflow_override = Some(ink_overflow);
394 self
395 }
396
397 pub fn auto_pad_ink_overflow(self) -> Self {
398 self.ink_overflow(TextInkOverflow::AutoPad)
399 }
400
401 pub fn text_align(mut self, align: fret_core::TextAlign) -> Self {
402 self.inner.align = align;
403 self
404 }
405
406 pub fn nowrap(self) -> Self {
407 self.wrap(TextWrap::None).overflow(TextOverflow::Clip)
408 }
409
410 pub fn truncate(self) -> Self {
411 self.wrap(TextWrap::None).overflow(TextOverflow::Ellipsis)
412 }
413
414 pub fn break_words(self) -> Self {
419 self.wrap(TextWrap::WordBreak).overflow(TextOverflow::Clip)
420 }
421
422 pub fn text_balance(self) -> Self {
424 self.wrap(TextWrap::Balance)
425 }
426
427 pub fn line_box_in_bounds(mut self) -> Self {
432 self.inner.vertical_placement_override =
433 Some(fret_core::TextVerticalPlacement::BoundsAsLineBox);
434 self
435 }
436}
437
438impl UiBuilder<crate::ui::RawTextBox> {
439 pub fn text_color(mut self, color: ColorRef) -> Self {
440 self.inner.color_override = Some(color);
441 self
442 }
443
444 pub fn wrap(mut self, wrap: TextWrap) -> Self {
445 self.inner.wrap = wrap;
446 self
447 }
448
449 pub fn overflow(mut self, overflow: TextOverflow) -> Self {
450 self.inner.overflow = overflow;
451 self
452 }
453
454 pub fn ink_overflow(mut self, ink_overflow: TextInkOverflow) -> Self {
455 self.inner.ink_overflow_override = Some(ink_overflow);
456 self
457 }
458
459 pub fn auto_pad_ink_overflow(self) -> Self {
460 self.ink_overflow(TextInkOverflow::AutoPad)
461 }
462
463 pub fn text_align(mut self, align: fret_core::TextAlign) -> Self {
464 self.inner.align = align;
465 self
466 }
467
468 pub fn nowrap(self) -> Self {
469 self.wrap(TextWrap::None).overflow(TextOverflow::Clip)
470 }
471
472 pub fn truncate(self) -> Self {
473 self.wrap(TextWrap::None).overflow(TextOverflow::Ellipsis)
474 }
475
476 pub fn break_words(self) -> Self {
481 self.wrap(TextWrap::WordBreak).overflow(TextOverflow::Clip)
482 }
483
484 pub fn text_balance(self) -> Self {
486 self.wrap(TextWrap::Balance)
487 }
488}
489
490impl UiBuilder<crate::ui::RichTextBox> {
491 pub fn text_style(mut self, style: fret_core::TextStyle) -> Self {
492 self.inner.style_override = Some(style);
493 self
494 }
495
496 pub fn text_color(mut self, color: ColorRef) -> Self {
497 self.inner.color_override = Some(color);
498 self
499 }
500
501 pub fn wrap(mut self, wrap: TextWrap) -> Self {
502 self.inner.wrap = wrap;
503 self
504 }
505
506 pub fn overflow(mut self, overflow: TextOverflow) -> Self {
507 self.inner.overflow = overflow;
508 self
509 }
510
511 pub fn ink_overflow(mut self, ink_overflow: TextInkOverflow) -> Self {
512 self.inner.ink_overflow_override = Some(ink_overflow);
513 self
514 }
515
516 pub fn auto_pad_ink_overflow(self) -> Self {
517 self.ink_overflow(TextInkOverflow::AutoPad)
518 }
519
520 pub fn text_align(mut self, align: fret_core::TextAlign) -> Self {
521 self.inner.align = align;
522 self
523 }
524
525 pub fn nowrap(self) -> Self {
526 self.wrap(TextWrap::None).overflow(TextOverflow::Clip)
527 }
528
529 pub fn truncate(self) -> Self {
530 self.wrap(TextWrap::None).overflow(TextOverflow::Ellipsis)
531 }
532
533 pub fn break_words(self) -> Self {
534 self.wrap(TextWrap::WordBreak).overflow(TextOverflow::Clip)
535 }
536
537 pub fn text_balance(self) -> Self {
538 self.wrap(TextWrap::Balance)
539 }
540}
541
542impl<T: UiSupportsChrome> UiBuilder<T> {
543 pub fn paddings(self, paddings: impl Into<Edges4<MetricRef>>) -> Self {
544 self.style_with(|mut c| {
545 let Edges4 {
546 top,
547 right,
548 bottom,
549 left,
550 } = paddings.into();
551 let mut padding = c.padding.unwrap_or_default();
552 padding.top = Some(top);
553 padding.right = Some(right);
554 padding.bottom = Some(bottom);
555 padding.left = Some(left);
556 c.padding = Some(padding);
557 c
558 })
559 }
560
561 pub fn paddings_fraction(self, paddings: impl Into<Edges4<f32>>) -> Self {
562 self.style_with(|mut c| {
563 let Edges4 {
564 top,
565 right,
566 bottom,
567 left,
568 } = paddings.into();
569 let mut padding = c.padding_length.unwrap_or_default();
570 padding.top = Some(LengthRefinement::Fraction(top));
571 padding.right = Some(LengthRefinement::Fraction(right));
572 padding.bottom = Some(LengthRefinement::Fraction(bottom));
573 padding.left = Some(LengthRefinement::Fraction(left));
574 c.padding_length = Some(padding);
575 c
576 })
577 }
578
579 pub fn paddings_percent(self, paddings: impl Into<Edges4<f32>>) -> Self {
580 let Edges4 {
581 top,
582 right,
583 bottom,
584 left,
585 } = paddings.into();
586 self.paddings_fraction(Edges4 {
587 top: top / 100.0,
588 right: right / 100.0,
589 bottom: bottom / 100.0,
590 left: left / 100.0,
591 })
592 }
593
594 pub fn padding(self, padding: impl Into<MetricRef>) -> Self {
595 self.paddings(Edges4::all(padding.into()))
596 }
597
598 pub fn padding_fraction(self, fraction: f32) -> Self {
599 self.paddings_fraction(Edges4::all(fraction))
600 }
601
602 pub fn padding_percent(self, percent: f32) -> Self {
603 self.padding_fraction(percent / 100.0)
604 }
605
606 pub fn padding_px(self, px: Px) -> Self {
607 self.padding(px)
608 }
609
610 pub fn padding_space(self, space: Space) -> Self {
611 self.padding(space)
612 }
613
614 pub fn focused_border(self) -> Self {
615 self.style_with(ChromeRefinement::focused_border)
616 }
617
618 pub fn corner_radii(self, radii: impl Into<Corners4<MetricRef>>) -> Self {
619 self.style_with(|c| c.corner_radii(radii))
620 }
621
622 pub fn rounded_tl(self, radius: Radius) -> Self {
623 self.style_with(|c| c.rounded_tl(radius))
624 }
625
626 pub fn rounded_tr(self, radius: Radius) -> Self {
627 self.style_with(|c| c.rounded_tr(radius))
628 }
629
630 pub fn rounded_br(self, radius: Radius) -> Self {
631 self.style_with(|c| c.rounded_br(radius))
632 }
633
634 pub fn rounded_bl(self, radius: Radius) -> Self {
635 self.style_with(|c| c.rounded_bl(radius))
636 }
637
638 pub fn shadow_none(self) -> Self {
639 self.style_with(ChromeRefinement::shadow_none)
640 }
641
642 pub fn shadow_xs(self) -> Self {
643 self.style_with(ChromeRefinement::shadow_xs)
644 }
645
646 pub fn shadow_sm(self) -> Self {
647 self.style_with(ChromeRefinement::shadow_sm)
648 }
649
650 pub fn shadow_md(self) -> Self {
651 self.style_with(ChromeRefinement::shadow_md)
652 }
653
654 pub fn shadow_lg(self) -> Self {
655 self.style_with(ChromeRefinement::shadow_lg)
656 }
657
658 pub fn shadow_xl(self) -> Self {
659 self.style_with(ChromeRefinement::shadow_xl)
660 }
661
662 pub fn debug_border(self, color: ColorRef) -> Self {
663 self.style_with(|c| c.debug_border(color))
664 }
665
666 pub fn debug_border_primary(self) -> Self {
667 self.style_with(ChromeRefinement::debug_border_primary)
668 }
669
670 pub fn debug_border_destructive(self) -> Self {
671 self.style_with(ChromeRefinement::debug_border_destructive)
672 }
673
674 pub fn debug_border_ring(self) -> Self {
675 self.style_with(ChromeRefinement::debug_border_ring)
676 }
677
678 pub fn px(self, space: Space) -> Self {
679 self.style_with(|c| c.px(space))
680 }
681
682 pub fn py(self, space: Space) -> Self {
683 self.style_with(|c| c.py(space))
684 }
685
686 pub fn p(self, space: Space) -> Self {
687 self.style_with(|c| c.p(space))
688 }
689
690 pub fn pt(self, space: Space) -> Self {
691 self.style_with(|c| c.pt(space))
692 }
693
694 pub fn pr(self, space: Space) -> Self {
695 self.style_with(|c| c.pr(space))
696 }
697
698 pub fn pb(self, space: Space) -> Self {
699 self.style_with(|c| c.pb(space))
700 }
701
702 pub fn pl(self, space: Space) -> Self {
703 self.style_with(|c| c.pl(space))
704 }
705
706 pub fn rounded(self, radius: Radius) -> Self {
707 self.style_with(|c| c.rounded(radius))
708 }
709
710 pub fn border_width(self, width: impl Into<MetricRef>) -> Self {
711 self.style_with(|c| c.border_width(width))
712 }
713
714 pub fn radius(self, radius: impl Into<MetricRef>) -> Self {
715 self.style_with(|c| c.radius(radius))
716 }
717
718 pub fn bg(self, color: ColorRef) -> Self {
719 self.style_with(|c| c.bg(color))
720 }
721
722 pub fn border_color(self, color: ColorRef) -> Self {
723 self.style_with(|c| c.border_color(color))
724 }
725
726 pub fn text_color(self, color: ColorRef) -> Self {
727 self.style_with(|c| c.text_color(color))
728 }
729
730 forward_style_noargs!(
731 px_0, px_1, px_0p5, px_1p5, px_2, px_2p5, px_3, px_4, py_0, py_1, py_0p5, py_1p5, py_2,
732 py_2p5, py_3, py_4, p_0, p_1, p_0p5, p_1p5, p_2, p_2p5, p_3, p_4, rounded_md, border_1,
733 );
734}
735
736impl<T: UiSupportsLayout> UiBuilder<T> {
737 pub fn insets(self, insets: impl Into<Edges4<SignedMetricRef>>) -> Self {
738 self.layout_with(|mut l| {
739 let Edges4 {
740 top,
741 right,
742 bottom,
743 left,
744 } = insets.into();
745 let mut inset = l.inset.unwrap_or_default();
746 inset.top = Some(crate::style::InsetEdgeRefinement::Px(top));
747 inset.right = Some(crate::style::InsetEdgeRefinement::Px(right));
748 inset.bottom = Some(crate::style::InsetEdgeRefinement::Px(bottom));
749 inset.left = Some(crate::style::InsetEdgeRefinement::Px(left));
750 l.inset = Some(inset);
751 l
752 })
753 }
754
755 pub fn margins(self, margins: impl Into<Edges4<MarginEdge>>) -> Self {
756 self.layout_with(|mut l| {
757 let Edges4 {
758 top,
759 right,
760 bottom,
761 left,
762 } = margins.into();
763 let mut margin = l.margin.unwrap_or_default();
764 margin.top = Some(top.into());
765 margin.right = Some(right.into());
766 margin.bottom = Some(bottom.into());
767 margin.left = Some(left.into());
768 l.margin = Some(margin);
769 l
770 })
771 }
772
773 pub fn aspect_ratio(self, ratio: f32) -> Self {
774 self.layout_with(|l| l.aspect_ratio(ratio))
775 }
776
777 pub fn inset(self, space: Space) -> Self {
778 self.layout_with(|l| l.inset(space))
779 }
780
781 pub fn inset_px(self, px: Px) -> Self {
782 self.layout_with(|l| l.inset_px(px))
783 }
784
785 pub fn top(self, space: Space) -> Self {
786 self.layout_with(|l| l.top(space))
787 }
788
789 pub fn top_px(self, px: Px) -> Self {
790 self.layout_with(|l| l.top_px(px))
791 }
792
793 pub fn top_neg(self, space: Space) -> Self {
794 self.layout_with(|l| l.top_neg(space))
795 }
796
797 pub fn right(self, space: Space) -> Self {
798 self.layout_with(|l| l.right(space))
799 }
800
801 pub fn right_px(self, px: Px) -> Self {
802 self.layout_with(|l| l.right_px(px))
803 }
804
805 pub fn right_neg(self, space: Space) -> Self {
806 self.layout_with(|l| l.right_neg(space))
807 }
808
809 pub fn bottom(self, space: Space) -> Self {
810 self.layout_with(|l| l.bottom(space))
811 }
812
813 pub fn bottom_px(self, px: Px) -> Self {
814 self.layout_with(|l| l.bottom_px(px))
815 }
816
817 pub fn bottom_neg(self, space: Space) -> Self {
818 self.layout_with(|l| l.bottom_neg(space))
819 }
820
821 pub fn left(self, space: Space) -> Self {
822 self.layout_with(|l| l.left(space))
823 }
824
825 pub fn left_px(self, px: Px) -> Self {
826 self.layout_with(|l| l.left_px(px))
827 }
828
829 pub fn left_neg(self, space: Space) -> Self {
830 self.layout_with(|l| l.left_neg(space))
831 }
832
833 pub fn m(self, space: Space) -> Self {
834 self.layout_with(|l| l.m(space))
835 }
836
837 pub fn m_px(self, px: Px) -> Self {
838 self.layout_with(|l| l.m_px(px))
839 }
840
841 pub fn m_neg(self, space: Space) -> Self {
842 self.layout_with(|l| l.m_neg(space))
843 }
844
845 pub fn mx(self, space: Space) -> Self {
846 self.layout_with(|l| l.mx(space))
847 }
848
849 pub fn mx_px(self, px: Px) -> Self {
850 self.layout_with(|l| l.mx_px(px))
851 }
852
853 pub fn mx_neg(self, space: Space) -> Self {
854 self.layout_with(|l| l.mx_neg(space))
855 }
856
857 pub fn my(self, space: Space) -> Self {
858 self.layout_with(|l| l.my(space))
859 }
860
861 pub fn my_px(self, px: Px) -> Self {
862 self.layout_with(|l| l.my_px(px))
863 }
864
865 pub fn my_neg(self, space: Space) -> Self {
866 self.layout_with(|l| l.my_neg(space))
867 }
868
869 pub fn mt(self, space: Space) -> Self {
870 self.layout_with(|l| l.mt(space))
871 }
872
873 pub fn mt_px(self, px: Px) -> Self {
874 self.layout_with(|l| l.mt_px(px))
875 }
876
877 pub fn mt_neg(self, space: Space) -> Self {
878 self.layout_with(|l| l.mt_neg(space))
879 }
880
881 pub fn mr(self, space: Space) -> Self {
882 self.layout_with(|l| l.mr(space))
883 }
884
885 pub fn mr_px(self, px: Px) -> Self {
886 self.layout_with(|l| l.mr_px(px))
887 }
888
889 pub fn mr_neg(self, space: Space) -> Self {
890 self.layout_with(|l| l.mr_neg(space))
891 }
892
893 pub fn mb(self, space: Space) -> Self {
894 self.layout_with(|l| l.mb(space))
895 }
896
897 pub fn mb_px(self, px: Px) -> Self {
898 self.layout_with(|l| l.mb_px(px))
899 }
900
901 pub fn mb_neg(self, space: Space) -> Self {
902 self.layout_with(|l| l.mb_neg(space))
903 }
904
905 pub fn ml(self, space: Space) -> Self {
906 self.layout_with(|l| l.ml(space))
907 }
908
909 pub fn ml_px(self, px: Px) -> Self {
910 self.layout_with(|l| l.ml_px(px))
911 }
912
913 pub fn ml_neg(self, space: Space) -> Self {
914 self.layout_with(|l| l.ml_neg(space))
915 }
916
917 pub fn min_w(self, width: impl Into<MetricRef>) -> Self {
918 self.layout_with(|l| l.min_w(width))
919 }
920
921 pub fn min_w_space(self, width: Space) -> Self {
922 self.layout_with(|l| l.min_w_space(width))
923 }
924
925 pub fn min_h(self, height: impl Into<MetricRef>) -> Self {
926 self.layout_with(|l| l.min_h(height))
927 }
928
929 pub fn min_h_space(self, height: Space) -> Self {
930 self.layout_with(|l| l.min_h_space(height))
931 }
932
933 pub fn w(self, width: LengthRefinement) -> Self {
934 self.layout_with(|l| l.w(width))
935 }
936
937 pub fn h(self, height: LengthRefinement) -> Self {
938 self.layout_with(|l| l.h(height))
939 }
940
941 pub fn w_px(self, width: impl Into<MetricRef>) -> Self {
942 self.layout_with(|l| l.w_px(width))
943 }
944
945 pub fn w_space(self, width: Space) -> Self {
946 self.layout_with(|l| l.w_space(width))
947 }
948
949 pub fn h_px(self, height: impl Into<MetricRef>) -> Self {
950 self.layout_with(|l| l.h_px(height))
951 }
952
953 pub fn h_space(self, height: Space) -> Self {
954 self.layout_with(|l| l.h_space(height))
955 }
956
957 pub fn max_w(self, width: impl Into<MetricRef>) -> Self {
958 self.layout_with(|l| l.max_w(width))
959 }
960
961 pub fn max_w_space(self, width: Space) -> Self {
962 self.layout_with(|l| l.max_w_space(width))
963 }
964
965 pub fn max_h(self, height: impl Into<MetricRef>) -> Self {
966 self.layout_with(|l| l.max_h(height))
967 }
968
969 pub fn max_h_space(self, height: Space) -> Self {
970 self.layout_with(|l| l.max_h_space(height))
971 }
972
973 pub fn basis(self, basis: LengthRefinement) -> Self {
974 self.layout_with(|l| l.basis(basis))
975 }
976
977 pub fn basis_px(self, basis: impl Into<MetricRef>) -> Self {
978 self.layout_with(|l| l.basis_px(basis))
979 }
980
981 pub fn flex_grow(self, grow: f32) -> Self {
982 self.layout_with(|l| l.flex_grow(grow))
983 }
984
985 pub fn flex_shrink(self, shrink: f32) -> Self {
986 self.layout_with(|l| l.flex_shrink(shrink))
987 }
988
989 pub fn align_self(self, align: CrossAlign) -> Self {
990 self.layout_with(|l| l.align_self(align))
991 }
992
993 pub fn justify_self(self, align: CrossAlign) -> Self {
994 self.layout_with(|l| l.justify_self(align))
995 }
996
997 forward_layout_noargs!(
998 relative,
999 absolute,
1000 overflow_hidden,
1001 overflow_visible,
1002 overflow_x_hidden,
1003 overflow_y_hidden,
1004 m_auto,
1005 mx_auto,
1006 my_auto,
1007 mt_auto,
1008 mr_auto,
1009 mb_auto,
1010 ml_auto,
1011 min_w_0,
1012 w_full,
1013 h_full,
1014 size_full,
1015 basis_0,
1016 flex_shrink_0,
1017 flex_1,
1018 flex_none,
1019 self_start,
1020 self_center,
1021 self_end,
1022 self_stretch,
1023 justify_self_start,
1024 justify_self_center,
1025 justify_self_end,
1026 justify_self_stretch,
1027 w_0,
1028 h_0,
1029 min_h_0,
1030 max_w_0,
1031 max_h_0,
1032 w_0p5,
1033 h_0p5,
1034 min_w_0p5,
1035 min_h_0p5,
1036 max_w_0p5,
1037 max_h_0p5,
1038 w_1,
1039 h_1,
1040 min_w_1,
1041 min_h_1,
1042 max_w_1,
1043 max_h_1,
1044 w_1p5,
1045 h_1p5,
1046 min_w_1p5,
1047 min_h_1p5,
1048 max_w_1p5,
1049 max_h_1p5,
1050 w_2,
1051 h_2,
1052 min_w_2,
1053 min_h_2,
1054 max_w_2,
1055 max_h_2,
1056 w_2p5,
1057 h_2p5,
1058 min_w_2p5,
1059 min_h_2p5,
1060 max_w_2p5,
1061 max_h_2p5,
1062 w_3,
1063 h_3,
1064 min_w_3,
1065 min_h_3,
1066 max_w_3,
1067 max_h_3,
1068 w_3p5,
1069 h_3p5,
1070 min_w_3p5,
1071 min_h_3p5,
1072 max_w_3p5,
1073 max_h_3p5,
1074 w_4,
1075 h_4,
1076 min_w_4,
1077 min_h_4,
1078 max_w_4,
1079 max_h_4,
1080 w_5,
1081 h_5,
1082 min_w_5,
1083 min_h_5,
1084 max_w_5,
1085 max_h_5,
1086 w_6,
1087 h_6,
1088 min_w_6,
1089 min_h_6,
1090 max_w_6,
1091 max_h_6,
1092 w_8,
1093 h_8,
1094 min_w_8,
1095 min_h_8,
1096 max_w_8,
1097 max_h_8,
1098 w_10,
1099 h_10,
1100 min_w_10,
1101 min_h_10,
1102 max_w_10,
1103 max_h_10,
1104 w_11,
1105 h_11,
1106 min_w_11,
1107 min_h_11,
1108 max_w_11,
1109 max_h_11,
1110 );
1111}
1112
1113impl<T: UiPatchTarget> UiBuilder<T> {
1114 pub fn build(self) -> T {
1115 self.inner.apply_ui_patch(self.patch)
1116 }
1117}
1118
1119impl<H, F> UiBuilder<crate::ui::FlexBox<H, F>> {
1120 pub fn gap(mut self, gap: impl Into<MetricRef>) -> Self {
1121 self.inner.gap = gap.into();
1122 self.inner.gap_length = None;
1123 self
1124 }
1125
1126 pub fn gap_px(self, gap: Px) -> Self {
1127 self.gap(gap)
1128 }
1129
1130 pub fn gap_metric(self, gap: MetricRef) -> Self {
1131 self.gap(gap)
1132 }
1133
1134 pub fn gap_fraction(mut self, fraction: f32) -> Self {
1135 self.inner.gap_length = Some(LengthRefinement::Fraction(fraction));
1136 self
1137 }
1138
1139 pub fn gap_percent(self, percent: f32) -> Self {
1140 self.gap_fraction(percent / 100.0)
1141 }
1142
1143 pub fn gap_full(mut self) -> Self {
1144 self.inner.gap_length = Some(LengthRefinement::Fill);
1145 self
1146 }
1147
1148 pub fn justify(mut self, justify: Justify) -> Self {
1149 self.inner.justify = justify;
1150 self
1151 }
1152
1153 pub fn justify_start(self) -> Self {
1154 self.justify(Justify::Start)
1155 }
1156
1157 pub fn justify_center(self) -> Self {
1158 self.justify(Justify::Center)
1159 }
1160
1161 pub fn justify_end(self) -> Self {
1162 self.justify(Justify::End)
1163 }
1164
1165 pub fn justify_between(self) -> Self {
1166 self.justify(Justify::Between)
1167 }
1168
1169 pub fn items(mut self, items: Items) -> Self {
1170 self.inner.items = items;
1171 self
1172 }
1173
1174 pub fn items_start(self) -> Self {
1175 self.items(Items::Start)
1176 }
1177
1178 pub fn items_center(self) -> Self {
1179 self.items(Items::Center)
1180 }
1181
1182 pub fn items_end(self) -> Self {
1183 self.items(Items::End)
1184 }
1185
1186 pub fn items_stretch(self) -> Self {
1187 self.items(Items::Stretch)
1188 }
1189
1190 pub fn wrap(mut self) -> Self {
1191 self.inner.wrap = true;
1192 self
1193 }
1194
1195 pub fn no_wrap(mut self) -> Self {
1196 self.inner.wrap = false;
1197 self
1198 }
1199}
1200
1201impl<H, B> UiBuilder<crate::ui::FlexBoxBuild<H, B>> {
1202 pub fn gap(mut self, gap: impl Into<MetricRef>) -> Self {
1203 self.inner.gap = gap.into();
1204 self.inner.gap_length = None;
1205 self
1206 }
1207
1208 pub fn gap_px(self, gap: Px) -> Self {
1209 self.gap(gap)
1210 }
1211
1212 pub fn gap_metric(self, gap: MetricRef) -> Self {
1213 self.gap(gap)
1214 }
1215
1216 pub fn gap_fraction(mut self, fraction: f32) -> Self {
1217 self.inner.gap_length = Some(LengthRefinement::Fraction(fraction));
1218 self
1219 }
1220
1221 pub fn gap_percent(self, percent: f32) -> Self {
1222 self.gap_fraction(percent / 100.0)
1223 }
1224
1225 pub fn gap_full(mut self) -> Self {
1226 self.inner.gap_length = Some(LengthRefinement::Fill);
1227 self
1228 }
1229
1230 pub fn justify(mut self, justify: Justify) -> Self {
1231 self.inner.justify = justify;
1232 self
1233 }
1234
1235 pub fn justify_start(self) -> Self {
1236 self.justify(Justify::Start)
1237 }
1238
1239 pub fn justify_center(self) -> Self {
1240 self.justify(Justify::Center)
1241 }
1242
1243 pub fn justify_end(self) -> Self {
1244 self.justify(Justify::End)
1245 }
1246
1247 pub fn justify_between(self) -> Self {
1248 self.justify(Justify::Between)
1249 }
1250
1251 pub fn items(mut self, items: Items) -> Self {
1252 self.inner.items = items;
1253 self
1254 }
1255
1256 pub fn items_start(self) -> Self {
1257 self.items(Items::Start)
1258 }
1259
1260 pub fn items_center(self) -> Self {
1261 self.items(Items::Center)
1262 }
1263
1264 pub fn items_end(self) -> Self {
1265 self.items(Items::End)
1266 }
1267
1268 pub fn items_stretch(self) -> Self {
1269 self.items(Items::Stretch)
1270 }
1271
1272 pub fn wrap(mut self) -> Self {
1273 self.inner.wrap = true;
1274 self
1275 }
1276
1277 pub fn no_wrap(mut self) -> Self {
1278 self.inner.wrap = false;
1279 self
1280 }
1281}
1282
1283impl<H, F> UiBuilder<crate::ui::ScrollAreaBox<H, F>> {
1284 pub fn axis(mut self, axis: ScrollAxis) -> Self {
1285 self.inner.axis = axis;
1286 self
1287 }
1288
1289 pub fn show_scrollbar_x(mut self, show: bool) -> Self {
1290 self.inner.show_scrollbar_x = show;
1291 self
1292 }
1293
1294 pub fn show_scrollbar_y(mut self, show: bool) -> Self {
1295 self.inner.show_scrollbar_y = show;
1296 self
1297 }
1298
1299 pub fn show_scrollbars(self, x: bool, y: bool) -> Self {
1300 self.show_scrollbar_x(x).show_scrollbar_y(y)
1301 }
1302
1303 pub fn handle(mut self, handle: ScrollHandle) -> Self {
1304 self.inner.handle = Some(handle);
1305 self
1306 }
1307}
1308
1309impl<H, B> UiBuilder<crate::ui::ScrollAreaBoxBuild<H, B>> {
1310 pub fn axis(mut self, axis: ScrollAxis) -> Self {
1311 self.inner.axis = axis;
1312 self
1313 }
1314
1315 pub fn show_scrollbar_x(mut self, show: bool) -> Self {
1316 self.inner.show_scrollbar_x = show;
1317 self
1318 }
1319
1320 pub fn show_scrollbar_y(mut self, show: bool) -> Self {
1321 self.inner.show_scrollbar_y = show;
1322 self
1323 }
1324
1325 pub fn show_scrollbars(self, x: bool, y: bool) -> Self {
1326 self.show_scrollbar_x(x).show_scrollbar_y(y)
1327 }
1328
1329 pub fn handle(mut self, handle: ScrollHandle) -> Self {
1330 self.inner.handle = Some(handle);
1331 self
1332 }
1333}
1334
1335#[track_caller]
1336fn render_ui_builder_target_into_element<H: UiHost, T>(
1337 value: T,
1338 cx: &mut ElementContext<'_, H>,
1339) -> AnyElement
1340where
1341 T: IntoUiElement<H>,
1342{
1343 IntoUiElement::into_element(value, cx)
1344}
1345
1346#[track_caller]
1347fn finalize_ui_builder_element<H: UiHost, T: UiPatchTarget>(
1348 builder: UiBuilder<T>,
1349 cx: &mut ElementContext<'_, H>,
1350 render: impl FnOnce(T, &mut ElementContext<'_, H>) -> AnyElement,
1351) -> AnyElement {
1352 let UiBuilder {
1353 inner,
1354 patch,
1355 semantics,
1356 key_context,
1357 } = builder;
1358 let builder = UiBuilder {
1359 inner,
1360 patch,
1361 semantics: None,
1362 key_context: None,
1363 };
1364 let el = render(builder.build(), cx);
1365 let el = match semantics {
1366 Some(decoration) => el.attach_semantics(decoration),
1367 None => el,
1368 };
1369 match key_context {
1370 Some(key_context) => el.key_context(key_context),
1371 None => el,
1372 }
1373}
1374
1375impl<H: UiHost, T> IntoUiElement<H> for UiBuilder<T>
1376where
1377 T: UiPatchTarget + IntoUiElement<H>,
1378{
1379 #[track_caller]
1380 fn into_element(self, cx: &mut ElementContext<'_, H>) -> AnyElement {
1381 let loc = Location::caller();
1382 finalize_ui_builder_element(self, cx, move |built, cx| {
1383 cx.scope_at(loc, |cx| render_ui_builder_target_into_element(built, cx))
1384 })
1385 }
1386}
1387
1388impl<T: UiPatchTarget> UiBuilder<T> {
1389 #[track_caller]
1390 pub fn into_element<H: UiHost>(self, cx: &mut ElementContext<'_, H>) -> AnyElement
1391 where
1392 T: IntoUiElement<H>,
1393 {
1394 let loc = Location::caller();
1395 finalize_ui_builder_element(self, cx, move |built, cx| {
1396 cx.scope_at(loc, |cx| render_ui_builder_target_into_element(built, cx))
1397 })
1398 }
1399}
1400
1401impl<H: UiHost, B> IntoUiElement<H> for UiBuilder<crate::ui::ContainerPropsBoxBuild<H, B>>
1402where
1403 B: FnOnce(&mut ElementContext<'_, H>, &mut Vec<AnyElement>),
1404{
1405 #[track_caller]
1406 fn into_element(self, cx: &mut ElementContext<'_, H>) -> AnyElement {
1407 UiBuilder::<crate::ui::ContainerPropsBoxBuild<H, B>>::into_element(self, cx)
1408 }
1409}
1410
1411impl<H: UiHost, B> UiBuilder<crate::ui::ContainerPropsBoxBuild<H, B>>
1412where
1413 B: FnOnce(&mut ElementContext<'_, H>, &mut Vec<AnyElement>),
1414{
1415 #[track_caller]
1416 pub fn into_element(self, cx: &mut ElementContext<'_, H>) -> AnyElement {
1417 let UiBuilder {
1418 inner,
1419 patch: _,
1420 semantics,
1421 key_context,
1422 } = self;
1423 let el = inner.into_element(cx);
1424 let el = match semantics {
1425 Some(decoration) => el.attach_semantics(decoration),
1426 None => el,
1427 };
1428 match key_context {
1429 Some(key_context) => el.key_context(key_context),
1430 None => el,
1431 }
1432 }
1433}
1434
1435impl<H: UiHost, F, I> IntoUiElement<H> for UiBuilder<crate::ui::ContainerPropsBox<H, F>>
1436where
1437 F: FnOnce(&mut ElementContext<'_, H>) -> I,
1438 I: IntoIterator,
1439 I::Item: crate::IntoUiElement<H>,
1440{
1441 #[track_caller]
1442 fn into_element(self, cx: &mut ElementContext<'_, H>) -> AnyElement {
1443 UiBuilder::<crate::ui::ContainerPropsBox<H, F>>::into_element(self, cx)
1444 }
1445}
1446
1447impl<H: UiHost, F, I> UiBuilder<crate::ui::ContainerPropsBox<H, F>>
1448where
1449 F: FnOnce(&mut ElementContext<'_, H>) -> I,
1450 I: IntoIterator,
1451 I::Item: crate::IntoUiElement<H>,
1452{
1453 #[track_caller]
1454 pub fn into_element(self, cx: &mut ElementContext<'_, H>) -> AnyElement {
1455 let UiBuilder {
1456 inner,
1457 patch: _,
1458 semantics,
1459 key_context,
1460 } = self;
1461 let el = inner.into_element(cx);
1462 let el = match semantics {
1463 Some(decoration) => el.attach_semantics(decoration),
1464 None => el,
1465 };
1466 match key_context {
1467 Some(key_context) => el.key_context(key_context),
1468 None => el,
1469 }
1470 }
1471}
1472
1473pub trait UiExt: UiPatchTarget + Sized {
1502 fn ui(self) -> UiBuilder<Self> {
1503 UiBuilder::new(self)
1504 }
1505}
1506
1507impl<T: UiPatchTarget> UiExt for T {}
1508
1509#[cfg(test)]
1510mod tests {
1511 use super::*;
1512 use crate::{LengthRefinement, MetricRef};
1513 use fret_core::Axis;
1514 use fret_core::Color;
1515 use fret_core::Px;
1516
1517 #[derive(Debug, Default, Clone)]
1518 struct Dummy {
1519 chrome: ChromeRefinement,
1520 layout: LayoutRefinement,
1521 }
1522
1523 impl UiPatchTarget for Dummy {
1524 fn apply_ui_patch(mut self, patch: UiPatch) -> Self {
1525 self.chrome = self.chrome.merge(patch.chrome);
1526 self.layout = self.layout.merge(patch.layout);
1527 self
1528 }
1529 }
1530
1531 impl UiSupportsChrome for Dummy {}
1532 impl UiSupportsLayout for Dummy {}
1533
1534 #[test]
1535 fn ui_builder_merges_chrome_and_layout() {
1536 let dummy = Dummy::default()
1537 .ui()
1538 .px_3()
1539 .py_2()
1540 .border_1()
1541 .rounded_md()
1542 .w_full()
1543 .build();
1544
1545 let padding = dummy.chrome.padding.expect("expected padding refinement");
1546 match padding.left {
1547 Some(MetricRef::Token { key, .. }) => assert_eq!(key, Space::N3.token_key()),
1548 _ => panic!("expected left padding token"),
1549 }
1550 match padding.top {
1551 Some(MetricRef::Token { key, .. }) => assert_eq!(key, Space::N2.token_key()),
1552 _ => panic!("expected top padding token"),
1553 }
1554
1555 assert!(dummy.chrome.border_width.is_some());
1556 assert!(dummy.chrome.radius.is_some());
1557
1558 let size = dummy.layout.size.expect("expected size refinement");
1559 match size.width {
1560 Some(LengthRefinement::Fill) => {}
1561 other => panic!("expected width Fill, got {other:?}"),
1562 }
1563 assert!(size.min_width.is_none());
1564 assert!(size.min_height.is_none());
1565 }
1566
1567 #[test]
1568 fn ui_builder_allows_px_and_space_mix() {
1569 let dummy = Dummy::default()
1570 .ui()
1571 .style_with(|mut c| {
1572 c.min_height = Some(Px(40.0).into());
1573 c
1574 })
1575 .build();
1576 assert!(dummy.chrome.min_height.is_some());
1577 }
1578
1579 #[test]
1580 fn ui_builder_edges4_helpers_write_fields() {
1581 let dummy = Dummy::default()
1582 .ui()
1583 .paddings(Edges4::trbl(Space::N1, Space::N2, Space::N3, Space::N4))
1584 .margins(Edges4::trbl(
1585 MarginEdge::auto(),
1586 Space::N2.into(),
1587 Space::N3.into(),
1588 Space::N4.into(),
1589 ))
1590 .insets(Edges4::all(Space::N1).neg())
1591 .focused_border()
1592 .corner_radii(Corners4::tltrbrbl(
1593 Radius::Sm,
1594 Radius::Md,
1595 Radius::Lg,
1596 Radius::Full,
1597 ))
1598 .rounded_tl(Radius::Lg)
1599 .shadow_md()
1600 .debug_border_primary()
1601 .debug_border_destructive()
1602 .build();
1603
1604 let padding = dummy.chrome.padding.expect("expected padding refinement");
1605 match padding.top {
1606 Some(MetricRef::Token { key, .. }) => assert_eq!(key, Space::N1.token_key()),
1607 other => panic!("expected top padding token, got {other:?}"),
1608 }
1609 match padding.right {
1610 Some(MetricRef::Token { key, .. }) => assert_eq!(key, Space::N2.token_key()),
1611 other => panic!("expected right padding token, got {other:?}"),
1612 }
1613 match padding.bottom {
1614 Some(MetricRef::Token { key, .. }) => assert_eq!(key, Space::N3.token_key()),
1615 other => panic!("expected bottom padding token, got {other:?}"),
1616 }
1617 match padding.left {
1618 Some(MetricRef::Token { key, .. }) => assert_eq!(key, Space::N4.token_key()),
1619 other => panic!("expected left padding token, got {other:?}"),
1620 }
1621
1622 let margin = dummy.layout.margin.expect("expected margin refinement");
1623 assert!(matches!(
1624 margin.top,
1625 Some(crate::style::MarginEdgeRefinement::Auto)
1626 ));
1627 match margin.right {
1628 Some(crate::style::MarginEdgeRefinement::Px(SignedMetricRef::Pos(
1629 MetricRef::Token { key, .. },
1630 ))) => assert_eq!(key, Space::N2.token_key()),
1631 other => panic!("expected right margin token, got {other:?}"),
1632 }
1633
1634 let inset = dummy.layout.inset.expect("expected inset refinement");
1635 match inset.left {
1636 Some(crate::style::InsetEdgeRefinement::Px(SignedMetricRef::Neg(
1637 MetricRef::Token { key, .. },
1638 ))) => {
1639 assert_eq!(key, Space::N1.token_key())
1640 }
1641 other => panic!("expected left inset negative token, got {other:?}"),
1642 }
1643
1644 match dummy.chrome.border_color {
1645 Some(ColorRef::Token { key, .. }) => assert_eq!(key, "destructive"),
1646 other => panic!("expected debug_border_destructive to set border_color, got {other:?}"),
1647 }
1648
1649 assert_eq!(dummy.chrome.shadow, Some(crate::style::ShadowPreset::Md));
1650
1651 let radii = dummy
1652 .chrome
1653 .corner_radii
1654 .expect("expected corner radii refinement");
1655 match radii.top_left {
1656 Some(MetricRef::Token { key, .. }) => assert_eq!(key, "component.radius.lg"),
1657 other => panic!("expected top_left token radius, got {other:?}"),
1658 }
1659 }
1660
1661 #[test]
1662 fn ui_builder_forwards_full_vocabulary_smoke() {
1663 let _ = Dummy::default()
1664 .ui()
1665 .paddings(Edges4::all(Space::N1))
1667 .px(Space::N1)
1668 .py(Space::N2)
1669 .p(Space::N3)
1670 .pt(Space::N0p5)
1671 .pr(Space::N1p5)
1672 .pb(Space::N2p5)
1673 .pl(Space::N3p5)
1674 .rounded(Radius::Full)
1675 .bg(ColorRef::Color(Color {
1676 r: 0.1,
1677 g: 0.2,
1678 b: 0.3,
1679 a: 1.0,
1680 }))
1681 .border_color(ColorRef::Color(Color {
1682 r: 0.3,
1683 g: 0.2,
1684 b: 0.1,
1685 a: 1.0,
1686 }))
1687 .text_color(ColorRef::Color(Color {
1688 r: 0.9,
1689 g: 0.9,
1690 b: 0.9,
1691 a: 1.0,
1692 }))
1693 .px_0()
1694 .px_1()
1695 .px_0p5()
1696 .px_1p5()
1697 .px_2()
1698 .px_2p5()
1699 .px_3()
1700 .px_4()
1701 .py_0()
1702 .py_1()
1703 .py_0p5()
1704 .py_1p5()
1705 .py_2()
1706 .py_2p5()
1707 .py_3()
1708 .py_4()
1709 .p_0()
1710 .p_1()
1711 .p_0p5()
1712 .p_1p5()
1713 .p_2()
1714 .p_2p5()
1715 .p_3()
1716 .p_4()
1717 .rounded_md()
1718 .border_1()
1719 .aspect_ratio(1.0)
1721 .relative()
1722 .absolute()
1723 .overflow_hidden()
1724 .overflow_visible()
1725 .overflow_x_hidden()
1726 .overflow_y_hidden()
1727 .margins(Edges4::all(Space::N1))
1728 .insets(Edges4::all(Space::N1))
1729 .inset(Space::N2)
1730 .top(Space::N3)
1731 .top_neg(Space::N3)
1732 .right(Space::N3)
1733 .right_neg(Space::N3)
1734 .bottom(Space::N3)
1735 .bottom_neg(Space::N3)
1736 .left(Space::N3)
1737 .left_neg(Space::N3)
1738 .m(Space::N2)
1739 .m_neg(Space::N2)
1740 .m_auto()
1741 .mx(Space::N2)
1742 .mx_neg(Space::N2)
1743 .mx_auto()
1744 .my(Space::N2)
1745 .my_neg(Space::N2)
1746 .my_auto()
1747 .mt(Space::N2)
1748 .mt_neg(Space::N2)
1749 .mt_auto()
1750 .mr(Space::N2)
1751 .mr_neg(Space::N2)
1752 .mr_auto()
1753 .mb(Space::N2)
1754 .mb_neg(Space::N2)
1755 .mb_auto()
1756 .ml(Space::N2)
1757 .ml_neg(Space::N2)
1758 .ml_auto()
1759 .min_w(Px(10.0))
1760 .min_w_space(Space::N1)
1761 .min_h(Px(10.0))
1762 .min_h_space(Space::N1)
1763 .min_w_0()
1764 .w(LengthRefinement::Fill)
1765 .h(LengthRefinement::Auto)
1766 .w_px(Px(10.0))
1767 .w_space(Space::N10)
1768 .h_px(Px(11.0))
1769 .h_space(Space::N11)
1770 .w_full()
1771 .h_full()
1772 .size_full()
1773 .max_w(Px(10.0))
1774 .max_w_space(Space::N1)
1775 .max_h(Px(10.0))
1776 .max_h_space(Space::N1)
1777 .basis(LengthRefinement::Auto)
1778 .basis_px(Px(10.0))
1779 .basis_0()
1780 .flex_grow(1.0)
1781 .flex_shrink(1.0)
1782 .flex_shrink_0()
1783 .flex_1()
1784 .flex_none()
1785 .w_0()
1787 .h_0()
1788 .min_h_0()
1789 .max_w_0()
1790 .max_h_0()
1791 .w_0p5()
1792 .h_0p5()
1793 .min_w_0p5()
1794 .min_h_0p5()
1795 .max_w_0p5()
1796 .max_h_0p5()
1797 .w_1()
1798 .h_1()
1799 .min_w_1()
1800 .min_h_1()
1801 .max_w_1()
1802 .max_h_1()
1803 .w_1p5()
1804 .h_1p5()
1805 .min_w_1p5()
1806 .min_h_1p5()
1807 .max_w_1p5()
1808 .max_h_1p5()
1809 .w_2()
1810 .h_2()
1811 .min_w_2()
1812 .min_h_2()
1813 .max_w_2()
1814 .max_h_2()
1815 .w_2p5()
1816 .h_2p5()
1817 .min_w_2p5()
1818 .min_h_2p5()
1819 .max_w_2p5()
1820 .max_h_2p5()
1821 .w_3()
1822 .h_3()
1823 .min_w_3()
1824 .min_h_3()
1825 .max_w_3()
1826 .max_h_3()
1827 .w_3p5()
1828 .h_3p5()
1829 .min_w_3p5()
1830 .min_h_3p5()
1831 .max_w_3p5()
1832 .max_h_3p5()
1833 .w_4()
1834 .h_4()
1835 .min_w_4()
1836 .min_h_4()
1837 .max_w_4()
1838 .max_h_4()
1839 .w_5()
1840 .h_5()
1841 .min_w_5()
1842 .min_h_5()
1843 .max_w_5()
1844 .max_h_5()
1845 .w_6()
1846 .h_6()
1847 .min_w_6()
1848 .min_h_6()
1849 .max_w_6()
1850 .max_h_6()
1851 .w_8()
1852 .h_8()
1853 .min_w_8()
1854 .min_h_8()
1855 .max_w_8()
1856 .max_h_8()
1857 .w_10()
1858 .h_10()
1859 .min_w_10()
1860 .min_h_10()
1861 .max_w_10()
1862 .max_h_10()
1863 .w_11()
1864 .h_11()
1865 .min_w_11()
1866 .min_h_11()
1867 .max_w_11()
1868 .max_h_11()
1869 .build();
1870 }
1871
1872 #[test]
1873 fn ui_flex_box_builder_records_gap_and_alignment() {
1874 let flex = crate::ui::FlexBox::<(), ()>::new(Axis::Horizontal, ())
1875 .ui()
1876 .gap(Space::N2)
1877 .justify_between()
1878 .items_center()
1879 .wrap()
1880 .build();
1881
1882 assert!(matches!(flex.gap, MetricRef::Token { key, .. } if key == Space::N2.token_key()));
1883 assert_eq!(flex.justify, Justify::Between);
1884 assert_eq!(flex.items, Items::Center);
1885 assert!(flex.wrap);
1886 }
1887}