1use frus_core::{Alignment, Rect, Scene, Size};
16use frus_layout::{Dimension, Style};
17
18use crate::interaction::Status;
19use crate::theme::Theme;
20use crate::widget::Widget;
21
22pub struct SizedBox<Msg = crate::callback::Callback> {
28 width: Dimension,
29 height: Dimension,
30 children: Vec<Box<dyn Widget<Msg>>>,
31}
32
33impl<Msg> SizedBox<Msg> {
34 pub fn empty() -> Self {
36 Self {
37 width: Dimension::Auto,
38 height: Dimension::Auto,
39 children: Vec::new(),
40 }
41 }
42
43 pub fn new(child: impl Widget<Msg> + 'static) -> Self {
45 let mut boxed = Self::empty();
46 boxed.children.push(Box::new(child));
47 boxed
48 }
49
50 pub fn square(side: f32, child: impl Widget<Msg> + 'static) -> Self {
52 Self::new(child).width(side).height(side)
53 }
54
55 pub fn expand(child: impl Widget<Msg> + 'static) -> Self {
58 Self::new(child)
59 .width_dimension(Dimension::Percent(1.0))
60 .height_dimension(Dimension::Percent(1.0))
61 }
62
63 pub fn shrink(child: impl Widget<Msg> + 'static) -> Self {
67 Self::new(child)
68 }
69
70 pub fn width(self, width: f32) -> Self {
72 self.width_dimension(Dimension::Length(width))
73 }
74
75 pub fn height(self, height: f32) -> Self {
77 self.height_dimension(Dimension::Length(height))
78 }
79
80 pub fn width_fraction(self, fraction: f32) -> Self {
82 self.width_dimension(Dimension::Percent(fraction))
83 }
84
85 pub fn height_fraction(self, fraction: f32) -> Self {
87 self.height_dimension(Dimension::Percent(fraction))
88 }
89
90 fn width_dimension(mut self, width: Dimension) -> Self {
91 self.width = width;
92 self
93 }
94
95 fn height_dimension(mut self, height: Dimension) -> Self {
96 self.height = height;
97 self
98 }
99
100 pub fn child(mut self, child: impl Widget<Msg> + 'static) -> Self {
102 self.children.clear();
103 self.children.push(Box::new(child));
104 self
105 }
106}
107
108impl<Msg: Clone> Widget<Msg> for SizedBox<Msg> {
109 fn style(&self) -> Style {
110 Style {
111 width: self.width,
112 height: self.height,
113 ..Default::default()
114 }
115 }
116
117 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
118 &self.children
119 }
120
121 fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {
122 }
124
125 fn on_click(&self) -> Option<Msg> {
126 None
127 }
128}
129
130pub struct ConstrainedBox<Msg = crate::callback::Callback> {
137 min_width: Dimension,
138 max_width: Dimension,
139 min_height: Dimension,
140 max_height: Dimension,
141 children: Vec<Box<dyn Widget<Msg>>>,
142}
143
144impl<Msg> ConstrainedBox<Msg> {
145 pub fn new(child: impl Widget<Msg> + 'static) -> Self {
147 Self {
148 min_width: Dimension::Auto,
149 max_width: Dimension::Auto,
150 min_height: Dimension::Auto,
151 max_height: Dimension::Auto,
152 children: vec![Box::new(child)],
153 }
154 }
155
156 pub fn new_boxed(child: Box<dyn Widget<Msg>>) -> Self {
159 Self {
160 min_width: Dimension::Auto,
161 max_width: Dimension::Auto,
162 min_height: Dimension::Auto,
163 max_height: Dimension::Auto,
164 children: vec![child],
165 }
166 }
167
168 pub fn tight(width: f32, height: f32, child: impl Widget<Msg> + 'static) -> Self {
170 Self::new(child)
171 .min_width(width)
172 .max_width(width)
173 .min_height(height)
174 .max_height(height)
175 }
176
177 pub fn loose(width: f32, height: f32, child: impl Widget<Msg> + 'static) -> Self {
179 Self::new(child).max_width(width).max_height(height)
180 }
181
182 pub fn min_width(mut self, width: f32) -> Self {
184 self.min_width = Dimension::Length(width);
185 self
186 }
187
188 pub fn max_width(mut self, width: f32) -> Self {
190 self.max_width = Dimension::Length(width);
191 self
192 }
193
194 pub fn min_height(mut self, height: f32) -> Self {
196 self.min_height = Dimension::Length(height);
197 self
198 }
199
200 pub fn max_height(mut self, height: f32) -> Self {
202 self.max_height = Dimension::Length(height);
203 self
204 }
205}
206
207impl<Msg: Clone> Widget<Msg> for ConstrainedBox<Msg> {
208 fn style(&self) -> Style {
209 Style {
210 min_width: self.min_width,
211 max_width: self.max_width,
212 min_height: self.min_height,
213 max_height: self.max_height,
214 ..Default::default()
215 }
216 }
217
218 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
219 &self.children
220 }
221
222 fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
223
224 fn on_click(&self) -> Option<Msg> {
225 None
226 }
227}
228
229#[derive(Copy, Clone, Debug, PartialEq, Eq)]
232pub enum IntrinsicAxis {
233 Width,
235 Height,
237}
238
239pub struct Intrinsic<Msg = crate::callback::Callback> {
249 axis: IntrinsicAxis,
250 step: Option<f32>,
251 children: Vec<Box<dyn Widget<Msg>>>,
252}
253
254pub type IntrinsicWidth<Msg> = Intrinsic<Msg>;
256pub type IntrinsicHeight<Msg> = Intrinsic<Msg>;
258
259impl<Msg> Intrinsic<Msg> {
260 pub fn width(child: impl Widget<Msg> + 'static) -> Self {
262 Self {
263 axis: IntrinsicAxis::Width,
264 step: None,
265 children: vec![Box::new(child)],
266 }
267 }
268
269 pub fn height(child: impl Widget<Msg> + 'static) -> Self {
271 Self {
272 axis: IntrinsicAxis::Height,
273 step: None,
274 children: vec![Box::new(child)],
275 }
276 }
277
278 pub fn step(mut self, step: f32) -> Self {
283 self.step = (step > 0.0).then_some(step);
284 self
285 }
286
287 pub fn axis(&self) -> IntrinsicAxis {
289 self.axis
290 }
291
292 pub fn quantise(&self, extent: f32) -> f32 {
294 match self.step {
295 Some(step) => (extent / step).ceil() * step,
296 None => extent,
297 }
298 }
299}
300
301impl<Msg: Clone> Widget<Msg> for Intrinsic<Msg> {
302 fn style(&self) -> Style {
303 Style::default()
306 }
307
308 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
309 &self.children
310 }
311
312 fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
313
314 fn on_click(&self) -> Option<Msg> {
315 None
316 }
317
318 fn intrinsic(&self) -> Option<(IntrinsicAxis, Option<f32>)> {
319 Some((self.axis, self.step))
320 }
321}
322
323#[derive(Copy, Clone, Debug, PartialEq)]
325pub struct Overflow {
326 pub width: Option<f32>,
328 pub height: Option<f32>,
330 pub unconstrained: bool,
333 pub alignment: Alignment,
335}
336
337pub struct OverflowBox<Msg = crate::callback::Callback> {
350 overflow: Overflow,
351 children: Vec<Box<dyn Widget<Msg>>>,
352}
353
354impl<Msg> OverflowBox<Msg> {
355 pub fn new(child: impl Widget<Msg> + 'static) -> Self {
358 Self {
359 overflow: Overflow {
360 width: None,
361 height: None,
362 unconstrained: false,
363 alignment: Alignment::CENTER,
364 },
365 children: vec![Box::new(child)],
366 }
367 }
368
369 pub fn unconstrained(child: impl Widget<Msg> + 'static) -> Self {
372 let mut boxed = Self::new(child);
373 boxed.overflow.unconstrained = true;
374 boxed
375 }
376
377 pub fn width(mut self, width: f32) -> Self {
379 self.overflow.width = Some(width);
380 self
381 }
382
383 pub fn height(mut self, height: f32) -> Self {
385 self.overflow.height = Some(height);
386 self
387 }
388
389 pub fn alignment(mut self, alignment: Alignment) -> Self {
391 self.overflow.alignment = alignment;
392 self
393 }
394}
395
396impl<Msg: Clone> Widget<Msg> for OverflowBox<Msg> {
397 fn style(&self) -> Style {
398 Style {
403 width: Dimension::Percent(1.0),
404 height: Dimension::Percent(1.0),
405 ..Default::default()
406 }
407 }
408
409 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
410 &self.children
411 }
412
413 fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
414
415 fn on_click(&self) -> Option<Msg> {
416 None
417 }
418
419 fn overflow_box(&self) -> Option<Overflow> {
420 Some(self.overflow)
421 }
422}
423
424impl Overflow {
425 pub(crate) fn child_size(&self, own: Size) -> Size {
428 Size::new(
429 self.width.unwrap_or(own.width),
430 self.height.unwrap_or(own.height),
431 )
432 }
433
434 pub(crate) fn origin(&self, own: Rect, child: Size) -> (f32, f32) {
436 (
437 own.x + (own.width - child.width) * self.alignment.fraction_x(),
438 own.y + (own.height - child.height) * self.alignment.fraction_y(),
439 )
440 }
441}
442
443pub struct SizedOverflowBox<Msg = crate::callback::Callback> {
457 width: f32,
458 height: f32,
459 overflow: Overflow,
460 children: Vec<Box<dyn Widget<Msg>>>,
461}
462
463impl<Msg> SizedOverflowBox<Msg> {
464 pub fn new(width: f32, height: f32, child: impl Widget<Msg> + 'static) -> Self {
467 Self {
468 width,
469 height,
470 overflow: Overflow {
471 width: None,
472 height: None,
473 unconstrained: true,
474 alignment: Alignment::CENTER,
475 },
476 children: vec![Box::new(child)],
477 }
478 }
479
480 pub fn child_size(mut self, width: f32, height: f32) -> Self {
482 self.overflow.unconstrained = false;
483 self.overflow.width = Some(width);
484 self.overflow.height = Some(height);
485 self
486 }
487
488 pub fn alignment(mut self, alignment: Alignment) -> Self {
490 self.overflow.alignment = alignment;
491 self
492 }
493}
494
495impl<Msg: Clone> Widget<Msg> for SizedOverflowBox<Msg> {
496 fn style(&self) -> Style {
497 Style {
498 width: Dimension::Length(self.width),
499 height: Dimension::Length(self.height),
500 ..Default::default()
501 }
502 }
503
504 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
505 &self.children
506 }
507
508 fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
509
510 fn on_click(&self) -> Option<Msg> {
511 None
512 }
513
514 fn overflow_box(&self) -> Option<Overflow> {
515 Some(self.overflow)
516 }
517
518 fn debug_name(&self) -> &'static str {
519 "SizedOverflowBox"
520 }
521}
522
523#[derive(Clone, Copy, Debug, PartialEq)]
525pub enum AxisConstraint {
526 AsGiven,
528 Unbounded,
531 Fixed(f32),
533}
534
535#[derive(Clone, Copy, Debug)]
551pub struct ConstraintsTransform {
552 pub width: AxisConstraint,
554 pub height: AxisConstraint,
556 pub alignment: Alignment,
558 pub report: bool,
562}
563
564impl ConstraintsTransform {
565 pub fn new() -> Self {
567 Self {
568 width: AxisConstraint::AsGiven,
569 height: AxisConstraint::AsGiven,
570 alignment: Alignment::CENTER,
571 report: true,
572 }
573 }
574
575 pub fn width(mut self, width: AxisConstraint) -> Self {
577 self.width = width;
578 self
579 }
580
581 pub fn height(mut self, height: AxisConstraint) -> Self {
583 self.height = height;
584 self
585 }
586
587 pub fn alignment(mut self, alignment: Alignment) -> Self {
589 self.alignment = alignment;
590 self
591 }
592
593 pub fn report(mut self, report: bool) -> Self {
596 self.report = report;
597 self
598 }
599}
600
601impl Default for ConstraintsTransform {
602 fn default() -> Self {
603 Self::new()
604 }
605}
606
607impl AxisConstraint {
608 pub(crate) fn offered(self, offer: Option<f32>) -> (f32, bool) {
611 match self {
612 AxisConstraint::Unbounded => (0.0, true),
613 AxisConstraint::Fixed(extent) => (extent.max(0.0), false),
614 AxisConstraint::AsGiven => match offer {
615 Some(extent) => (extent.max(0.0), false),
616 None => (0.0, true),
617 },
618 }
619 }
620
621 pub(crate) fn at(self, own: f32) -> (f32, bool) {
628 match self {
629 AxisConstraint::Unbounded => (0.0, true),
630 AxisConstraint::Fixed(extent) => (extent.max(0.0), false),
631 AxisConstraint::AsGiven => (own.max(0.0), false),
632 }
633 }
634}
635
636pub struct ConstraintsTransformBox<Msg = crate::callback::Callback> {
654 transform: ConstraintsTransform,
655 children: Vec<Box<dyn Widget<Msg>>>,
656}
657
658impl<Msg> ConstraintsTransformBox<Msg> {
659 pub fn new(child: impl Widget<Msg> + 'static) -> Self {
661 Self {
662 transform: ConstraintsTransform::new(),
663 children: vec![Box::new(child)],
664 }
665 }
666
667 pub fn width(mut self, width: AxisConstraint) -> Self {
669 self.transform.width = width;
670 self
671 }
672
673 pub fn height(mut self, height: AxisConstraint) -> Self {
675 self.transform.height = height;
676 self
677 }
678
679 pub fn alignment(mut self, alignment: Alignment) -> Self {
681 self.transform.alignment = alignment;
682 self
683 }
684
685 pub fn report(mut self, report: bool) -> Self {
687 self.transform.report = report;
688 self
689 }
690
691 pub fn with(child: impl Widget<Msg> + 'static, transform: ConstraintsTransform) -> Self {
693 Self {
694 transform,
695 children: vec![Box::new(child)],
696 }
697 }
698}
699
700impl<Msg: Clone> Widget<Msg> for ConstraintsTransformBox<Msg> {
701 fn style(&self) -> Style {
702 Style::default()
703 }
704
705 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
706 &self.children
707 }
708
709 fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
710
711 fn on_click(&self) -> Option<Msg> {
712 None
713 }
714
715 fn constraints_transform(&self) -> Option<ConstraintsTransform> {
716 Some(self.transform)
717 }
718
719 fn debug_name(&self) -> &'static str {
720 "ConstraintsTransformBox"
721 }
722}
723
724pub struct UnconstrainedBox;
746
747impl UnconstrainedBox {
748 #[allow(clippy::new_ret_no_self)]
750 pub fn new<Msg: Clone + 'static>(
751 child: impl Widget<Msg> + 'static,
752 ) -> ConstraintsTransformBox<Msg> {
753 ConstraintsTransformBox::new(child)
754 .width(AxisConstraint::Unbounded)
755 .height(AxisConstraint::Unbounded)
756 }
757
758 pub fn axis<Msg: Clone + 'static>(
760 child: impl Widget<Msg> + 'static,
761 axis: crate::scroll::Axis,
762 ) -> ConstraintsTransformBox<Msg> {
763 let freed = |free: bool| match free {
764 true => AxisConstraint::Unbounded,
765 false => AxisConstraint::AsGiven,
766 };
767 ConstraintsTransformBox::new(child)
768 .width(freed(axis.free_x()))
769 .height(freed(axis.free_y()))
770 }
771}
772#[cfg(test)]
773mod tests {
774 use super::*;
775 use crate::{build_ui, Container, Flex, InspectorNode, Runtime, Text};
776 use frus_core::{Color, Primitive};
777
778 const RED: Color = Color {
779 r: 1.0,
780 g: 0.0,
781 b: 0.0,
782 a: 1.0,
783 };
784
785 fn filler() -> Container<()> {
789 Container::new().flex(1.0).color(RED)
790 }
791
792 fn red_box<W: Widget<()> + 'static>(root: W, available: Size) -> Rect {
794 let runtime = Runtime::default();
795 let ui = build_ui(&root, available, &runtime, &Theme::dark());
796 ui.scene()
797 .primitives()
798 .iter()
799 .find_map(|p| match p {
800 Primitive::Rect { rect, color, .. } if color.r > 0.5 && color.g < 0.5 => {
801 Some(*rect)
802 }
803 _ => None,
804 })
805 .expect("the red box")
806 }
807
808 #[test]
809 fn a_ceiling_stops_a_box_that_would_fill() {
810 let root = Flex::<()>::column()
812 .width(400.0)
813 .child(ConstrainedBox::new(filler().height(40.0)).max_width(120.0));
814 let rect = red_box(root, Size::new(400.0, 200.0));
815 assert!((rect.width - 120.0).abs() < 0.5, "capped: {rect:?}");
816 }
817
818 #[test]
819 fn a_floor_stops_a_box_that_would_hug() {
820 let root = Flex::<()>::row()
823 .width(400.0)
824 .height(60.0)
825 .child(ConstrainedBox::new(filler()).min_width(90.0));
826 let rect = red_box(root, Size::new(400.0, 200.0));
827 assert!((rect.width - 90.0).abs() < 0.5, "floored: {rect:?}");
828 }
829
830 #[test]
831 fn a_tight_box_is_that_size_whatever_is_inside_it() {
832 let root = Flex::<()>::column()
833 .width(400.0)
834 .child(ConstrainedBox::tight(75.0, 33.0, filler()));
835 let rect = red_box(root, Size::new(400.0, 200.0));
836 assert!(
837 (rect.width - 75.0).abs() < 0.5 && (rect.height - 33.0).abs() < 0.5,
838 "tight: {rect:?}"
839 );
840 }
841
842 #[test]
843 fn a_sized_box_that_expands_takes_everything_on_offer() {
844 let root = Flex::<()>::column()
845 .width(400.0)
846 .height(200.0)
847 .child(SizedBox::expand(filler()));
848 let rect = red_box(root, Size::new(400.0, 200.0));
849 assert!(
850 (rect.width - 400.0).abs() < 0.5 && (rect.height - 200.0).abs() < 0.5,
851 "expanded: {rect:?}"
852 );
853 }
854
855 #[test]
856 fn a_sized_box_is_the_size_it_was_given() {
857 let root = Flex::<()>::column()
858 .width(400.0)
859 .child(SizedBox::square(64.0, filler()));
860 let rect = red_box(root, Size::new(400.0, 200.0));
861 assert!(
862 (rect.width - 64.0).abs() < 0.5 && (rect.height - 64.0).abs() < 0.5,
863 "square: {rect:?}"
864 );
865 }
866
867 #[test]
868 fn an_intrinsic_width_is_what_the_content_wants_not_what_the_column_offers() {
869 let root = Flex::<()>::column().width(400.0).child(Intrinsic::width(
872 Flex::<()>::column()
873 .child(Text::new("a short line"))
874 .child(Container::new().height(10.0).color(RED)),
875 ));
876 let rect = red_box(root, Size::new(400.0, 200.0));
877 assert!(
878 rect.width > 1.0 && rect.width < 380.0,
879 "the content's width, not the column's: {rect:?}"
880 );
881 }
882
883 #[test]
884 fn an_intrinsic_width_rounds_up_to_its_step() {
885 let step = Intrinsic::<()>::width(Container::new()).step(20.0);
886 assert_eq!(step.quantise(1.0), 20.0);
887 assert_eq!(step.quantise(20.0), 20.0);
888 assert_eq!(step.quantise(21.0), 40.0);
889 let plain = Intrinsic::<()>::width(Container::new());
891 assert_eq!(plain.quantise(21.0), 21.0);
892 }
893
894 #[test]
895 fn an_overflowing_child_is_bigger_than_its_box_and_centred_on_it() {
896 let root = Flex::<()>::column().width(400.0).child(
898 SizedBox::empty()
899 .width(40.0)
900 .height(40.0)
901 .child(OverflowBox::new(filler()).width(100.0).height(100.0)),
902 );
903 let rect = red_box(root, Size::new(400.0, 200.0));
904 assert!(
905 (rect.width - 100.0).abs() < 0.5 && (rect.height - 100.0).abs() < 0.5,
906 "the child keeps its own size: {rect:?}"
907 );
908 assert!(
909 rect.x < -29.0 && rect.x > -31.0,
910 "centred, spilling: {rect:?}"
911 );
912 }
913
914 #[test]
915 fn an_overflow_box_can_anchor_the_spill_to_one_corner() {
916 let root = Flex::<()>::column().width(400.0).child(
917 SizedBox::empty().width(40.0).height(40.0).child(
918 OverflowBox::new(filler())
919 .width(100.0)
920 .height(100.0)
921 .alignment(Alignment::TOP_LEFT),
922 ),
923 );
924 let rect = red_box(root, Size::new(400.0, 200.0));
925 assert!(
926 rect.x.abs() < 0.5 && rect.y.abs() < 0.5,
927 "anchored top-left, spilling right and down: {rect:?}"
928 );
929 }
930
931 #[test]
932 fn the_box_around_an_overflowing_child_does_not_grow() {
933 let root = Flex::<()>::column()
936 .width(400.0)
937 .child(
938 SizedBox::empty().width(40.0).height(40.0).child(
939 OverflowBox::new(Container::new().flex(1.0))
940 .width(100.0)
941 .height(100.0),
942 ),
943 )
944 .child(Container::new().width(10.0).height(10.0).color(RED));
945 let rect = red_box(root, Size::new(400.0, 300.0));
946 assert!((rect.y - 40.0).abs() < 0.5, "the neighbour moved: {rect:?}");
947 }
948
949 #[test]
950 fn an_unconstrained_child_is_the_size_it_asks_for() {
951 let root = Flex::<()>::column().width(400.0).child(
953 SizedBox::empty()
954 .width(20.0)
955 .height(20.0)
956 .child(OverflowBox::unconstrained(
957 Container::new().width(90.0).height(30.0).color(RED),
958 )),
959 );
960 let rect = red_box(root, Size::new(400.0, 200.0));
961 assert!(
962 (rect.width - 90.0).abs() < 0.5 && (rect.height - 30.0).abs() < 0.5,
963 "unconstrained: {rect:?}"
964 );
965 }
966
967 fn inspected(root: &dyn Widget<()>, available: Size) -> (crate::Ui<()>, Vec<InspectorNode>) {
969 crate::build_ui_inspected(root, available, &Runtime::default(), &Theme::dark())
970 }
971
972 fn box_named(nodes: &[InspectorNode], name: &str) -> Rect {
975 nodes
976 .iter()
977 .find(|n| n.name == name)
978 .unwrap_or_else(|| panic!("no {name} in {}", crate::dump_tree(nodes)))
979 .rect
980 }
981
982 fn red(width: f32, height: f32) -> Container<()> {
984 Container::new().width(width).height(height).color(RED)
985 }
986
987 fn narrow_column(width: f32) -> Flex<()> {
990 Flex::<()>::column()
991 .width(width)
992 .align(frus_layout::Align::Start)
993 }
994
995 #[test]
996 fn a_child_that_fits_gives_the_unconstrained_box_its_own_size() {
997 let root = narrow_column(400.0).child(UnconstrainedBox::new(red(90.0, 30.0)));
998 let (ui, nodes) = inspected(&root, Size::new(400.0, 200.0));
999 let own = box_named(&nodes, "ConstraintsTransformBox");
1000 assert!(
1001 (own.width - 90.0).abs() < 0.5 && (own.height - 30.0).abs() < 0.5,
1002 "the box is the child's size: {own:?}"
1003 );
1004 assert!(ui.overflows().is_empty(), "nothing ran past anything");
1005 }
1006
1007 #[test]
1011 fn an_unconstrained_child_that_does_not_fit_overflows_and_says_so() {
1012 let root = narrow_column(100.0).child(UnconstrainedBox::new(red(300.0, 20.0)));
1013 let (ui, nodes) = inspected(&root, Size::new(100.0, 200.0));
1014 let child = red_box_in(&ui);
1015 assert!(
1016 (child.width - 300.0).abs() < 0.5,
1017 "the child kept its size: {child:?}"
1018 );
1019 let own = box_named(&nodes, "ConstraintsTransformBox");
1020 assert!(
1021 (own.width - 100.0).abs() < 0.5,
1022 "the box kept the room it was offered: {own:?}"
1023 );
1024 let spills = ui.overflows();
1027 let mut sides: Vec<_> = spills.iter().map(|o| (o.side, o.amount)).collect();
1028 sides.sort_by_key(|(side, _)| format!("{side:?}"));
1029 assert_eq!(sides.len(), 2, "two edges: {spills:?}");
1030 assert_eq!(sides[0].0, frus_layout::Side::Left);
1031 assert_eq!(sides[1].0, frus_layout::Side::Right);
1032 assert!(
1033 sides.iter().all(|(_, amount)| (amount - 100.0).abs() < 0.5),
1034 "a hundred either side: {spills:?}"
1035 );
1036 }
1037
1038 #[test]
1041 fn freeing_one_axis_leaves_the_other_one_alone() {
1042 let root = narrow_column(100.0).child(UnconstrainedBox::axis(
1043 Container::new().height(200.0).color(RED),
1044 crate::scroll::Axis::Vertical,
1045 ));
1046 let (ui, _) = inspected(&root, Size::new(100.0, 80.0));
1047 let child = red_box_in(&ui);
1048 assert!(
1049 (child.width - 100.0).abs() < 0.5,
1050 "held to the column: {child:?}"
1051 );
1052 assert!(
1053 (child.height - 200.0).abs() < 0.5,
1054 "and as tall as it asked: {child:?}"
1055 );
1056 }
1057
1058 #[test]
1062 fn a_transform_can_give_the_child_a_room_of_its_own() {
1063 let root = narrow_column(200.0).child(
1064 ConstraintsTransformBox::new(Container::new().height(20.0).color(RED))
1065 .width(AxisConstraint::Fixed(100.0)),
1066 );
1067 let (ui, nodes) = inspected(&root, Size::new(200.0, 200.0));
1068 let child = red_box_in(&ui);
1069 assert!(
1070 (child.width - 100.0).abs() < 0.5,
1071 "the child was laid out in the room it was given: {child:?}"
1072 );
1073 assert!((box_named(&nodes, "ConstraintsTransformBox").width - 100.0).abs() < 0.5);
1074 assert!(ui.overflows().is_empty(), "half of it fits in all of it");
1075 }
1076
1077 #[test]
1078 fn a_sized_overflow_box_reports_one_size_and_lays_its_child_out_at_another() {
1079 let root = Flex::<()>::column()
1080 .width(400.0)
1081 .child(SizedOverflowBox::new(40.0, 40.0, red(100.0, 100.0)));
1082 let (ui, nodes) = inspected(&root, Size::new(400.0, 300.0));
1083 let own = box_named(&nodes, "SizedOverflowBox");
1084 assert!(
1085 (own.width - 40.0).abs() < 0.5 && (own.height - 40.0).abs() < 0.5,
1086 "the box is the size it stated: {own:?}"
1087 );
1088 let child = red_box_in(&ui);
1089 assert!(
1090 (child.width - 100.0).abs() < 0.5,
1091 "the child is not: {child:?}"
1092 );
1093 assert!(
1094 (child.x + 30.0).abs() < 0.5 && (child.y + 30.0).abs() < 0.5,
1095 "centred on the box it hangs out of: {child:?}"
1096 );
1097 }
1098
1099 fn red_box_in(ui: &crate::Ui<()>) -> Rect {
1101 ui.scene()
1102 .primitives()
1103 .iter()
1104 .find_map(|p| match p {
1105 Primitive::Rect { rect, color, .. } if color.r > 0.5 && color.g < 0.5 => {
1106 Some(*rect)
1107 }
1108 _ => None,
1109 })
1110 .expect("the red box")
1111 }
1112}