Skip to main content

egui_reactor/
layout.rs

1//! Layout attributes: [`Length`], [`ItemStyle`] and [`ContainerStyle`].
2//!
3//! These are the types every element accepts as layout props. They exist so
4//! that `rsx!` can hand string literals (`justify="space-between"`) and numbers
5//! (`gap={8}`) straight to a setter, and so that the conversion to
6//! [`taffy::Style`] lives in one place.
7//!
8//! How a box *looks* rides along in the same prop: [`ItemStyle::paint`] is a
9//! [`PaintStyle`], with forwarding setters (`bg`, `border`, `radius`, ...), so
10//! no element signature changes to gain a background. Only the border reaches
11//! taffy — the layout has to reserve the width of the stroke — and the engine
12//! paints the rest once the layout is solved.
13
14pub use crate::paint::PaintStyle;
15
16/// A length in a layout attribute.
17///
18/// `Percent` holds a fraction, like taffy: `Length::Percent(0.5)` is `50%`.
19/// The `From<&str>` impl parses the CSS-ish spellings (`"50%"`, `"auto"`,
20/// `"12px"`, `"12"`).
21#[derive(Clone, Copy, Debug, Default, PartialEq)]
22pub enum Length {
23    /// An absolute length in egui points.
24    Px(f32),
25    /// A fraction of the containing block (`0.5` is `50%`).
26    Percent(f32),
27    /// Sized by the layout algorithm.
28    #[default]
29    Auto,
30}
31
32impl From<f32> for Length {
33    fn from(v: f32) -> Self {
34        Self::Px(v)
35    }
36}
37
38impl From<i32> for Length {
39    fn from(v: i32) -> Self {
40        Self::Px(v as f32)
41    }
42}
43
44impl From<&str> for Length {
45    /// # Panics
46    /// Panics on anything that is not `"auto"`, `"<n>%"`, `"<n>px"` or `"<n>"`.
47    fn from(s: &str) -> Self {
48        let t = s.trim();
49        if t == "auto" {
50            return Self::Auto;
51        }
52        if let Some(n) = t.strip_suffix('%') {
53            return Self::Percent(parse_number(n, s) / 100.0);
54        }
55        let n = t.strip_suffix("px").unwrap_or(t);
56        Self::Px(parse_number(n, s))
57    }
58}
59
60fn parse_number(n: &str, whole: &str) -> f32 {
61    n.trim().parse::<f32>().unwrap_or_else(|_| {
62        panic!("egui-reactor: cannot parse {whole:?} as a length (expected \"auto\", \"50%\", \"12px\" or \"12\")")
63    })
64}
65
66impl Length {
67    /// As a taffy `Dimension` (`width` / `height` / `flex-basis`).
68    pub fn to_dimension(self) -> taffy::Dimension {
69        match self {
70            Self::Px(v) => taffy::Dimension::length(v),
71            Self::Percent(v) => taffy::Dimension::percent(v),
72            Self::Auto => taffy::Dimension::auto(),
73        }
74    }
75
76    /// As a taffy `LengthPercentageAuto` (`margin`).
77    pub fn to_length_percentage_auto(self) -> taffy::LengthPercentageAuto {
78        match self {
79            Self::Px(v) => taffy::LengthPercentageAuto::length(v),
80            Self::Percent(v) => taffy::LengthPercentageAuto::percent(v),
81            Self::Auto => taffy::LengthPercentageAuto::auto(),
82        }
83    }
84
85    /// As a taffy `LengthPercentage` (`padding`). `Auto` becomes zero.
86    pub fn to_length_percentage(self) -> taffy::LengthPercentage {
87        match self {
88            Self::Px(v) => taffy::LengthPercentage::length(v),
89            Self::Percent(v) => taffy::LengthPercentage::percent(v),
90            Self::Auto => taffy::LengthPercentage::length(0.0),
91        }
92    }
93}
94
95/// Build a layout enum together with its `From<&str>` parser.
96macro_rules! str_enum {
97    (
98        $(#[$meta:meta])*
99        pub enum $name:ident {
100            $( $(#[$vmeta:meta])* $variant:ident = $text:literal ),* $(,)?
101        }
102    ) => {
103        $(#[$meta])*
104        #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
105        pub enum $name {
106            $( $(#[$vmeta])* $variant, )*
107        }
108
109        impl $name {
110            /// Parse the CSS-ish spelling, returning `None` if unknown.
111            pub fn parse(s: &str) -> Option<Self> {
112                match s.trim() {
113                    $( $text => Some(Self::$variant), )*
114                    _ => None,
115                }
116            }
117        }
118
119        impl From<&str> for $name {
120            /// # Panics
121            /// Panics on an unknown spelling.
122            fn from(s: &str) -> Self {
123                Self::parse(s).unwrap_or_else(|| {
124                    panic!(
125                        "egui-reactor: {:?} is not a valid {} (expected one of: {})",
126                        s,
127                        stringify!($name),
128                        [$($text),*].join(", "),
129                    )
130                })
131            }
132        }
133    };
134}
135
136str_enum! {
137    /// `flex-direction`.
138    pub enum Direction {
139        #[default] Row = "row",
140        Column = "column",
141        RowReverse = "row-reverse",
142        ColumnReverse = "column-reverse",
143    }
144}
145
146str_enum! {
147    /// `justify-content` / `align-content`. `Normal` leaves it unset.
148    pub enum Justify {
149        #[default] Normal = "normal",
150        Start = "start",
151        End = "end",
152        FlexStart = "flex-start",
153        FlexEnd = "flex-end",
154        Center = "center",
155        Stretch = "stretch",
156        SpaceBetween = "space-between",
157        SpaceEvenly = "space-evenly",
158        SpaceAround = "space-around",
159    }
160}
161
162str_enum! {
163    /// `align-items` / `align-self`. `Normal` leaves it unset.
164    pub enum Align {
165        #[default] Normal = "normal",
166        Start = "start",
167        End = "end",
168        FlexStart = "flex-start",
169        FlexEnd = "flex-end",
170        Center = "center",
171        Baseline = "baseline",
172        Stretch = "stretch",
173    }
174}
175
176str_enum! {
177    /// `display`.
178    pub enum Display {
179        #[default] Flex = "flex",
180        Grid = "grid",
181        Block = "block",
182        None = "none",
183    }
184}
185
186/// `align-self`, which taffy spells with the same type as `align-items`.
187pub type AlignSelf = Align;
188
189impl Direction {
190    /// The taffy equivalent.
191    pub fn to_taffy(self) -> taffy::FlexDirection {
192        match self {
193            Self::Row => taffy::FlexDirection::Row,
194            Self::Column => taffy::FlexDirection::Column,
195            Self::RowReverse => taffy::FlexDirection::RowReverse,
196            Self::ColumnReverse => taffy::FlexDirection::ColumnReverse,
197        }
198    }
199}
200
201impl Justify {
202    /// The taffy equivalent, or `None` for `Normal`.
203    pub fn to_taffy(self) -> Option<taffy::AlignContent> {
204        Some(match self {
205            Self::Normal => return None,
206            Self::Start => taffy::AlignContent::Start,
207            Self::End => taffy::AlignContent::End,
208            Self::FlexStart => taffy::AlignContent::FlexStart,
209            Self::FlexEnd => taffy::AlignContent::FlexEnd,
210            Self::Center => taffy::AlignContent::Center,
211            Self::Stretch => taffy::AlignContent::Stretch,
212            Self::SpaceBetween => taffy::AlignContent::SpaceBetween,
213            Self::SpaceEvenly => taffy::AlignContent::SpaceEvenly,
214            Self::SpaceAround => taffy::AlignContent::SpaceAround,
215        })
216    }
217}
218
219impl Align {
220    /// The taffy equivalent, or `None` for `Normal`.
221    pub fn to_taffy(self) -> Option<taffy::AlignItems> {
222        Some(match self {
223            Self::Normal => return None,
224            Self::Start => taffy::AlignItems::Start,
225            Self::End => taffy::AlignItems::End,
226            Self::FlexStart => taffy::AlignItems::FlexStart,
227            Self::FlexEnd => taffy::AlignItems::FlexEnd,
228            Self::Center => taffy::AlignItems::Center,
229            Self::Baseline => taffy::AlignItems::Baseline,
230            Self::Stretch => taffy::AlignItems::Stretch,
231        })
232    }
233}
234
235impl Display {
236    /// The taffy equivalent.
237    pub fn to_taffy(self) -> taffy::Display {
238        match self {
239            Self::Flex => taffy::Display::Flex,
240            Self::Grid => taffy::Display::Grid,
241            Self::Block => taffy::Display::Block,
242            Self::None => taffy::Display::None,
243        }
244    }
245}
246
247/// The layout attributes every element accepts, as a flex/grid *item*.
248///
249/// Every setter takes `impl Into<Length>`, so `.w(120.0)`, `.w("50%")` and
250/// `.w(Length::Auto)` all work; `rsx!` passes literals straight through.
251///
252/// It carries the item's [`PaintStyle`] too, reached through setters of the
253/// same names (`.bg(..)`, `.border(..)`), so one prop is the whole vocabulary.
254#[derive(Clone, Copy, Debug, Default, PartialEq)]
255pub struct ItemStyle {
256    /// `width`.
257    pub w: Option<Length>,
258    /// `height`.
259    pub h: Option<Length>,
260    /// `min-width`.
261    pub min_w: Option<Length>,
262    /// `min-height`.
263    pub min_h: Option<Length>,
264    /// `max-width`.
265    pub max_w: Option<Length>,
266    /// `max-height`.
267    pub max_h: Option<Length>,
268    /// `flex-grow`.
269    pub grow: Option<f32>,
270    /// `flex-shrink`.
271    pub shrink: Option<f32>,
272    /// `flex-basis`.
273    pub basis: Option<Length>,
274    /// `align-self`.
275    pub align_self: Option<AlignSelf>,
276    /// `margin` (all sides).
277    pub m: Option<Length>,
278    /// `margin-inline`.
279    pub mx: Option<Length>,
280    /// `margin-block`.
281    pub my: Option<Length>,
282    /// `margin-top`.
283    pub mt: Option<Length>,
284    /// `margin-right`.
285    pub mr: Option<Length>,
286    /// `margin-bottom`.
287    pub mb: Option<Length>,
288    /// `margin-left`.
289    pub ml: Option<Length>,
290    /// `padding` (all sides).
291    pub p: Option<Length>,
292    /// `padding-inline`.
293    pub px: Option<Length>,
294    /// `padding-block`.
295    pub py: Option<Length>,
296    /// `padding-top`.
297    pub pt: Option<Length>,
298    /// `padding-right`.
299    pub pr: Option<Length>,
300    /// `padding-bottom`.
301    pub pb: Option<Length>,
302    /// `padding-left`.
303    pub pl: Option<Length>,
304    /// `grid-column: span N`.
305    pub col_span: Option<u16>,
306    /// `grid-row: span N`.
307    pub row_span: Option<u16>,
308    /// How the box looks: background, border, radius, shadow, opacity.
309    pub paint: PaintStyle,
310}
311
312macro_rules! length_setters {
313    ($( $(#[$meta:meta])* $name:ident ),* $(,)?) => {
314        $(
315            $(#[$meta])*
316            #[must_use]
317            pub fn $name(mut self, v: impl Into<Length>) -> Self {
318                self.$name = Some(v.into());
319                self
320            }
321        )*
322    };
323}
324
325impl ItemStyle {
326    length_setters!(
327        /// Set `width`.
328        w,
329        /// Set `height`.
330        h,
331        /// Set `min-width`.
332        min_w,
333        /// Set `min-height`.
334        min_h,
335        /// Set `max-width`.
336        max_w,
337        /// Set `max-height`.
338        max_h,
339        /// Set `flex-basis`.
340        basis,
341        /// Set the margin on all four sides.
342        m,
343        /// Set the left and right margin.
344        mx,
345        /// Set the top and bottom margin.
346        my,
347        /// Set the top margin.
348        mt,
349        /// Set the right margin.
350        mr,
351        /// Set the bottom margin.
352        mb,
353        /// Set the left margin.
354        ml,
355        /// Set the padding on all four sides.
356        p,
357        /// Set the left and right padding.
358        px,
359        /// Set the top and bottom padding.
360        py,
361        /// Set the top padding.
362        pt,
363        /// Set the right padding.
364        pr,
365        /// Set the bottom padding.
366        pb,
367        /// Set the left padding.
368        pl,
369    );
370
371    /// Set `flex-grow`.
372    #[must_use]
373    pub fn grow(mut self, v: f32) -> Self {
374        self.grow = Some(v);
375        self
376    }
377
378    /// Set `flex-shrink`.
379    #[must_use]
380    pub fn shrink(mut self, v: f32) -> Self {
381        self.shrink = Some(v);
382        self
383    }
384
385    /// Set `align-self`.
386    #[must_use]
387    pub fn align_self(mut self, v: impl Into<AlignSelf>) -> Self {
388        self.align_self = Some(v.into());
389        self
390    }
391
392    /// Set how many grid columns this item spans.
393    #[must_use]
394    pub fn col_span(mut self, v: u16) -> Self {
395        self.col_span = Some(v);
396        self
397    }
398
399    /// Set how many grid rows this item spans.
400    #[must_use]
401    pub fn row_span(mut self, v: u16) -> Self {
402        self.row_span = Some(v);
403        self
404    }
405
406    /// Set the background colour. See [`PaintStyle::bg`].
407    #[must_use]
408    pub fn bg(mut self, v: impl Into<egui::Color32>) -> Self {
409        self.paint = self.paint.bg(v);
410        self
411    }
412
413    /// Set the border stroke. See [`PaintStyle::border`].
414    #[must_use]
415    pub fn border(mut self, v: impl Into<egui::Stroke>) -> Self {
416        self.paint = self.paint.border(v);
417        self
418    }
419
420    /// Set the corner radius. See [`PaintStyle::radius`].
421    #[must_use]
422    pub fn radius(mut self, v: f32) -> Self {
423        self.paint = self.paint.radius(v);
424        self
425    }
426
427    /// Cast the theme's window shadow. See [`PaintStyle::shadow`].
428    #[must_use]
429    pub fn shadow(mut self, v: bool) -> Self {
430        self.paint = self.paint.shadow(v);
431        self
432    }
433
434    /// Cast a shadow of your own. See [`PaintStyle::custom_shadow`].
435    #[must_use]
436    pub fn custom_shadow(mut self, v: egui::Shadow) -> Self {
437        self.paint = self.paint.custom_shadow(v);
438        self
439    }
440
441    /// Multiply the opacity of this node. See [`PaintStyle::opacity`].
442    #[must_use]
443    pub fn opacity(mut self, v: f32) -> Self {
444        self.paint = self.paint.opacity(v);
445        self
446    }
447
448    /// The padding this item asks for, in points, if it asked for any.
449    ///
450    /// `[top, right, bottom, left]`, resolved most-specific-wins exactly as
451    /// [`ItemStyle::to_taffy`] resolves it. `None` when no padding prop is set
452    /// — there is nothing to take over — and when any side is a percentage or
453    /// `auto`, because those need the containing block, which only the layout
454    /// has.
455    ///
456    /// This is for an element that hands its padding to the widget rather than
457    /// to the layout: `<Button>` does, so that the hover frame and the click
458    /// area cover the whole box, and `<Frame>` does outside a tree, where there
459    /// is no layout to hold it.
460    pub fn padding_px(&self) -> Option<[f32; 4]> {
461        let sides = [self.p, self.px, self.py, self.pt, self.pr, self.pb, self.pl];
462        if sides.iter().all(Option::is_none) {
463            return None;
464        }
465        let rect = resolve_rect(
466            sides,
467            |v| match v {
468                Length::Px(v) => Some(v),
469                _ => None,
470            },
471            Some(0.0),
472        );
473        Some([rect.top?, rect.right?, rect.bottom?, rect.left?])
474    }
475
476    /// This style with every padding prop cleared.
477    ///
478    /// The other half of [`ItemStyle::padding_px`]: an element that took the
479    /// padding over passes this to `cx.leaf`, so the layout does not reserve
480    /// the same points a second time.
481    #[must_use]
482    pub fn without_padding(mut self) -> Self {
483        self.p = None;
484        self.px = None;
485        self.py = None;
486        self.pt = None;
487        self.pr = None;
488        self.pb = None;
489        self.pl = None;
490        self
491    }
492
493    /// The taffy style of this item, with container properties left default.
494    pub fn to_taffy(&self) -> taffy::Style {
495        let dim = |v: Option<Length>| {
496            v.map(Length::to_dimension)
497                .unwrap_or(taffy::Dimension::auto())
498        };
499        let mut style = taffy::Style {
500            size: taffy::Size {
501                width: dim(self.w),
502                height: dim(self.h),
503            },
504            min_size: taffy::Size {
505                width: dim(self.min_w),
506                height: dim(self.min_h),
507            },
508            max_size: taffy::Size {
509                width: dim(self.max_w),
510                height: dim(self.max_h),
511            },
512            margin: resolve_rect(
513                [self.m, self.mx, self.my, self.mt, self.mr, self.mb, self.ml],
514                Length::to_length_percentage_auto,
515                taffy::LengthPercentageAuto::length(0.0),
516            ),
517            padding: resolve_rect(
518                [self.p, self.px, self.py, self.pt, self.pr, self.pb, self.pl],
519                Length::to_length_percentage,
520                taffy::LengthPercentage::length(0.0),
521            ),
522            ..Default::default()
523        };
524        if let Some(grow) = self.grow {
525            style.flex_grow = grow;
526        }
527        if let Some(shrink) = self.shrink {
528            style.flex_shrink = shrink;
529        }
530        if let Some(basis) = self.basis {
531            style.flex_basis = basis.to_dimension();
532        }
533        if let Some(align_self) = self.align_self {
534            style.align_self = align_self.to_taffy();
535        }
536        if let Some(span) = self.col_span {
537            style.grid_column = taffy::style_helpers::span(span);
538        }
539        if let Some(span) = self.row_span {
540            style.grid_row = taffy::style_helpers::span(span);
541        }
542        // A border is the only paint the layout has to know about: the stroke
543        // needs a band of its own, or it is drawn over the content. taffy keeps
544        // `border` beside `padding` and `content_rect` subtracts both, so the
545        // children (or the widget) start inside the stroke.
546        if let Some(border) = self.paint.border {
547            style.border = taffy::Rect::length(border.width);
548        }
549        style
550    }
551}
552
553/// Resolve `[all, x, y, top, right, bottom, left]` into a taffy `Rect`.
554///
555/// The shorthand order is `all` -> `x` / `y` -> the individual side, so the
556/// most specific value written wins.
557fn resolve_rect<T: Copy>(
558    [all, x, y, top, right, bottom, left]: [Option<Length>; 7],
559    convert: impl Fn(Length) -> T,
560    zero: T,
561) -> taffy::Rect<T> {
562    let pick = |specific: Option<Length>, axis: Option<Length>| {
563        specific.or(axis).or(all).map(&convert).unwrap_or(zero)
564    };
565    taffy::Rect {
566        top: pick(top, y),
567        right: pick(right, x),
568        bottom: pick(bottom, y),
569        left: pick(left, x),
570    }
571}
572
573/// The `gap` prop of a container: one value for both axes, or one per axis.
574#[derive(Clone, Copy, Debug, Default, PartialEq)]
575pub struct Gap {
576    /// Space between columns, in points.
577    pub column: f32,
578    /// Space between rows, in points.
579    pub row: f32,
580}
581
582impl From<f32> for Gap {
583    fn from(v: f32) -> Self {
584        Self { column: v, row: v }
585    }
586}
587
588impl From<i32> for Gap {
589    fn from(v: i32) -> Self {
590        Self::from(v as f32)
591    }
592}
593
594impl From<(f32, f32)> for Gap {
595    fn from((column, row): (f32, f32)) -> Self {
596        Self { column, row }
597    }
598}
599
600/// The layout attributes of a container (`<View>`), as a flex/grid *parent*.
601#[derive(Clone, Debug, Default, PartialEq)]
602pub struct ContainerStyle {
603    /// `display`.
604    pub display: Display,
605    /// `flex-direction`.
606    pub direction: Direction,
607    /// `flex-wrap`.
608    pub wrap: bool,
609    /// `justify-content`.
610    pub justify: Justify,
611    /// `align-items`.
612    pub align: Align,
613    /// `align-content`.
614    pub align_content: Option<Justify>,
615    /// `column-gap` and `row-gap`, in points.
616    pub gap: (f32, f32),
617    /// Number of equal-width grid columns, for `display="grid"`.
618    pub cols: Option<u16>,
619}
620
621impl ContainerStyle {
622    /// Set `display`.
623    #[must_use]
624    pub fn display(mut self, v: impl Into<Display>) -> Self {
625        self.display = v.into();
626        self
627    }
628
629    /// Set `flex-direction`.
630    #[must_use]
631    pub fn direction(mut self, v: impl Into<Direction>) -> Self {
632        self.direction = v.into();
633        self
634    }
635
636    /// Set `flex-wrap`.
637    #[must_use]
638    pub fn wrap(mut self, v: bool) -> Self {
639        self.wrap = v;
640        self
641    }
642
643    /// Set `justify-content`.
644    #[must_use]
645    pub fn justify(mut self, v: impl Into<Justify>) -> Self {
646        self.justify = v.into();
647        self
648    }
649
650    /// Set `align-items`.
651    #[must_use]
652    pub fn align(mut self, v: impl Into<Align>) -> Self {
653        self.align = v.into();
654        self
655    }
656
657    /// Set `align-content`.
658    #[must_use]
659    pub fn align_content(mut self, v: impl Into<Justify>) -> Self {
660        self.align_content = Some(v.into());
661        self
662    }
663
664    /// Set the column and row gap.
665    #[must_use]
666    pub fn gap(mut self, v: impl Into<Gap>) -> Self {
667        let gap = v.into();
668        self.gap = (gap.column, gap.row);
669        self
670    }
671
672    /// Set the column and row gap separately.
673    #[must_use]
674    pub fn gaps(mut self, column: f32, row: f32) -> Self {
675        self.gap = (column, row);
676        self
677    }
678
679    /// Set the number of equal-width grid columns.
680    #[must_use]
681    pub fn cols(mut self, v: u16) -> Self {
682        self.cols = Some(v);
683        self
684    }
685
686    /// The taffy style of a node that is both this container and `item`.
687    ///
688    /// A taffy node carries its own item properties and its children's
689    /// container properties in one `Style`, so the two halves are merged here.
690    pub fn merge(&self, item: &ItemStyle) -> taffy::Style {
691        let mut style = item.to_taffy();
692        style.display = self.display.to_taffy();
693        style.flex_direction = self.direction.to_taffy();
694        style.flex_wrap = if self.wrap {
695            taffy::FlexWrap::Wrap
696        } else {
697            taffy::FlexWrap::NoWrap
698        };
699        style.justify_content = self.justify.to_taffy();
700        style.align_items = self.align.to_taffy();
701        style.align_content = self.align_content.and_then(Justify::to_taffy);
702        style.gap = taffy::Size {
703            width: taffy::LengthPercentage::length(self.gap.0),
704            height: taffy::LengthPercentage::length(self.gap.1),
705        };
706        if self.display == Display::Grid
707            && let Some(cols) = self.cols
708        {
709            style.grid_template_columns = taffy::style_helpers::evenly_sized_tracks(cols);
710        }
711        style
712    }
713}
714
715/// The taffy style of the root container: a column that fills the window.
716///
717/// `reserve_available_space` tells the layout engine how much room there is,
718/// but it leaves the root node's own `size` at `auto`, so taffy would size that
719/// node by its content. Two things go wrong then. A `<View grow={1.0}
720/// justify="center">` child finds no free space to grow into and nothing to be
721/// centred in, so the app sits in the window's top-left corner. And a child
722/// too wide to fit never has to shrink, because a content-sized parent simply
723/// grows with it and there is no overflow to resolve — the row runs off the
724/// right edge instead of `grow` and `flex-shrink` sharing out what there is.
725///
726/// So both sides are fixed at 100%: a window is exactly as big as it is. The
727/// height used to be only a *minimum* of 100%, so that a column taller than
728/// the window would lay out at its own height. That let a `leaf_fill` (a
729/// `<ScrollArea>`, a `<VirtualList>`) push the root past the window: such a
730/// leaf reports the whole root height as its content size, so a column of
731/// "a header, then a list that fills the rest" measured as header plus window,
732/// and the root grew to fit — the list's last rows sat below the window edge.
733/// With a definite height the header keeps its content height (a flex item's
734/// automatic minimum) and the list gets what is left. Content taller than the
735/// window still overflows it rather than being shrunk, for the same reason,
736/// and belongs in a `ScrollArea` as it always did.
737///
738/// Exposed so a runner, a test or an element that roots a tree of its own
739/// (`<Overlay>`) all use the same style.
740pub fn root_style() -> taffy::Style {
741    ContainerStyle::default()
742        .direction("column")
743        .merge(&ItemStyle::default().w("100%").h("100%"))
744}