Skip to main content

frus_widgets/
constraints.rs

1//! The **constraint boxes**: widgets whose whole job is to change the size their
2//! child is allowed, or made, to be.
3//!
4//! - [`SizedBox`] — a fixed box, or one that fills, or one that hugs.
5//! - [`ConstrainedBox`] — floors and ceilings on either axis.
6//! - [`IntrinsicWidth`] / [`IntrinsicHeight`] — a box the size its content would
7//!   *like* to be, rather than the size the space on offer suggests.
8//! - [`OverflowBox`] — a child laid out to constraints of its own, which it may
9//!   exceed, painted over whatever is around it.
10//! - [`LimitedBox`] — a ceiling that applies only where the room on offer has no end.
11//!
12//! The first three are ordinary layout nodes and cost nothing beyond what taffy
13//! already does. [`OverflowBox`] is not: its child is laid out **separately**, which
14//! is what lets the child be bigger than the box that holds it.
15
16use frus_core::{Alignment, Rect, Scene, Size};
17use frus_layout::{Dimension, Style};
18
19use crate::interaction::Status;
20use crate::theme::Theme;
21use crate::widget::{FillAxes, Widget};
22
23/// A box that **caps its child only where nothing else does**: where the room on offer has no
24/// end — down a column's scroll, along a list — the child may take at most `max_width` or
25/// `max_height`; where it has one, the box changes nothing.
26///
27/// ```
28/// use frus_widgets::{LimitedBox, Text};
29///
30/// // A placeholder that asks for all the room, and is 120 px tall in a scrolling list.
31/// let _row: LimitedBox<()> = LimitedBox::new(Text::new("Loading")).max_height(120.0);
32/// ```
33///
34/// Without a limit on an axis, that axis is left as it came. The child is never made
35/// bigger than it would be: a child that hugs its content still does.
36pub struct LimitedBox<Msg = crate::callback::Callback> {
37    max: (Option<f32>, Option<f32>),
38    children: Vec<Box<dyn Widget<Msg>>>,
39}
40
41impl<Msg> LimitedBox<Msg> {
42    /// A box around `child`, with no limit yet.
43    pub fn new(child: impl Widget<Msg> + 'static) -> Self {
44        Self {
45            max: (None, None),
46            children: vec![Box::new(child)],
47        }
48    }
49
50    /// The most the child may take across, where the room on offer has no end.
51    pub fn max_width(mut self, max: f32) -> Self {
52        self.max.0 = Some(max.max(0.0));
53        self
54    }
55
56    /// The most the child may take down, where the room on offer has no end.
57    pub fn max_height(mut self, max: f32) -> Self {
58        self.max.1 = Some(max.max(0.0));
59        self
60    }
61}
62
63impl<Msg: Clone> Widget<Msg> for LimitedBox<Msg> {
64    fn style(&self) -> Style {
65        Style::default()
66    }
67
68    fn children(&self) -> &[Box<dyn Widget<Msg>>] {
69        &self.children
70    }
71
72    fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
73
74    fn on_click(&self) -> Option<Msg> {
75        None
76    }
77
78    fn constraints_transform(&self) -> Option<ConstraintsTransform> {
79        let rule = |max: Option<f32>| match max {
80            Some(max) => AxisConstraint::Limited(max),
81            None => AxisConstraint::Loose,
82        };
83        Some(ConstraintsTransform {
84            width: rule(self.max.0),
85            height: rule(self.max.1),
86            alignment: Alignment::TOP_LEFT,
87            report: false,
88        })
89    }
90
91    fn debug_name(&self) -> &'static str {
92        "LimitedBox"
93    }
94}
95
96/// A box of a given size — or one that fills what it is given, or one that takes
97/// only what its child needs.
98///
99/// `SizedBox::empty()` with a size is also the plainest way to put a gap somewhere
100/// that has no gap of its own; with no child it draws nothing at all.
101pub struct SizedBox<Msg = crate::callback::Callback> {
102    width: Dimension,
103    height: Dimension,
104    /// The axes it asks all the room on (milestone 599).
105    fills: FillAxes,
106    children: Vec<Box<dyn Widget<Msg>>>,
107}
108
109impl<Msg> SizedBox<Msg> {
110    /// An empty box, `Auto` on both axes until a size is set.
111    pub fn empty() -> Self {
112        Self {
113            width: Dimension::Auto,
114            height: Dimension::Auto,
115            fills: FillAxes::NONE,
116            children: Vec::new(),
117        }
118    }
119
120    /// A box holding `child`, its size still to be chosen.
121    pub fn new(child: impl Widget<Msg> + 'static) -> Self {
122        let mut boxed = Self::empty();
123        boxed.children.push(Box::new(child));
124        boxed
125    }
126
127    /// A `side`×`side` box holding `child`.
128    pub fn square(side: f32, child: impl Widget<Msg> + 'static) -> Self {
129        Self::new(child).width(side).height(side)
130    }
131
132    /// A box that **fills** the space on offer, on both axes, and hands it to
133    /// `child`.
134    ///
135    /// All of it wherever the parent has it to give: a whole page, a whole card, the width
136    /// of a column even inside a scroll. Along a row's or a column's own direction, where
137    /// the line is shared with other children, it is only as long as its child; wrap it in
138    /// [`Expanded`](crate::Expanded) for the rest of the line.
139    pub fn expand(child: impl Widget<Msg> + 'static) -> Self {
140        let mut boxed = Self::new(child);
141        boxed.fills = FillAxes::BOTH;
142        boxed
143    }
144
145    /// A box that takes **only what its child needs**, on both axes — the useful
146    /// opposite of [`SizedBox::expand`] inside a container that would otherwise
147    /// stretch it.
148    pub fn shrink(child: impl Widget<Msg> + 'static) -> Self {
149        Self::new(child)
150    }
151
152    /// Sets the width, in logical pixels.
153    pub fn width(self, width: f32) -> Self {
154        self.width_dimension(Dimension::Length(width))
155    }
156
157    /// Sets the height, in logical pixels.
158    pub fn height(self, height: f32) -> Self {
159        self.height_dimension(Dimension::Length(height))
160    }
161
162    /// Sets the width as a fraction of the parent's (`1.0` = all of it).
163    pub fn width_fraction(self, fraction: f32) -> Self {
164        self.width_dimension(Dimension::Percent(fraction))
165    }
166
167    /// Sets the height as a fraction of the parent's.
168    pub fn height_fraction(self, fraction: f32) -> Self {
169        self.height_dimension(Dimension::Percent(fraction))
170    }
171
172    fn width_dimension(mut self, width: Dimension) -> Self {
173        self.width = width;
174        self
175    }
176
177    fn height_dimension(mut self, height: Dimension) -> Self {
178        self.height = height;
179        self
180    }
181
182    /// Sets the child, replacing any already there.
183    pub fn child(mut self, child: impl Widget<Msg> + 'static) -> Self {
184        self.children.clear();
185        self.children.push(Box::new(child));
186        self
187    }
188}
189
190impl<Msg: Clone> Widget<Msg> for SizedBox<Msg> {
191    fn style(&self) -> Style {
192        let base = Style {
193            width: self.width,
194            height: self.height,
195            ..Default::default()
196        };
197        // An expanding box **hands** its child the room it took, as the reference's does
198        // with tight constraints: one cell, stretched both ways (milestone 599).
199        match self.fills == FillAxes::BOTH {
200            true => Style {
201                overlap: true,
202                align: frus_layout::Align::Stretch,
203                ..base
204            },
205            false => base,
206        }
207    }
208
209    fn children(&self) -> &[Box<dyn Widget<Msg>>] {
210        &self.children
211    }
212
213    fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {
214        // A pure layout widget: it draws nothing of its own.
215    }
216
217    fn on_click(&self) -> Option<Msg> {
218        None
219    }
220
221    /// The room on offer, when it was made to expand. It used to be a percentage of the
222    /// parent, which is nothing when the parent is as big as its content: a column inside a
223    /// scroll (milestone 599).
224    fn fill_axes(&self, _theme: &Theme) -> FillAxes {
225        self.fills
226    }
227}
228
229/// Floors and ceilings on either axis, for a child that would otherwise be free to
230/// take any size.
231///
232/// A floor and a ceiling that are equal make the box **tight**: whatever the child
233/// or the space on offer says, that is the size. Set nothing and the box is
234/// transparent — it is the constraints that make it worth having, not the box.
235pub struct ConstrainedBox<Msg = crate::callback::Callback> {
236    min_width: Dimension,
237    max_width: Dimension,
238    min_height: Dimension,
239    max_height: Dimension,
240    children: Vec<Box<dyn Widget<Msg>>>,
241}
242
243impl<Msg> ConstrainedBox<Msg> {
244    /// An unconstrained box holding `child`; the constraints are set from here.
245    pub fn new(child: impl Widget<Msg> + 'static) -> Self {
246        Self {
247            min_width: Dimension::Auto,
248            max_width: Dimension::Auto,
249            min_height: Dimension::Auto,
250            max_height: Dimension::Auto,
251            children: vec![Box::new(child)],
252        }
253    }
254
255    /// The same, for a child that is **already boxed** — what a slot holds once the
256    /// caller's type has been erased.
257    pub fn new_boxed(child: Box<dyn Widget<Msg>>) -> Self {
258        Self {
259            min_width: Dimension::Auto,
260            max_width: Dimension::Auto,
261            min_height: Dimension::Auto,
262            max_height: Dimension::Auto,
263            children: vec![child],
264        }
265    }
266
267    /// A box whose floor **is** its ceiling on both axes: exactly `width`×`height`.
268    pub fn tight(width: f32, height: f32, child: impl Widget<Msg> + 'static) -> Self {
269        Self::new(child)
270            .min_width(width)
271            .max_width(width)
272            .min_height(height)
273            .max_height(height)
274    }
275
276    /// A box with ceilings only: the child may be anything up to `width`×`height`.
277    pub fn loose(width: f32, height: f32, child: impl Widget<Msg> + 'static) -> Self {
278        Self::new(child).max_width(width).max_height(height)
279    }
280
281    /// The width the box never goes below.
282    pub fn min_width(mut self, width: f32) -> Self {
283        self.min_width = Dimension::Length(width);
284        self
285    }
286
287    /// The width the box never goes above.
288    pub fn max_width(mut self, width: f32) -> Self {
289        self.max_width = Dimension::Length(width);
290        self
291    }
292
293    /// The height the box never goes below.
294    pub fn min_height(mut self, height: f32) -> Self {
295        self.min_height = Dimension::Length(height);
296        self
297    }
298
299    /// The height the box never goes above.
300    pub fn max_height(mut self, height: f32) -> Self {
301        self.max_height = Dimension::Length(height);
302        self
303    }
304}
305
306impl<Msg: Clone> Widget<Msg> for ConstrainedBox<Msg> {
307    fn style(&self) -> Style {
308        Style {
309            min_width: self.min_width,
310            max_width: self.max_width,
311            min_height: self.min_height,
312            max_height: self.max_height,
313            ..Default::default()
314        }
315    }
316
317    fn children(&self) -> &[Box<dyn Widget<Msg>>] {
318        &self.children
319    }
320
321    fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
322
323    fn on_click(&self) -> Option<Msg> {
324        None
325    }
326}
327
328/// Which axis a box takes from its content's **preferred** size rather than from
329/// the space on offer.
330#[derive(Copy, Clone, Debug, PartialEq, Eq)]
331pub enum IntrinsicAxis {
332    /// The width.
333    Width,
334    /// The height.
335    Height,
336}
337
338/// A box sized to what its content would **like** to be.
339///
340/// The usual case: a column of buttons that should all be as wide as the widest
341/// label, and no wider. Stretching gives them the whole column; hugging gives each
342/// its own width; only an intrinsic width gives them all the widest one.
343///
344/// It is not free. The content is measured **once more**, unconstrained, before it
345/// is laid out for real — which is why it is a widget you reach for deliberately and
346/// not a property every box carries. Nested one inside another, the cost multiplies.
347pub struct Intrinsic<Msg = crate::callback::Callback> {
348    axis: IntrinsicAxis,
349    step: Option<f32>,
350    children: Vec<Box<dyn Widget<Msg>>>,
351}
352
353/// A box as wide as its content would like to be. See [`Intrinsic`].
354pub type IntrinsicWidth<Msg> = Intrinsic<Msg>;
355/// A box as tall as its content would like to be. See [`Intrinsic`].
356pub type IntrinsicHeight<Msg> = Intrinsic<Msg>;
357
358impl<Msg> Intrinsic<Msg> {
359    /// A box as **wide** as `child` would like to be.
360    pub fn width(child: impl Widget<Msg> + 'static) -> Self {
361        Self {
362            axis: IntrinsicAxis::Width,
363            step: None,
364            children: vec![Box::new(child)],
365        }
366    }
367
368    /// A box as **tall** as `child` would like to be.
369    pub fn height(child: impl Widget<Msg> + 'static) -> Self {
370        Self {
371            axis: IntrinsicAxis::Height,
372            step: None,
373            children: vec![Box::new(child)],
374        }
375    }
376
377    /// Rounds the measured size **up** to a multiple of `step`.
378    ///
379    /// For a box whose width should change in steps rather than continuously — a
380    /// label that grows a character at a time makes everything beside it jitter.
381    pub fn step(mut self, step: f32) -> Self {
382        self.step = (step > 0.0).then_some(step);
383        self
384    }
385
386    /// The axis this box takes from its content.
387    pub fn axis(&self) -> IntrinsicAxis {
388        self.axis
389    }
390
391    /// Applies the step rounding to a measured extent.
392    pub fn quantise(&self, extent: f32) -> f32 {
393        match self.step {
394            Some(step) => (extent / step).ceil() * step,
395            None => extent,
396        }
397    }
398}
399
400impl<Msg: Clone> Widget<Msg> for Intrinsic<Msg> {
401    fn style(&self) -> Style {
402        // The measured extent is written into this style by `build_layout`, which is
403        // the only place with a runtime to measure against.
404        Style::default()
405    }
406
407    fn children(&self) -> &[Box<dyn Widget<Msg>>] {
408        &self.children
409    }
410
411    fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
412
413    fn on_click(&self) -> Option<Msg> {
414        None
415    }
416
417    fn intrinsic(&self) -> Option<(IntrinsicAxis, Option<f32>)> {
418        Some((self.axis, self.step))
419    }
420}
421
422/// The constraints an [`OverflowBox`] hands its child, and where the result sits.
423#[derive(Copy, Clone, Debug, PartialEq)]
424pub struct Overflow {
425    /// Width given to the child; `None` = the box's own width.
426    pub width: Option<f32>,
427    /// Height given to the child; `None` = the box's own height.
428    pub height: Option<f32>,
429    /// Lay the child out **unconstrained**, at the size it asks for. Overrides the
430    /// two above.
431    pub unconstrained: bool,
432    /// Lay the child out at the box's width and **the height it asks for** (milestone
433    /// 595): a paragraph kept at the width it is shown at and allowed to run below the
434    /// box. Overrides `height`.
435    pub natural_height: bool,
436    /// Where the child sits inside the box, and therefore which way it spills.
437    pub alignment: Alignment,
438}
439
440/// A child laid out to constraints of **its own**, free to be bigger than the box
441/// that holds it, and painted over whatever is around it.
442///
443/// The box itself takes the size it would have taken with no child at all, so
444/// nothing around it moves. What changes is the child: it is laid out separately —
445/// at a size given here, or at the size it asks for
446/// ([`OverflowBox::unconstrained`]) — then anchored in the box and allowed to spill
447/// past its edges.
448///
449/// This is the escape hatch for a background that should bleed past its slot, or a
450/// decoration wider than the row it belongs to. It does **not** clip: put a
451/// [`crate::ClipRRect`] above it if the spill should stop somewhere.
452pub struct OverflowBox<Msg = crate::callback::Callback> {
453    overflow: Overflow,
454    children: Vec<Box<dyn Widget<Msg>>>,
455}
456
457impl<Msg> OverflowBox<Msg> {
458    /// A box that lays `child` out to the sizes given by the builders below,
459    /// centred; unset axes keep the box's own size.
460    pub fn new(child: impl Widget<Msg> + 'static) -> Self {
461        Self {
462            overflow: Overflow {
463                width: None,
464                height: None,
465                unconstrained: false,
466                natural_height: false,
467                alignment: Alignment::CENTER,
468            },
469            children: vec![Box::new(child)],
470        }
471    }
472
473    /// A box that lets `child` be **whatever size it asks for**, as if nothing were
474    /// constraining it, and centres the result.
475    pub fn unconstrained(child: impl Widget<Msg> + 'static) -> Self {
476        let mut boxed = Self::new(child);
477        boxed.overflow.unconstrained = true;
478        boxed
479    }
480
481    /// The width to lay the child out at.
482    pub fn width(mut self, width: f32) -> Self {
483        self.overflow.width = Some(width);
484        self
485    }
486
487    /// The height to lay the child out at.
488    pub fn height(mut self, height: f32) -> Self {
489        self.overflow.height = Some(height);
490        self
491    }
492
493    /// Lays the child out at the box's width and **the height it asks for**, however tall
494    /// or short the box is (milestone 595).
495    pub fn natural_height(mut self) -> Self {
496        self.overflow.natural_height = true;
497        self
498    }
499
500    /// Where the child sits in the box — and so which edges it spills past.
501    pub fn alignment(mut self, alignment: Alignment) -> Self {
502        self.overflow.alignment = alignment;
503        self
504    }
505}
506
507impl<Msg: Clone> Widget<Msg> for OverflowBox<Msg> {
508    fn style(&self) -> Style {
509        // The box takes **the largest size it is allowed**, because its child is laid
510        // out separately and so contributes nothing to its size. Left to hug, it would
511        // collapse to nothing on the main axis, and the child would be anchored to a
512        // box of zero width — spilling equally in both directions from a point.
513        Style {
514            width: Dimension::Percent(1.0),
515            height: Dimension::Percent(1.0),
516            ..Default::default()
517        }
518    }
519
520    fn children(&self) -> &[Box<dyn Widget<Msg>>] {
521        &self.children
522    }
523
524    fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
525
526    fn on_click(&self) -> Option<Msg> {
527        None
528    }
529
530    fn overflow_box(&self) -> Option<Overflow> {
531        Some(self.overflow)
532    }
533}
534
535impl Overflow {
536    /// The size to lay the child out at, given the box's own, and whether the
537    /// measurement should be unconstrained.
538    pub(crate) fn child_size(&self, own: Size) -> Size {
539        Size::new(
540            self.width.unwrap_or(own.width),
541            self.height.unwrap_or(own.height),
542        )
543    }
544
545    /// Where the child's top-left corner goes, given both sizes.
546    pub(crate) fn origin(&self, own: Rect, child: Size) -> (f32, f32) {
547        (
548            own.x + (own.width - child.width) * self.alignment.fraction_x(),
549            own.y + (own.height - child.height) * self.alignment.fraction_y(),
550        )
551    }
552}
553
554/// A box of a **stated size** whose child is laid out at another one, and spills.
555///
556/// The size given here is what the parent sees and what the neighbours make room for.
557/// The child is laid out separately — at the size it asks for, or at one stated with
558/// [`SizedOverflowBox::child_size`] — anchored in the box by its alignment, and allowed
559/// past every edge.
560///
561/// It is [`OverflowBox`] with the hole given a size: an overflow box fills whatever it is
562/// offered, because nothing else could anchor a child that contributes no size, and this
563/// is the form for a slot narrower than the space around it — a 24 px gap in a toolbar
564/// showing a 40 px control, a caption strip under an image that reaches past both sides.
565///
566/// It does **not** clip: put a [`crate::ClipRRect`] above it if the spill should stop.
567pub struct SizedOverflowBox<Msg = crate::callback::Callback> {
568    width: f32,
569    height: f32,
570    overflow: Overflow,
571    children: Vec<Box<dyn Widget<Msg>>>,
572}
573
574impl<Msg> SizedOverflowBox<Msg> {
575    /// A `width`×`height` box holding `child`, which is laid out at **its own** natural
576    /// size and centred.
577    pub fn new(width: f32, height: f32, child: impl Widget<Msg> + 'static) -> Self {
578        Self {
579            width,
580            height,
581            overflow: Overflow {
582                width: None,
583                height: None,
584                unconstrained: true,
585                natural_height: false,
586                alignment: Alignment::CENTER,
587            },
588            children: vec![Box::new(child)],
589        }
590    }
591
592    /// Lays the child out at a size of its own rather than at the one it asks for.
593    pub fn child_size(mut self, width: f32, height: f32) -> Self {
594        self.overflow.unconstrained = false;
595        self.overflow.width = Some(width);
596        self.overflow.height = Some(height);
597        self
598    }
599
600    /// Where the child sits in the box — and so which edges it spills past.
601    pub fn alignment(mut self, alignment: Alignment) -> Self {
602        self.overflow.alignment = alignment;
603        self
604    }
605}
606
607impl<Msg: Clone> Widget<Msg> for SizedOverflowBox<Msg> {
608    fn style(&self) -> Style {
609        Style {
610            width: Dimension::Length(self.width),
611            height: Dimension::Length(self.height),
612            ..Default::default()
613        }
614    }
615
616    fn children(&self) -> &[Box<dyn Widget<Msg>>] {
617        &self.children
618    }
619
620    fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
621
622    fn on_click(&self) -> Option<Msg> {
623        None
624    }
625
626    fn overflow_box(&self) -> Option<Overflow> {
627        Some(self.overflow)
628    }
629
630    fn debug_name(&self) -> &'static str {
631        "SizedOverflowBox"
632    }
633}
634
635/// What a [`ConstraintsTransformBox`] does to **one axis** of the space on offer.
636#[derive(Clone, Copy, Debug, PartialEq)]
637pub enum AxisConstraint {
638    /// As it came: the child is given the room the parent offered.
639    AsGiven,
640    /// Taken away: the child is asked how big it would like to be, and may come back
641    /// bigger than the room there was.
642    Unbounded,
643    /// A number of its own, whatever was offered.
644    Fixed(f32),
645    /// The room on offer as **the most** the child may take, not a size it is handed: a
646    /// paragraph wraps at it and is then as wide as its longest line. With no room on offer,
647    /// as big as it likes (milestone 598).
648    Loose,
649    /// As [`Loose`](Self::Loose) where the room on offer is bounded; where it is not, at most
650    /// this many pixels. What [`LimitedBox`] does (milestone 598).
651    Limited(f32),
652}
653
654/// What a [`ConstraintsTransformBox`] gives its child, per axis, and what it does about a
655/// child that comes back too big.
656///
657/// It is a small vocabulary rather than an arbitrary function of the space on offer, and
658/// that is a decision worth stating. The layout and the paint walk are two passes over the
659/// same tree, and only the first is told what the parent offered: by the time the walk lays
660/// the child out, the box has been sized to the child and the offer is not recoverable from
661/// it. A function of the offer could not be run again there — and a function run again on
662/// the wrong input answers plausibly, which is worse than not answering at all. Each of
663/// these three **can** be run again: a fixed number is itself, an unbounded axis is a
664/// question with no input, and an axis given as it came produced a child of exactly the
665/// box's own size.
666///
667/// The reference's transform is a closure, and every transform the reference itself ships
668/// is one of these three.
669#[derive(Clone, Copy, Debug)]
670pub struct ConstraintsTransform {
671    /// What the child is given across.
672    pub width: AxisConstraint,
673    /// And down.
674    pub height: AxisConstraint,
675    /// Where a child smaller than the box sits in it.
676    pub alignment: Alignment,
677    /// Whether a child that came out **bigger** than the box is reported as an overflow —
678    /// the yellow and black band, in debug builds. On by default: a box that transforms its
679    /// constraints is asking a question about what fits, and a silent answer is no answer.
680    pub report: bool,
681}
682
683impl ConstraintsTransform {
684    /// Feeds what decides the box's geometry to a layout fingerprint (milestone 593): the
685    /// rule on each axis and where the child sits. A transform is data, not a closure, so
686    /// two frames that hash alike ask the same question of the same child.
687    pub(crate) fn layout_hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
688        use std::hash::Hash;
689        for axis in [self.width, self.height] {
690            match axis {
691                AxisConstraint::AsGiven => 0u8.hash(hasher),
692                AxisConstraint::Unbounded => 1u8.hash(hasher),
693                AxisConstraint::Fixed(v) => {
694                    2u8.hash(hasher);
695                    v.to_bits().hash(hasher);
696                }
697                AxisConstraint::Loose => 3u8.hash(hasher),
698                AxisConstraint::Limited(v) => {
699                    4u8.hash(hasher);
700                    v.to_bits().hash(hasher);
701                }
702            }
703        }
704        self.alignment.x.to_bits().hash(hasher);
705        self.alignment.y.to_bits().hash(hasher);
706    }
707
708    /// Both axes as they came, which is the transform that does nothing.
709    pub fn new() -> Self {
710        Self {
711            width: AxisConstraint::AsGiven,
712            height: AxisConstraint::AsGiven,
713            alignment: Alignment::CENTER,
714            report: true,
715        }
716    }
717
718    /// What the child is given across.
719    pub fn width(mut self, width: AxisConstraint) -> Self {
720        self.width = width;
721        self
722    }
723
724    /// What the child is given down.
725    pub fn height(mut self, height: AxisConstraint) -> Self {
726        self.height = height;
727        self
728    }
729
730    /// Where a child smaller than the box sits in it.
731    pub fn alignment(mut self, alignment: Alignment) -> Self {
732        self.alignment = alignment;
733        self
734    }
735
736    /// Whether a child bigger than the box wears a band. Off is what a deliberate spill
737    /// wants.
738    pub fn report(mut self, report: bool) -> Self {
739        self.report = report;
740        self
741    }
742}
743
744impl Default for ConstraintsTransform {
745    fn default() -> Self {
746        Self::new()
747    }
748}
749
750impl AxisConstraint {
751    /// `(extent, free, filled)` for the **measurement**, given what the layout offered on
752    /// this axis — `None` being no limit at all. A bounded axis is either **handed** to the
753    /// child (`filled`: a child with no size of its own takes it) or only **allowed** (the
754    /// most it may take).
755    pub(crate) fn offered(self, offer: Option<f32>) -> (f32, bool, bool) {
756        match self {
757            AxisConstraint::Unbounded => (0.0, true, false),
758            AxisConstraint::Fixed(extent) => (extent.max(0.0), false, true),
759            AxisConstraint::AsGiven => match offer {
760                Some(extent) => (extent.max(0.0), false, true),
761                None => (0.0, true, false),
762            },
763            AxisConstraint::Loose => match offer {
764                Some(extent) => (extent.max(0.0), false, false),
765                None => (0.0, true, false),
766            },
767            AxisConstraint::Limited(most) => match offer {
768                Some(extent) => (extent.max(0.0), false, false),
769                None => (most.max(0.0), false, false),
770            },
771        }
772    }
773
774    /// `(extent, free)` for the **walk**, given the box the measurement produced.
775    ///
776    /// It answers the same thing the measurement did, from what is left of it: a fixed
777    /// number is itself, an unbounded axis is the same question either way, and an axis
778    /// given as it came produced a child of the box's own extent — the box being that child
779    /// held to what was offered.
780    ///
781    /// `(extent, free, filled)`, as [`Self::offered`]. A loose or limited axis produced a box
782    /// of the child's own extent, and the child is allowed that extent again, not handed it:
783    /// a paragraph that wrapped at the room and is as wide as its longest line wraps at that
784    /// line the same way.
785    pub(crate) fn at(self, own: f32) -> (f32, bool, bool) {
786        match self {
787            AxisConstraint::Unbounded => (0.0, true, false),
788            AxisConstraint::Fixed(extent) => (extent.max(0.0), false, true),
789            AxisConstraint::AsGiven => (own.max(0.0), false, true),
790            AxisConstraint::Loose | AxisConstraint::Limited(_) => (own.max(0.0), false, false),
791        }
792    }
793}
794
795/// A box that **changes the space on offer** before its child is laid out in it, and is
796/// then as big as what came back — never bigger than what it was itself offered.
797///
798/// This is the general form of the boxes above, and the one they are special cases of: what
799/// the child is given, per axis, said in [`AxisConstraint`]. Taking a ceiling away lets the
800/// child be its natural size ([`UnconstrainedBox`]); a fixed number lays it out in a room
801/// of that size whatever the parent offered.
802///
803/// ```ignore
804/// // "However tall you need to be" — and a band across the box if that was too tall.
805/// ConstraintsTransformBox::new(paragraph).height(AxisConstraint::Unbounded)
806/// ```
807///
808/// A child that comes out bigger than the box **overflows it**: painted past the edges, and
809/// reported with the debug band. That is the point of the widget — it asks what the space
810/// on offer is doing to the content, and answers on the screen. Turn the band off with
811/// [`ConstraintsTransformBox::report`] when the spill is deliberate.
812pub struct ConstraintsTransformBox<Msg = crate::callback::Callback> {
813    transform: ConstraintsTransform,
814    children: Vec<Box<dyn Widget<Msg>>>,
815}
816
817impl<Msg> ConstraintsTransformBox<Msg> {
818    /// A box holding `child`, with both axes still as they came.
819    pub fn new(child: impl Widget<Msg> + 'static) -> Self {
820        Self {
821            transform: ConstraintsTransform::new(),
822            children: vec![Box::new(child)],
823        }
824    }
825
826    /// What the child is given across.
827    pub fn width(mut self, width: AxisConstraint) -> Self {
828        self.transform.width = width;
829        self
830    }
831
832    /// What the child is given down.
833    pub fn height(mut self, height: AxisConstraint) -> Self {
834        self.transform.height = height;
835        self
836    }
837
838    /// Where a child smaller than the box sits in it.
839    pub fn alignment(mut self, alignment: Alignment) -> Self {
840        self.transform.alignment = alignment;
841        self
842    }
843
844    /// Whether a child bigger than the box wears a band.
845    pub fn report(mut self, report: bool) -> Self {
846        self.transform.report = report;
847        self
848    }
849
850    /// A box built from a transform prepared elsewhere.
851    pub fn with(child: impl Widget<Msg> + 'static, transform: ConstraintsTransform) -> Self {
852        Self {
853            transform,
854            children: vec![Box::new(child)],
855        }
856    }
857}
858
859impl<Msg: Clone> Widget<Msg> for ConstraintsTransformBox<Msg> {
860    fn style(&self) -> Style {
861        Style::default()
862    }
863
864    fn children(&self) -> &[Box<dyn Widget<Msg>>] {
865        &self.children
866    }
867
868    fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
869
870    fn on_click(&self) -> Option<Msg> {
871        None
872    }
873
874    fn constraints_transform(&self) -> Option<ConstraintsTransform> {
875        Some(self.transform)
876    }
877
878    fn debug_name(&self) -> &'static str {
879        "ConstraintsTransformBox"
880    }
881}
882
883/// Lets its child be its **natural size** even where the parent offers less — and says so
884/// when that does not fit.
885///
886/// The child is laid out as if nothing were constraining it, and the box is as big as the
887/// child, up to the space it was itself offered. Where the child fits, nothing is
888/// unusual — the box is simply the child's size, hugging it in a place that would have
889/// stretched it. Where it does not, the child is painted past the edges and a band says by
890/// how much, in debug builds.
891///
892/// ```ignore
893/// // A row of chips that must not be squashed: too many, and the overflow is visible.
894/// UnconstrainedBox::new(Flex::row().child(chip_a).child(chip_b))
895/// ```
896///
897/// That visible failure is the point, and the difference between this and [`OverflowBox`],
898/// which spills on purpose and quietly. It is [`ConstraintsTransformBox`] with the
899/// constraint taken away, and nothing more.
900///
901/// [`UnconstrainedBox::axis`] frees **one** axis and leaves the other as it was, which is
902/// usually what is wanted: a paragraph that may be as tall as it likes is still as wide as
903/// its column.
904pub struct UnconstrainedBox;
905
906impl UnconstrainedBox {
907    /// Frees both axes.
908    #[allow(clippy::new_ret_no_self)]
909    pub fn new<Msg: Clone + 'static>(
910        child: impl Widget<Msg> + 'static,
911    ) -> ConstraintsTransformBox<Msg> {
912        ConstraintsTransformBox::new(child)
913            .width(AxisConstraint::Unbounded)
914            .height(AxisConstraint::Unbounded)
915    }
916
917    /// Frees the given axis, and leaves the other one as the parent offered it.
918    pub fn axis<Msg: Clone + 'static>(
919        child: impl Widget<Msg> + 'static,
920        axis: crate::scroll::Axis,
921    ) -> ConstraintsTransformBox<Msg> {
922        let freed = |free: bool| match free {
923            true => AxisConstraint::Unbounded,
924            false => AxisConstraint::AsGiven,
925        };
926        ConstraintsTransformBox::new(child)
927            .width(freed(axis.free_x()))
928            .height(freed(axis.free_y()))
929    }
930}
931#[cfg(test)]
932mod tests {
933    use super::*;
934    use crate::{build_ui, Container, Flex, InspectorNode, Runtime, Text};
935    use frus_core::{Color, Primitive};
936
937    const RED: Color = Color {
938        r: 1.0,
939        g: 0.0,
940        b: 0.0,
941        a: 1.0,
942    };
943
944    /// A child that fills whatever box it is put in, painted red so the tests below
945    /// can read that box off the scene. `Align::Stretch` gives it the cross axis;
946    /// `flex(1)` gives it the main one.
947    fn filler() -> Container<()> {
948        Container::new().flex(1.0).color(RED)
949    }
950
951    /// The first red rectangle painted.
952    fn red_box<W: Widget<()> + 'static>(root: W, available: Size) -> Rect {
953        let runtime = Runtime::default();
954        let ui = build_ui(&root, available, &runtime, &Theme::dark());
955        ui.scene()
956            .primitives()
957            .iter()
958            .find_map(|p| match p {
959                Primitive::Rect { rect, color, .. } if color.r > 0.5 && color.g < 0.5 => {
960                    Some(*rect)
961                }
962                _ => None,
963            })
964            .expect("the red box")
965    }
966
967    #[test]
968    fn a_ceiling_stops_a_box_that_would_fill() {
969        // Stretched by its column, the box would be 400 wide; capped, it is 120.
970        let root = Flex::<()>::column()
971            .width(400.0)
972            .child(ConstrainedBox::new(filler().height(40.0)).max_width(120.0));
973        let rect = red_box(root, Size::new(400.0, 200.0));
974        assert!((rect.width - 120.0).abs() < 0.5, "capped: {rect:?}");
975    }
976
977    #[test]
978    fn a_floor_stops_a_box_that_would_hug() {
979        // In a row the box hugs its content, which asks for nothing; the floor is
980        // the only thing giving it a width.
981        let root = Flex::<()>::row()
982            .width(400.0)
983            .height(60.0)
984            .child(ConstrainedBox::new(filler()).min_width(90.0))
985            // Along a row the room is the row's to share out: with a neighbour, an empty
986            // box is nothing there, as the reference's is (milestone 633). Alone in the row
987            // frus gives it the room instead, where the reference would give it none.
988            .child(Container::new().width(10.0).height(10.0));
989        let rect = red_box(root, Size::new(400.0, 200.0));
990        assert!((rect.width - 90.0).abs() < 0.5, "floored: {rect:?}");
991    }
992
993    #[test]
994    fn a_tight_box_is_that_size_whatever_is_inside_it() {
995        let root = Flex::<()>::column()
996            .width(400.0)
997            .child(ConstrainedBox::tight(75.0, 33.0, filler()));
998        let rect = red_box(root, Size::new(400.0, 200.0));
999        assert!(
1000            (rect.width - 75.0).abs() < 0.5 && (rect.height - 33.0).abs() < 0.5,
1001            "tight: {rect:?}"
1002        );
1003    }
1004
1005    #[test]
1006    fn a_sized_box_that_expands_takes_everything_on_offer() {
1007        let root = Flex::<()>::column()
1008            .width(400.0)
1009            .height(200.0)
1010            .child(SizedBox::expand(filler()));
1011        let rect = red_box(root, Size::new(400.0, 200.0));
1012        assert!(
1013            (rect.width - 400.0).abs() < 0.5 && (rect.height - 200.0).abs() < 0.5,
1014            "expanded: {rect:?}"
1015        );
1016    }
1017
1018    #[test]
1019    fn a_sized_box_is_the_size_it_was_given() {
1020        let root = Flex::<()>::column()
1021            .width(400.0)
1022            .child(SizedBox::square(64.0, filler()));
1023        let rect = red_box(root, Size::new(400.0, 200.0));
1024        assert!(
1025            (rect.width - 64.0).abs() < 0.5 && (rect.height - 64.0).abs() < 0.5,
1026            "square: {rect:?}"
1027        );
1028    }
1029
1030    #[test]
1031    fn an_intrinsic_width_is_what_the_content_wants_not_what_the_column_offers() {
1032        // Stretched by the 400 px column, the inner block would be 400 wide. Taken
1033        // intrinsically, it is as wide as the line inside it — and much less.
1034        let root = Flex::<()>::column().width(400.0).child(Intrinsic::width(
1035            Flex::<()>::column()
1036                .child(Text::new("a short line"))
1037                .child(Container::new().height(10.0).color(RED)),
1038        ));
1039        let rect = red_box(root, Size::new(400.0, 200.0));
1040        assert!(
1041            rect.width > 1.0 && rect.width < 380.0,
1042            "the content's width, not the column's: {rect:?}"
1043        );
1044    }
1045
1046    #[test]
1047    fn an_intrinsic_width_rounds_up_to_its_step() {
1048        let step = Intrinsic::<()>::width(Container::new()).step(20.0);
1049        assert_eq!(step.quantise(1.0), 20.0);
1050        assert_eq!(step.quantise(20.0), 20.0);
1051        assert_eq!(step.quantise(21.0), 40.0);
1052        // With no step, the measurement passes through untouched.
1053        let plain = Intrinsic::<()>::width(Container::new());
1054        assert_eq!(plain.quantise(21.0), 21.0);
1055    }
1056
1057    #[test]
1058    fn an_overflowing_child_is_bigger_than_its_box_and_centred_on_it() {
1059        // A 40 px slot holding a 100 px child: it hangs 30 px off either side.
1060        let root = Flex::<()>::column().width(400.0).child(
1061            SizedBox::empty()
1062                .width(40.0)
1063                .height(40.0)
1064                .child(OverflowBox::new(filler()).width(100.0).height(100.0)),
1065        );
1066        let rect = red_box(root, Size::new(400.0, 200.0));
1067        assert!(
1068            (rect.width - 100.0).abs() < 0.5 && (rect.height - 100.0).abs() < 0.5,
1069            "the child keeps its own size: {rect:?}"
1070        );
1071        assert!(
1072            rect.x < -29.0 && rect.x > -31.0,
1073            "centred, spilling: {rect:?}"
1074        );
1075    }
1076
1077    #[test]
1078    fn an_overflow_box_can_anchor_the_spill_to_one_corner() {
1079        let root = Flex::<()>::column().width(400.0).child(
1080            SizedBox::empty().width(40.0).height(40.0).child(
1081                OverflowBox::new(filler())
1082                    .width(100.0)
1083                    .height(100.0)
1084                    .alignment(Alignment::TOP_LEFT),
1085            ),
1086        );
1087        let rect = red_box(root, Size::new(400.0, 200.0));
1088        assert!(
1089            rect.x.abs() < 0.5 && rect.y.abs() < 0.5,
1090            "anchored top-left, spilling right and down: {rect:?}"
1091        );
1092    }
1093
1094    #[test]
1095    fn the_box_around_an_overflowing_child_does_not_grow() {
1096        // The neighbour below sits at 40, not at 100: the box did not grow, which is
1097        // the whole difference between an overflow and a big child.
1098        let root = Flex::<()>::column()
1099            .width(400.0)
1100            .child(
1101                SizedBox::empty().width(40.0).height(40.0).child(
1102                    OverflowBox::new(Container::new().flex(1.0))
1103                        .width(100.0)
1104                        .height(100.0),
1105                ),
1106            )
1107            .child(Container::new().width(10.0).height(10.0).color(RED));
1108        let rect = red_box(root, Size::new(400.0, 300.0));
1109        assert!((rect.y - 40.0).abs() < 0.5, "the neighbour moved: {rect:?}");
1110    }
1111
1112    #[test]
1113    fn an_unconstrained_child_is_the_size_it_asks_for() {
1114        // The slot is 20 px; the child asks for 90×30 and gets it.
1115        let root = Flex::<()>::column().width(400.0).child(
1116            SizedBox::empty()
1117                .width(20.0)
1118                .height(20.0)
1119                .child(OverflowBox::unconstrained(
1120                    Container::new().width(90.0).height(30.0).color(RED),
1121                )),
1122        );
1123        let rect = red_box(root, Size::new(400.0, 200.0));
1124        assert!(
1125            (rect.width - 90.0).abs() < 0.5 && (rect.height - 30.0).abs() < 0.5,
1126            "unconstrained: {rect:?}"
1127        );
1128    }
1129
1130    /// The scene and the inspector's boxes, for a tree laid out in `available`.
1131    fn inspected(root: &dyn Widget<()>, available: Size) -> (crate::Ui<()>, Vec<InspectorNode>) {
1132        crate::build_ui_inspected(root, available, &Runtime::default(), &Theme::dark())
1133    }
1134
1135    /// The box the walk gave the widget named `name` — the box itself, which is the thing
1136    /// these widgets are about and the one thing the scene does not show.
1137    fn box_named(nodes: &[InspectorNode], name: &str) -> Rect {
1138        nodes
1139            .iter()
1140            .find(|n| n.name == name)
1141            .unwrap_or_else(|| panic!("no {name} in {}", crate::dump_tree(nodes)))
1142            .rect
1143    }
1144
1145    /// A red box of a stated size — laid out at whatever it is given, and visible.
1146    fn red(width: f32, height: f32) -> Container<()> {
1147        Container::new().width(width).height(height).color(RED)
1148    }
1149
1150    /// A column that does **not** stretch its children, so a box that hugs can be seen
1151    /// hugging.
1152    fn narrow_column(width: f32) -> Flex<()> {
1153        Flex::<()>::column()
1154            .width(width)
1155            .align(frus_layout::Align::Start)
1156    }
1157
1158    #[test]
1159    fn a_child_that_fits_gives_the_unconstrained_box_its_own_size() {
1160        let root = narrow_column(400.0).child(UnconstrainedBox::new(red(90.0, 30.0)));
1161        let (ui, nodes) = inspected(&root, Size::new(400.0, 200.0));
1162        let own = box_named(&nodes, "ConstraintsTransformBox");
1163        assert!(
1164            (own.width - 90.0).abs() < 0.5 && (own.height - 30.0).abs() < 0.5,
1165            "the box is the child's size: {own:?}"
1166        );
1167        assert!(ui.overflows().is_empty(), "nothing ran past anything");
1168    }
1169
1170    /// The other half, and the reason the widget is worth having: the child keeps the
1171    /// size it asked for, the box keeps the size it was offered, and the difference is
1172    /// **reported** rather than quietly absorbed.
1173    #[test]
1174    fn an_unconstrained_child_that_does_not_fit_overflows_and_says_so() {
1175        let root = narrow_column(100.0).child(UnconstrainedBox::new(red(300.0, 20.0)));
1176        let (ui, nodes) = inspected(&root, Size::new(100.0, 200.0));
1177        let child = red_box_in(&ui);
1178        assert!(
1179            (child.width - 300.0).abs() < 0.5,
1180            "the child kept its size: {child:?}"
1181        );
1182        let own = box_named(&nodes, "ConstraintsTransformBox");
1183        assert!(
1184            (own.width - 100.0).abs() < 0.5,
1185            "the box kept the room it was offered: {own:?}"
1186        );
1187        // Centred, it runs past **both** sides, and both are reported: a band on one of
1188        // them would say the child was a hundred too wide rather than two.
1189        let spills = ui.overflows();
1190        let mut sides: Vec<_> = spills.iter().map(|o| (o.side, o.amount)).collect();
1191        sides.sort_by_key(|(side, _)| format!("{side:?}"));
1192        assert_eq!(sides.len(), 2, "two edges: {spills:?}");
1193        assert_eq!(sides[0].0, frus_layout::Side::Left);
1194        assert_eq!(sides[1].0, frus_layout::Side::Right);
1195        assert!(
1196            sides.iter().all(|(_, amount)| (amount - 100.0).abs() < 0.5),
1197            "a hundred either side: {spills:?}"
1198        );
1199    }
1200
1201    /// One axis freed, the other left as it was: the child is as tall as it likes and no
1202    /// wider than the column, which is what a paragraph wants.
1203    #[test]
1204    fn freeing_one_axis_leaves_the_other_one_alone() {
1205        let root = narrow_column(100.0).child(UnconstrainedBox::axis(
1206            Container::new().height(200.0).color(RED),
1207            crate::scroll::Axis::Vertical,
1208        ));
1209        let (ui, _) = inspected(&root, Size::new(100.0, 80.0));
1210        let child = red_box_in(&ui);
1211        assert!(
1212            (child.width - 100.0).abs() < 0.5,
1213            "held to the column: {child:?}"
1214        );
1215        assert!(
1216            (child.height - 200.0).abs() < 0.5,
1217            "and as tall as it asked: {child:?}"
1218        );
1219    }
1220
1221    /// Unbounded is only one of the three things an axis can say: this one gives the
1222    /// child a hundred pixels of room in a column twice that wide, and the box comes back
1223    /// the size of what it asked for.
1224    #[test]
1225    fn a_transform_can_give_the_child_a_room_of_its_own() {
1226        let root = narrow_column(200.0).child(
1227            ConstraintsTransformBox::new(Container::new().height(20.0).color(RED))
1228                .width(AxisConstraint::Fixed(100.0)),
1229        );
1230        let (ui, nodes) = inspected(&root, Size::new(200.0, 200.0));
1231        let child = red_box_in(&ui);
1232        assert!(
1233            (child.width - 100.0).abs() < 0.5,
1234            "the child was laid out in the room it was given: {child:?}"
1235        );
1236        assert!((box_named(&nodes, "ConstraintsTransformBox").width - 100.0).abs() < 0.5);
1237        assert!(ui.overflows().is_empty(), "half of it fits in all of it");
1238    }
1239
1240    #[test]
1241    fn a_sized_overflow_box_reports_one_size_and_lays_its_child_out_at_another() {
1242        let root = Flex::<()>::column()
1243            .width(400.0)
1244            .child(SizedOverflowBox::new(40.0, 40.0, red(100.0, 100.0)));
1245        let (ui, nodes) = inspected(&root, Size::new(400.0, 300.0));
1246        let own = box_named(&nodes, "SizedOverflowBox");
1247        assert!(
1248            (own.width - 40.0).abs() < 0.5 && (own.height - 40.0).abs() < 0.5,
1249            "the box is the size it stated: {own:?}"
1250        );
1251        let child = red_box_in(&ui);
1252        assert!(
1253            (child.width - 100.0).abs() < 0.5,
1254            "the child is not: {child:?}"
1255        );
1256        assert!(
1257            (child.x + 30.0).abs() < 0.5 && (child.y + 30.0).abs() < 0.5,
1258            "centred on the box it hangs out of: {child:?}"
1259        );
1260    }
1261
1262    /// The first red rectangle in a finished scene.
1263    fn red_box_in(ui: &crate::Ui<()>) -> Rect {
1264        ui.scene()
1265            .primitives()
1266            .iter()
1267            .find_map(|p| match p {
1268                Primitive::Rect { rect, color, .. } if color.r > 0.5 && color.g < 0.5 => {
1269                    Some(*rect)
1270                }
1271                _ => None,
1272            })
1273            .expect("the red box")
1274    }
1275
1276    /// A red box that asks for all the room it is given, on both axes: what a centred child
1277    /// asks for is passed up through its container.
1278    fn expanding() -> Container<()> {
1279        Container::new()
1280            .color(RED)
1281            .child(crate::Center::new(SizedBox::empty()))
1282    }
1283
1284    /// **Where the room has no end**, down a scroll, the limit is the box: a child asking for
1285    /// all the room is 120 tall, and as wide as the scroll.
1286    #[test]
1287    fn a_limited_box_caps_a_child_where_the_room_has_no_end() {
1288        let root = crate::SingleChildScrollView::<()>::new()
1289            .width(200.0)
1290            .height(300.0)
1291            .child(Flex::column().child(LimitedBox::new(expanding()).max_height(120.0)));
1292        let rect = red_box(root, Size::new(200.0, 300.0));
1293        assert!((rect.height - 120.0).abs() < 0.5, "{rect:?}");
1294        assert!((rect.width - 200.0).abs() < 0.5, "{rect:?}");
1295    }
1296
1297    /// **Where the room has an end**, the limit changes nothing: on a page, the same child is
1298    /// the page.
1299    #[test]
1300    fn a_limited_box_leaves_a_bounded_room_alone() {
1301        let rect = red_box(
1302            LimitedBox::new(expanding()).max_height(120.0),
1303            Size::new(200.0, 300.0),
1304        );
1305        assert!((rect.height - 300.0).abs() < 0.5, "{rect:?}");
1306    }
1307
1308    /// **A child that hugs** still does: the limit is a ceiling, not a size.
1309    #[test]
1310    fn a_limited_box_does_not_grow_a_child_that_hugs() {
1311        let root = crate::SingleChildScrollView::<()>::new()
1312            .width(200.0)
1313            .height(300.0)
1314            .child(
1315                Flex::column().child(
1316                    LimitedBox::new(Container::new().width(40.0).height(20.0).color(RED))
1317                        .max_height(120.0),
1318                ),
1319            );
1320        let rect = red_box(root, Size::new(200.0, 300.0));
1321        assert!((rect.height - 20.0).abs() < 0.5, "{rect:?}");
1322        assert!((rect.width - 40.0).abs() < 0.5, "{rect:?}");
1323    }
1324
1325    /// **Across**, the same: along a horizontal scroll, a child asking for all the room is as
1326    /// wide as the limit.
1327    #[test]
1328    fn a_limited_box_caps_across_too() {
1329        let root = crate::SingleChildScrollView::<()>::new()
1330            .axis(crate::scroll::Axis::Horizontal)
1331            .width(300.0)
1332            .height(100.0)
1333            .child(Flex::row().child(LimitedBox::new(expanding()).max_width(90.0)));
1334        let rect = red_box(root, Size::new(300.0, 100.0));
1335        assert!((rect.width - 90.0).abs() < 0.5, "{rect:?}");
1336    }
1337
1338    /// **A paragraph wraps at the limit**, along a horizontal scroll where nothing else would
1339    /// stop it: it is as wide as the limit and several lines tall.
1340    #[test]
1341    fn a_paragraph_wraps_at_the_limit() {
1342        let words = "one two three four five six seven eight nine ten eleven twelve";
1343        let root = crate::SingleChildScrollView::<()>::new()
1344            .axis(crate::scroll::Axis::Horizontal)
1345            .width(300.0)
1346            .height(200.0)
1347            .child(
1348                Flex::row().child(
1349                    LimitedBox::new(Container::new().color(RED).child(Text::new(words)))
1350                        .max_width(120.0),
1351                ),
1352            );
1353        let rect = red_box(root, Size::new(300.0, 200.0));
1354        assert!((rect.width - 120.0).abs() < 0.5, "{rect:?}");
1355        assert!(rect.height > 50.0, "several lines: {rect:?}");
1356    }
1357
1358    /// **Inside a scroll**, the column holding it is as wide as the scroll because the box
1359    /// asks for it, and the box is that width. It was 0: a percentage of a column that was as
1360    /// wide as its content.
1361    #[test]
1362    fn a_sized_box_that_expands_is_as_wide_as_a_scroll() {
1363        let root = crate::SingleChildScrollView::<()>::new()
1364            .width(200.0)
1365            .height(300.0)
1366            .child(
1367                Flex::column().child(SizedBox::expand(Container::new().color(RED)).height(50.0)),
1368            );
1369        let rect = red_box(root, Size::new(200.0, 300.0));
1370        assert!((rect.width - 200.0).abs() < 0.5, "{rect:?}");
1371        assert!((rect.height - 50.0).abs() < 0.5, "{rect:?}");
1372    }
1373
1374    /// **Alone on a page**, it is the page; **beside a label in a row**, it shares the line
1375    /// and is as wide as its child, after the label.
1376    #[test]
1377    fn a_sized_box_that_expands_fills_a_page_and_shares_a_line() {
1378        let rect = red_box(SizedBox::expand(filler()), Size::new(200.0, 300.0));
1379        assert!(
1380            (rect.width - 200.0).abs() < 0.5 && (rect.height - 300.0).abs() < 0.5,
1381            "{rect:?}"
1382        );
1383        let row = Flex::<()>::row()
1384            .width(300.0)
1385            .height(100.0)
1386            .child(Text::new("label").no_wrap())
1387            .child(SizedBox::expand(Container::new().width(30.0).color(RED)));
1388        let rect = red_box(row, Size::new(300.0, 100.0));
1389        assert!((rect.width - 30.0).abs() < 0.5, "{rect:?}");
1390        assert!((rect.height - 100.0).abs() < 0.5, "{rect:?}");
1391        assert!(rect.x > 20.0, "after the label: {rect:?}");
1392    }
1393}