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//!
11//! The first three are ordinary layout nodes and cost nothing beyond what taffy
12//! already does. [`OverflowBox`] is not: its child is laid out **separately**, which
13//! is what lets the child be bigger than the box that holds it.
14
15use 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
22/// A box of a given size — or one that fills what it is given, or one that takes
23/// only what its child needs.
24///
25/// `SizedBox::empty()` with a size is also the plainest way to put a gap somewhere
26/// that has no gap of its own; with no child it draws nothing at all.
27pub 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    /// An empty box, `Auto` on both axes until a size is set.
35    pub fn empty() -> Self {
36        Self {
37            width: Dimension::Auto,
38            height: Dimension::Auto,
39            children: Vec::new(),
40        }
41    }
42
43    /// A box holding `child`, its size still to be chosen.
44    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    /// A `side`×`side` box holding `child`.
51    pub fn square(side: f32, child: impl Widget<Msg> + 'static) -> Self {
52        Self::new(child).width(side).height(side)
53    }
54
55    /// A box that **fills** the space on offer, on both axes, and hands it to
56    /// `child`.
57    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    /// A box that takes **only what its child needs**, on both axes — the useful
64    /// opposite of [`SizedBox::expand`] inside a container that would otherwise
65    /// stretch it.
66    pub fn shrink(child: impl Widget<Msg> + 'static) -> Self {
67        Self::new(child)
68    }
69
70    /// Sets the width, in logical pixels.
71    pub fn width(self, width: f32) -> Self {
72        self.width_dimension(Dimension::Length(width))
73    }
74
75    /// Sets the height, in logical pixels.
76    pub fn height(self, height: f32) -> Self {
77        self.height_dimension(Dimension::Length(height))
78    }
79
80    /// Sets the width as a fraction of the parent's (`1.0` = all of it).
81    pub fn width_fraction(self, fraction: f32) -> Self {
82        self.width_dimension(Dimension::Percent(fraction))
83    }
84
85    /// Sets the height as a fraction of the parent's.
86    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    /// Sets the child, replacing any already there.
101    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        // A pure layout widget: it draws nothing of its own.
123    }
124
125    fn on_click(&self) -> Option<Msg> {
126        None
127    }
128}
129
130/// Floors and ceilings on either axis, for a child that would otherwise be free to
131/// take any size.
132///
133/// A floor and a ceiling that are equal make the box **tight**: whatever the child
134/// or the space on offer says, that is the size. Set nothing and the box is
135/// transparent — it is the constraints that make it worth having, not the box.
136pub 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    /// An unconstrained box holding `child`; the constraints are set from here.
146    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    /// The same, for a child that is **already boxed** — what a slot holds once the
157    /// caller's type has been erased.
158    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    /// A box whose floor **is** its ceiling on both axes: exactly `width`×`height`.
169    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    /// A box with ceilings only: the child may be anything up to `width`×`height`.
178    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    /// The width the box never goes below.
183    pub fn min_width(mut self, width: f32) -> Self {
184        self.min_width = Dimension::Length(width);
185        self
186    }
187
188    /// The width the box never goes above.
189    pub fn max_width(mut self, width: f32) -> Self {
190        self.max_width = Dimension::Length(width);
191        self
192    }
193
194    /// The height the box never goes below.
195    pub fn min_height(mut self, height: f32) -> Self {
196        self.min_height = Dimension::Length(height);
197        self
198    }
199
200    /// The height the box never goes above.
201    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/// Which axis a box takes from its content's **preferred** size rather than from
230/// the space on offer.
231#[derive(Copy, Clone, Debug, PartialEq, Eq)]
232pub enum IntrinsicAxis {
233    /// The width.
234    Width,
235    /// The height.
236    Height,
237}
238
239/// A box sized to what its content would **like** to be.
240///
241/// The usual case: a column of buttons that should all be as wide as the widest
242/// label, and no wider. Stretching gives them the whole column; hugging gives each
243/// its own width; only an intrinsic width gives them all the widest one.
244///
245/// It is not free. The content is measured **once more**, unconstrained, before it
246/// is laid out for real — which is why it is a widget you reach for deliberately and
247/// not a property every box carries. Nested one inside another, the cost multiplies.
248pub struct Intrinsic<Msg = crate::callback::Callback> {
249    axis: IntrinsicAxis,
250    step: Option<f32>,
251    children: Vec<Box<dyn Widget<Msg>>>,
252}
253
254/// A box as wide as its content would like to be. See [`Intrinsic`].
255pub type IntrinsicWidth<Msg> = Intrinsic<Msg>;
256/// A box as tall as its content would like to be. See [`Intrinsic`].
257pub type IntrinsicHeight<Msg> = Intrinsic<Msg>;
258
259impl<Msg> Intrinsic<Msg> {
260    /// A box as **wide** as `child` would like to be.
261    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    /// A box as **tall** as `child` would like to be.
270    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    /// Rounds the measured size **up** to a multiple of `step`.
279    ///
280    /// For a box whose width should change in steps rather than continuously — a
281    /// label that grows a character at a time makes everything beside it jitter.
282    pub fn step(mut self, step: f32) -> Self {
283        self.step = (step > 0.0).then_some(step);
284        self
285    }
286
287    /// The axis this box takes from its content.
288    pub fn axis(&self) -> IntrinsicAxis {
289        self.axis
290    }
291
292    /// Applies the step rounding to a measured extent.
293    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        // The measured extent is written into this style by `build_layout`, which is
304        // the only place with a runtime to measure against.
305        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/// The constraints an [`OverflowBox`] hands its child, and where the result sits.
324#[derive(Copy, Clone, Debug, PartialEq)]
325pub struct Overflow {
326    /// Width given to the child; `None` = the box's own width.
327    pub width: Option<f32>,
328    /// Height given to the child; `None` = the box's own height.
329    pub height: Option<f32>,
330    /// Lay the child out **unconstrained**, at the size it asks for. Overrides the
331    /// two above.
332    pub unconstrained: bool,
333    /// Where the child sits inside the box, and therefore which way it spills.
334    pub alignment: Alignment,
335}
336
337/// A child laid out to constraints of **its own**, free to be bigger than the box
338/// that holds it, and painted over whatever is around it.
339///
340/// The box itself takes the size it would have taken with no child at all, so
341/// nothing around it moves. What changes is the child: it is laid out separately —
342/// at a size given here, or at the size it asks for
343/// ([`OverflowBox::unconstrained`]) — then anchored in the box and allowed to spill
344/// past its edges.
345///
346/// This is the escape hatch for a background that should bleed past its slot, or a
347/// decoration wider than the row it belongs to. It does **not** clip: put a
348/// [`crate::ClipRRect`] above it if the spill should stop somewhere.
349pub struct OverflowBox<Msg = crate::callback::Callback> {
350    overflow: Overflow,
351    children: Vec<Box<dyn Widget<Msg>>>,
352}
353
354impl<Msg> OverflowBox<Msg> {
355    /// A box that lays `child` out to the sizes given by the builders below,
356    /// centred; unset axes keep the box's own size.
357    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    /// A box that lets `child` be **whatever size it asks for**, as if nothing were
370    /// constraining it, and centres the result.
371    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    /// The width to lay the child out at.
378    pub fn width(mut self, width: f32) -> Self {
379        self.overflow.width = Some(width);
380        self
381    }
382
383    /// The height to lay the child out at.
384    pub fn height(mut self, height: f32) -> Self {
385        self.overflow.height = Some(height);
386        self
387    }
388
389    /// Where the child sits in the box — and so which edges it spills past.
390    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        // The box takes **the largest size it is allowed**, because its child is laid
399        // out separately and so contributes nothing to its size. Left to hug, it would
400        // collapse to nothing on the main axis, and the child would be anchored to a
401        // box of zero width — spilling equally in both directions from a point.
402        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    /// The size to lay the child out at, given the box's own, and whether the
426    /// measurement should be unconstrained.
427    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    /// Where the child's top-left corner goes, given both sizes.
435    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
443/// A box of a **stated size** whose child is laid out at another one, and spills.
444///
445/// The size given here is what the parent sees and what the neighbours make room for.
446/// The child is laid out separately — at the size it asks for, or at one stated with
447/// [`SizedOverflowBox::child_size`] — anchored in the box by its alignment, and allowed
448/// past every edge.
449///
450/// It is [`OverflowBox`] with the hole given a size: an overflow box fills whatever it is
451/// offered, because nothing else could anchor a child that contributes no size, and this
452/// is the form for a slot narrower than the space around it — a 24 px gap in a toolbar
453/// showing a 40 px control, a caption strip under an image that reaches past both sides.
454///
455/// It does **not** clip: put a [`crate::ClipRRect`] above it if the spill should stop.
456pub 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    /// A `width`×`height` box holding `child`, which is laid out at **its own** natural
465    /// size and centred.
466    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    /// Lays the child out at a size of its own rather than at the one it asks for.
481    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    /// Where the child sits in the box — and so which edges it spills past.
489    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/// What a [`ConstraintsTransformBox`] does to **one axis** of the space on offer.
524#[derive(Clone, Copy, Debug, PartialEq)]
525pub enum AxisConstraint {
526    /// As it came: the child is given the room the parent offered.
527    AsGiven,
528    /// Taken away: the child is asked how big it would like to be, and may come back
529    /// bigger than the room there was.
530    Unbounded,
531    /// A number of its own, whatever was offered.
532    Fixed(f32),
533}
534
535/// What a [`ConstraintsTransformBox`] gives its child, per axis, and what it does about a
536/// child that comes back too big.
537///
538/// It is a small vocabulary rather than an arbitrary function of the space on offer, and
539/// that is a decision worth stating. The layout and the paint walk are two passes over the
540/// same tree, and only the first is told what the parent offered: by the time the walk lays
541/// the child out, the box has been sized to the child and the offer is not recoverable from
542/// it. A function of the offer could not be run again there — and a function run again on
543/// the wrong input answers plausibly, which is worse than not answering at all. Each of
544/// these three **can** be run again: a fixed number is itself, an unbounded axis is a
545/// question with no input, and an axis given as it came produced a child of exactly the
546/// box's own size.
547///
548/// The reference's transform is a closure, and every transform the reference itself ships
549/// is one of these three.
550#[derive(Clone, Copy, Debug)]
551pub struct ConstraintsTransform {
552    /// What the child is given across.
553    pub width: AxisConstraint,
554    /// And down.
555    pub height: AxisConstraint,
556    /// Where a child smaller than the box sits in it.
557    pub alignment: Alignment,
558    /// Whether a child that came out **bigger** than the box is reported as an overflow —
559    /// the yellow and black band, in debug builds. On by default: a box that transforms its
560    /// constraints is asking a question about what fits, and a silent answer is no answer.
561    pub report: bool,
562}
563
564impl ConstraintsTransform {
565    /// Both axes as they came, which is the transform that does nothing.
566    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    /// What the child is given across.
576    pub fn width(mut self, width: AxisConstraint) -> Self {
577        self.width = width;
578        self
579    }
580
581    /// What the child is given down.
582    pub fn height(mut self, height: AxisConstraint) -> Self {
583        self.height = height;
584        self
585    }
586
587    /// Where a child smaller than the box sits in it.
588    pub fn alignment(mut self, alignment: Alignment) -> Self {
589        self.alignment = alignment;
590        self
591    }
592
593    /// Whether a child bigger than the box wears a band. Off is what a deliberate spill
594    /// wants.
595    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    /// `(extent, free)` for the **measurement**, given what the layout offered on this
609    /// axis — `None` being no limit at all.
610    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    /// `(extent, free)` for the **walk**, given the box the measurement produced.
622    ///
623    /// It answers the same thing the measurement did, from what is left of it: a fixed
624    /// number is itself, an unbounded axis is the same question either way, and an axis
625    /// given as it came produced a child of the box's own extent — the box being that child
626    /// held to what was offered.
627    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
636/// A box that **changes the space on offer** before its child is laid out in it, and is
637/// then as big as what came back — never bigger than what it was itself offered.
638///
639/// This is the general form of the boxes above, and the one they are special cases of: what
640/// the child is given, per axis, said in [`AxisConstraint`]. Taking a ceiling away lets the
641/// child be its natural size ([`UnconstrainedBox`]); a fixed number lays it out in a room
642/// of that size whatever the parent offered.
643///
644/// ```ignore
645/// // "However tall you need to be" — and a band across the box if that was too tall.
646/// ConstraintsTransformBox::new(paragraph).height(AxisConstraint::Unbounded)
647/// ```
648///
649/// A child that comes out bigger than the box **overflows it**: painted past the edges, and
650/// reported with the debug band. That is the point of the widget — it asks what the space
651/// on offer is doing to the content, and answers on the screen. Turn the band off with
652/// [`ConstraintsTransformBox::report`] when the spill is deliberate.
653pub struct ConstraintsTransformBox<Msg = crate::callback::Callback> {
654    transform: ConstraintsTransform,
655    children: Vec<Box<dyn Widget<Msg>>>,
656}
657
658impl<Msg> ConstraintsTransformBox<Msg> {
659    /// A box holding `child`, with both axes still as they came.
660    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    /// What the child is given across.
668    pub fn width(mut self, width: AxisConstraint) -> Self {
669        self.transform.width = width;
670        self
671    }
672
673    /// What the child is given down.
674    pub fn height(mut self, height: AxisConstraint) -> Self {
675        self.transform.height = height;
676        self
677    }
678
679    /// Where a child smaller than the box sits in it.
680    pub fn alignment(mut self, alignment: Alignment) -> Self {
681        self.transform.alignment = alignment;
682        self
683    }
684
685    /// Whether a child bigger than the box wears a band.
686    pub fn report(mut self, report: bool) -> Self {
687        self.transform.report = report;
688        self
689    }
690
691    /// A box built from a transform prepared elsewhere.
692    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
724/// Lets its child be its **natural size** even where the parent offers less — and says so
725/// when that does not fit.
726///
727/// The child is laid out as if nothing were constraining it, and the box is as big as the
728/// child, up to the space it was itself offered. Where the child fits, nothing is
729/// unusual — the box is simply the child's size, hugging it in a place that would have
730/// stretched it. Where it does not, the child is painted past the edges and a band says by
731/// how much, in debug builds.
732///
733/// ```ignore
734/// // A row of chips that must not be squashed: too many, and the overflow is visible.
735/// UnconstrainedBox::new(Flex::row().child(chip_a).child(chip_b))
736/// ```
737///
738/// That visible failure is the point, and the difference between this and [`OverflowBox`],
739/// which spills on purpose and quietly. It is [`ConstraintsTransformBox`] with the
740/// constraint taken away, and nothing more.
741///
742/// [`UnconstrainedBox::axis`] frees **one** axis and leaves the other as it was, which is
743/// usually what is wanted: a paragraph that may be as tall as it likes is still as wide as
744/// its column.
745pub struct UnconstrainedBox;
746
747impl UnconstrainedBox {
748    /// Frees both axes.
749    #[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    /// Frees the given axis, and leaves the other one as the parent offered it.
759    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    /// A child that fills whatever box it is put in, painted red so the tests below
786    /// can read that box off the scene. `Align::Stretch` gives it the cross axis;
787    /// `flex(1)` gives it the main one.
788    fn filler() -> Container<()> {
789        Container::new().flex(1.0).color(RED)
790    }
791
792    /// The first red rectangle painted.
793    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        // Stretched by its column, the box would be 400 wide; capped, it is 120.
811        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        // In a row the box hugs its content, which asks for nothing; the floor is
821        // the only thing giving it a width.
822        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        // Stretched by the 400 px column, the inner block would be 400 wide. Taken
870        // intrinsically, it is as wide as the line inside it — and much less.
871        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        // With no step, the measurement passes through untouched.
890        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        // A 40 px slot holding a 100 px child: it hangs 30 px off either side.
897        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        // The neighbour below sits at 40, not at 100: the box did not grow, which is
934        // the whole difference between an overflow and a big child.
935        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        // The slot is 20 px; the child asks for 90×30 and gets it.
952        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    /// The scene and the inspector's boxes, for a tree laid out in `available`.
968    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    /// The box the walk gave the widget named `name` — the box itself, which is the thing
973    /// these widgets are about and the one thing the scene does not show.
974    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    /// A red box of a stated size — laid out at whatever it is given, and visible.
983    fn red(width: f32, height: f32) -> Container<()> {
984        Container::new().width(width).height(height).color(RED)
985    }
986
987    /// A column that does **not** stretch its children, so a box that hugs can be seen
988    /// hugging.
989    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    /// The other half, and the reason the widget is worth having: the child keeps the
1008    /// size it asked for, the box keeps the size it was offered, and the difference is
1009    /// **reported** rather than quietly absorbed.
1010    #[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        // Centred, it runs past **both** sides, and both are reported: a band on one of
1025        // them would say the child was a hundred too wide rather than two.
1026        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    /// One axis freed, the other left as it was: the child is as tall as it likes and no
1039    /// wider than the column, which is what a paragraph wants.
1040    #[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    /// Unbounded is only one of the three things an axis can say: this one gives the
1059    /// child a hundred pixels of room in a column twice that wide, and the box comes back
1060    /// the size of what it asked for.
1061    #[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    /// The first red rectangle in a finished scene.
1100    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}