Skip to main content

slint_interpreter/
eval_layout.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use crate::Value;
5use crate::dynamic_item_tree::InstanceRef;
6use crate::eval::{self, EvalLocalContext};
7use i_slint_compiler::expression_tree::Expression;
8use i_slint_compiler::langtype::Type;
9use i_slint_compiler::layout::{
10    BoxLayout, GridLayout, LayoutConstraints, LayoutGeometry, Orientation, RowColExpr,
11};
12use i_slint_compiler::namedreference::NamedReference;
13use i_slint_compiler::object_tree::ElementRc;
14use i_slint_core::items::{DialogButtonRole, FlexboxLayoutDirection, ItemRc};
15use i_slint_core::layout::{self as core_layout, GridLayoutInputData, GridLayoutOrganizedData};
16use i_slint_core::model::RepeatedItemTree;
17use i_slint_core::slice::Slice;
18use i_slint_core::window::WindowAdapter;
19use std::rc::Rc;
20use std::str::FromStr;
21
22pub(crate) fn to_runtime(o: Orientation) -> core_layout::Orientation {
23    match o {
24        Orientation::Horizontal => core_layout::Orientation::Horizontal,
25        Orientation::Vertical => core_layout::Orientation::Vertical,
26    }
27}
28
29pub(crate) fn from_runtime(o: core_layout::Orientation) -> Orientation {
30    match o {
31        core_layout::Orientation::Horizontal => Orientation::Horizontal,
32        core_layout::Orientation::Vertical => Orientation::Vertical,
33    }
34}
35
36pub(crate) fn compute_grid_layout_info(
37    grid_layout: &GridLayout,
38    organized_data: &GridLayoutOrganizedData,
39    orientation: Orientation,
40    local_context: &mut EvalLocalContext,
41    cross_axis_size: Option<f32>,
42) -> Value {
43    let component = local_context.component_instance;
44    let expr_eval = |nr: &NamedReference| -> f32 {
45        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
46    };
47    let (padding, spacing) = padding_and_spacing(&grid_layout.geometry, orientation, &expr_eval);
48    let repeater_steps = grid_repeater_steps(grid_layout, local_context);
49    let repeater_indices = grid_repeater_indices(grid_layout, local_context, &repeater_steps);
50    let constraints = grid_layout_constraints(
51        grid_layout,
52        orientation,
53        local_context,
54        &repeater_steps,
55        cross_axis_size,
56    );
57    core_layout::grid_layout_info(
58        organized_data.clone(),
59        Slice::from_slice(constraints.as_slice()),
60        Slice::from_slice(repeater_indices.as_slice()),
61        Slice::from_slice(repeater_steps.as_slice()),
62        spacing,
63        &padding,
64        to_runtime(orientation),
65    )
66    .into()
67}
68
69/// Determine layout info of a box layout
70pub(crate) fn compute_box_layout_info(
71    box_layout: &BoxLayout,
72    orientation: Orientation,
73    local_context: &mut EvalLocalContext,
74    cross_axis_size: Option<f32>,
75) -> Value {
76    let component = local_context.component_instance;
77    let expr_eval = |nr: &NamedReference| -> f32 {
78        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
79    };
80    let (cells, alignment) =
81        box_layout_data(box_layout, orientation, component, &expr_eval, None, cross_axis_size);
82    let (padding, spacing) = padding_and_spacing(&box_layout.geometry, orientation, &expr_eval);
83    if orientation == box_layout.orientation {
84        core_layout::box_layout_info(Slice::from(cells.as_slice()), spacing, &padding, alignment)
85    } else {
86        core_layout::box_layout_info_ortho(Slice::from(cells.as_slice()), &padding)
87    }
88    .into()
89}
90
91pub(crate) fn organize_grid_layout(
92    layout: &GridLayout,
93    local_context: &mut EvalLocalContext,
94) -> Value {
95    let repeater_steps = grid_repeater_steps(layout, local_context);
96    let cells = grid_layout_input_data(layout, local_context, &repeater_steps);
97    let repeater_indices = grid_repeater_indices(layout, local_context, &repeater_steps);
98    if let Some(buttons_roles) = &layout.dialog_button_roles {
99        let roles = buttons_roles
100            .iter()
101            .map(|r| DialogButtonRole::from_str(r).unwrap())
102            .collect::<Vec<_>>();
103        core_layout::organize_dialog_button_layout(
104            Slice::from_slice(cells.as_slice()),
105            Slice::from_slice(roles.as_slice()),
106        )
107        .into()
108    } else {
109        core_layout::organize_grid_layout(
110            Slice::from_slice(cells.as_slice()),
111            Slice::from_slice(repeater_indices.as_slice()),
112            Slice::from_slice(repeater_steps.as_slice()),
113        )
114        .into()
115    }
116}
117
118pub(crate) fn solve_grid_layout(
119    organized_data: &GridLayoutOrganizedData,
120    grid_layout: &GridLayout,
121    orientation: Orientation,
122    local_context: &mut EvalLocalContext,
123) -> Value {
124    let component = local_context.component_instance;
125    let expr_eval = |nr: &NamedReference| -> f32 {
126        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
127    };
128    let repeater_steps = grid_repeater_steps(grid_layout, local_context);
129    let repeater_indices = grid_repeater_indices(grid_layout, local_context, &repeater_steps);
130    let constraints =
131        grid_layout_constraints(grid_layout, orientation, local_context, &repeater_steps, None);
132
133    let (padding, spacing) = padding_and_spacing(&grid_layout.geometry, orientation, &expr_eval);
134    let size_ref = grid_layout.geometry.rect.size_reference(orientation);
135
136    let data = core_layout::GridLayoutData {
137        size: size_ref.map(expr_eval).unwrap_or(0.),
138        spacing,
139        padding,
140        organized_data: organized_data.clone(),
141    };
142
143    core_layout::solve_grid_layout(
144        &data,
145        Slice::from_slice(constraints.as_slice()),
146        to_runtime(orientation),
147        Slice::from_slice(repeater_indices.as_slice()),
148        Slice::from_slice(repeater_steps.as_slice()),
149    )
150    .into()
151}
152
153pub(crate) fn solve_box_layout(
154    box_layout: &BoxLayout,
155    orientation: Orientation,
156    local_context: &mut EvalLocalContext,
157) -> Value {
158    let component = local_context.component_instance;
159    let expr_eval = |nr: &NamedReference| -> f32 {
160        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
161    };
162
163    let mut repeated_indices = Vec::new();
164    let (cells, alignment) = box_layout_data(
165        box_layout,
166        orientation,
167        component,
168        &expr_eval,
169        Some(&mut repeated_indices),
170        None,
171    );
172    let (padding, spacing) = padding_and_spacing(&box_layout.geometry, orientation, &expr_eval);
173    let size = box_layout.geometry.rect.size_reference(orientation).map(&expr_eval).unwrap_or(0.);
174    if orientation == box_layout.orientation {
175        core_layout::solve_box_layout(
176            &core_layout::BoxLayoutData {
177                size,
178                spacing,
179                padding,
180                alignment,
181                cells: Slice::from(cells.as_slice()),
182            },
183            Slice::from(repeated_indices.as_slice()),
184        )
185        .into()
186    } else {
187        let align_items = box_layout
188            .cross_alignment
189            .as_ref()
190            .map(|nr| {
191                eval::load_property(component, &nr.element(), nr.name())
192                    .unwrap()
193                    .try_into()
194                    .unwrap_or_default()
195            })
196            .unwrap_or_default();
197        core_layout::solve_box_layout_ortho(
198            &core_layout::BoxLayoutOrthoData {
199                size,
200                padding,
201                align_items,
202                cells: Slice::from(cells.as_slice()),
203            },
204            Slice::from(repeated_indices.as_slice()),
205        )
206        .into()
207    }
208}
209
210pub(crate) fn solve_flexbox_layout(
211    flexbox_layout: &i_slint_compiler::layout::FlexboxLayout,
212    local_context: &mut EvalLocalContext,
213) -> Value {
214    let component = local_context.component_instance;
215    let expr_eval = |nr: &NamedReference| -> f32 {
216        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
217    };
218
219    let width_ref = &flexbox_layout.geometry.rect.width_reference;
220    let height_ref = &flexbox_layout.geometry.rect.height_reference;
221    let direction = flexbox_layout_direction(flexbox_layout, local_context);
222
223    // For column direction, pass the container width so cells_v can use it
224    // as the constraint for height-for-width items (items stretch to it).
225    let container_width_for_cells = match direction {
226        i_slint_core::items::FlexboxLayoutDirection::Column
227        | i_slint_core::items::FlexboxLayoutDirection::ColumnReverse => {
228            width_ref.as_ref().map(&expr_eval)
229        }
230        _ => None,
231    };
232
233    let (cells_h, cells_v, repeated_indices) = flexbox_layout_data(
234        flexbox_layout,
235        component,
236        &expr_eval,
237        local_context,
238        container_width_for_cells,
239        None,
240    );
241
242    let alignment = flexbox_layout
243        .geometry
244        .alignment
245        .as_ref()
246        .map_or(i_slint_core::items::LayoutAlignment::default(), |nr| {
247            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
248        });
249    let align_content = flexbox_layout
250        .align_content
251        .as_ref()
252        .map_or(i_slint_core::items::FlexboxLayoutAlignContent::default(), |nr| {
253            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
254        });
255    let align_items = flexbox_layout
256        .align_items
257        .as_ref()
258        .map_or(i_slint_core::items::LayoutAlignItems::default(), |nr| {
259            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
260        });
261    let flex_wrap = flexbox_layout
262        .flex_wrap
263        .as_ref()
264        .map_or(i_slint_core::items::FlexboxLayoutWrap::default(), |nr| {
265            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
266        });
267
268    let (padding_h, spacing_h) =
269        padding_and_spacing(&flexbox_layout.geometry, Orientation::Horizontal, &expr_eval);
270    let (padding_v, spacing_v) =
271        padding_and_spacing(&flexbox_layout.geometry, Orientation::Vertical, &expr_eval);
272
273    let data = core_layout::FlexboxLayoutData {
274        width: width_ref.as_ref().map(&expr_eval).unwrap_or(0.),
275        height: height_ref.as_ref().map(&expr_eval).unwrap_or(0.),
276        spacing_h,
277        spacing_v,
278        padding_h,
279        padding_v,
280        alignment,
281        direction,
282        align_content,
283        align_items,
284        flex_wrap,
285        cells_h: Slice::from(cells_h.as_slice()),
286        cells_v: Slice::from(cells_v.as_slice()),
287    };
288    let ri = Slice::from(repeated_indices.as_slice());
289
290    let window_adapter = component.window_adapter();
291
292    // Build measure callback that computes constrained layout_info for items
293    // that support height-for-width (Text with wrap, Image with aspect ratio,
294    // and component instances with a synthesized
295    // `layoutinfo-{v,h}-with-constraint`).
296    //
297    // Collect `(element, has_constrained_layoutinfo_{v,h})` so we can
298    // dispatch component instances through the parametrized layout-info
299    // function rather than through the Item vtable (which returns trivial
300    // info for the Empty wrapper of a sub-component instance).
301    struct ChildElem {
302        elem: ElementRc,
303        has_constrained_layoutinfo_v: bool,
304        has_constrained_layoutinfo_h: bool,
305        /// True when the cell aggregates layoutinfo from its own subtree
306        /// (set by `default_geometry::gen_layout_info_prop`) — typical for
307        /// component-wrappers like a Rectangle containing a layout. In
308        /// that case the Item vtable's `layout_info` on the wrapper item
309        /// returns trivial info that doesn't reflect the aggregated
310        /// constraints; we read the aggregated property directly.
311        has_aggregated_info: bool,
312    }
313    let mut child_elems: Vec<Option<ChildElem>> = Vec::new();
314    for layout_elem in &flexbox_layout.elems {
315        if layout_elem.item.element.borrow().repeated.is_some() {
316            let component_vec = repeater_instances(component, &layout_elem.item.element);
317            for _ in 0..component_vec.len() {
318                child_elems.push(None);
319            }
320        } else {
321            let elem_b = layout_elem.item.element.borrow();
322            let has_constrained_layoutinfo_v =
323                elem_b.inherited_layout_info_v_with_constraint().is_some();
324            let has_constrained_layoutinfo_h =
325                elem_b.inherited_layout_info_h_with_constraint().is_some();
326            let has_aggregated_info = elem_b.layout_info_prop.is_some();
327            drop(elem_b);
328            child_elems.push(Some(ChildElem {
329                elem: layout_elem.item.element.clone(),
330                has_constrained_layoutinfo_v,
331                has_constrained_layoutinfo_h,
332                has_aggregated_info,
333            }));
334        }
335    }
336
337    let mut measure = |child_index: usize,
338                       known_w: Option<f32>,
339                       known_h: Option<f32>|
340     -> (f32, f32) {
341        let default_w = cells_h.get(child_index).map_or(0., |c| c.constraint.preferred_bounded());
342        let default_h = cells_v.get(child_index).map_or(0., |c| c.constraint.preferred_bounded());
343        let w = known_w.unwrap_or(default_w);
344        let h = known_h.unwrap_or(default_h);
345
346        let ce = match child_elems.get(child_index) {
347            Some(Some(c)) => c,
348            _ => return (w, h),
349        };
350
351        // Cells whose layoutinfo aggregates from a sub-tree (set by
352        // default_geometry) or that have a parametrized layout-info
353        // function need to be queried by NamedReference. The Item
354        // vtable's `layout_info` on the wrapper item returns trivial
355        // info that ignores the aggregated children.
356        let use_property_lookup = ce.has_aggregated_info
357            || ce.has_constrained_layoutinfo_v
358            || ce.has_constrained_layoutinfo_h;
359
360        if known_w.is_some() && known_h.is_none() {
361            if use_property_lookup {
362                let v_info = get_layout_info_with_constraint(
363                    &ce.elem,
364                    component,
365                    &window_adapter,
366                    Orientation::Vertical,
367                    ce.has_constrained_layoutinfo_v.then_some(w),
368                );
369                return (w, v_info.preferred_bounded());
370            }
371            // Builtin path (Text, Image): use the Item vtable's
372            // layout_info, which honors the cross-axis constraint.
373            let elem_id = ce.elem.borrow().id.clone();
374            if let Some(item_within) = component.description.items.get(elem_id.as_str()) {
375                let item_comp = component.self_weak().get().unwrap().upgrade().unwrap();
376                let item_rc =
377                    ItemRc::new(vtable::VRc::into_dyn(item_comp), item_within.item_index());
378                let item = unsafe { item_within.item_from_item_tree(component.as_ptr()) };
379                let v_info = item.as_ref().layout_info(
380                    to_runtime(Orientation::Vertical),
381                    w,
382                    &window_adapter,
383                    &item_rc,
384                );
385                return (w, v_info.preferred_bounded());
386            }
387            return (w, h);
388        }
389        if known_h.is_some() && known_w.is_none() {
390            if use_property_lookup {
391                let h_info = get_layout_info_with_constraint(
392                    &ce.elem,
393                    component,
394                    &window_adapter,
395                    Orientation::Horizontal,
396                    ce.has_constrained_layoutinfo_h.then_some(h),
397                );
398                return (h_info.preferred_bounded(), h);
399            }
400            let elem_id = ce.elem.borrow().id.clone();
401            if let Some(item_within) = component.description.items.get(elem_id.as_str()) {
402                let item_comp = component.self_weak().get().unwrap().upgrade().unwrap();
403                let item_rc =
404                    ItemRc::new(vtable::VRc::into_dyn(item_comp), item_within.item_index());
405                let item = unsafe { item_within.item_from_item_tree(component.as_ptr()) };
406                let h_info = item.as_ref().layout_info(
407                    to_runtime(Orientation::Horizontal),
408                    h,
409                    &window_adapter,
410                    &item_rc,
411                );
412                return (h_info.preferred_bounded(), h);
413            }
414            return (w, h);
415        }
416        (w, h)
417    };
418
419    core_layout::solve_flexbox_layout_with_measure(&data, ri, Some(&mut measure)).into()
420}
421
422fn flexbox_layout_direction(
423    flexbox_layout: &i_slint_compiler::layout::FlexboxLayout,
424    local_context: &EvalLocalContext,
425) -> FlexboxLayoutDirection {
426    flexbox_layout
427        .direction
428        .as_ref()
429        .and_then(|nr| {
430            let value =
431                eval::load_property(local_context.component_instance, &nr.element(), nr.name())
432                    .ok()?;
433            if let Value::EnumerationValue(_, variant) = &value {
434                match variant.as_str() {
435                    "row" => Some(FlexboxLayoutDirection::Row),
436                    "row-reverse" => Some(FlexboxLayoutDirection::RowReverse),
437                    "column" => Some(FlexboxLayoutDirection::Column),
438                    "column-reverse" => Some(FlexboxLayoutDirection::ColumnReverse),
439                    _ => None,
440                }
441            } else {
442                None
443            }
444        })
445        .unwrap_or(FlexboxLayoutDirection::Row)
446}
447
448pub(crate) fn compute_flexbox_layout_info(
449    flexbox_layout: &i_slint_compiler::layout::FlexboxLayout,
450    orientation: Orientation,
451    local_context: &mut EvalLocalContext,
452    cross_axis_size: Option<f32>,
453) -> Value {
454    let component = local_context.component_instance;
455    let expr_eval = |nr: &NamedReference| -> f32 {
456        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
457    };
458
459    // `cross_axis_size` carries a width when called from a
460    // `layoutinfo-v-with-constraint` body, a height from a
461    // `layoutinfo-h-with-constraint` body. Route it to the matching
462    // cell-list so cells don't receive a dimension on the wrong axis.
463    let (width_override, height_override) = match orientation {
464        Orientation::Vertical => (cross_axis_size, None),
465        Orientation::Horizontal => (None, cross_axis_size),
466    };
467    let (cells_h, cells_v, _repeated_indices) = flexbox_layout_data(
468        flexbox_layout,
469        component,
470        &expr_eval,
471        local_context,
472        width_override,
473        height_override,
474    );
475
476    // Get the direction from the property binding
477    let direction = flexbox_layout_direction(flexbox_layout, local_context);
478
479    // Determine if we're on the main axis or cross axis
480    let is_main_axis = matches!(
481        (direction, orientation),
482        (FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse, Orientation::Horizontal)
483            | (
484                FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse,
485                Orientation::Vertical
486            )
487    );
488
489    let (padding_h, spacing_h) =
490        padding_and_spacing(&flexbox_layout.geometry, Orientation::Horizontal, &expr_eval);
491    let (padding_v, spacing_v) =
492        padding_and_spacing(&flexbox_layout.geometry, Orientation::Vertical, &expr_eval);
493
494    let flex_wrap = flexbox_layout
495        .flex_wrap
496        .as_ref()
497        .map_or(i_slint_core::items::FlexboxLayoutWrap::default(), |nr| {
498            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
499        });
500
501    if is_main_axis {
502        let (cells, spacing, padding) = match orientation {
503            Orientation::Horizontal => (&cells_h, spacing_h, &padding_h),
504            Orientation::Vertical => (&cells_v, spacing_v, &padding_v),
505        };
506        core_layout::flexbox_layout_info_main_axis(
507            Slice::from(cells.as_slice()),
508            spacing,
509            padding,
510            flex_wrap,
511        )
512        .into()
513    } else {
514        // Override when set (e.g., from a `layoutinfo-h-with-constraint`
515        // body); otherwise self's perpendicular dimension. The override
516        // path breaks the cycle for nested perpendicular flexboxes.
517        let constraint_size = cross_axis_size.unwrap_or_else(|| match orientation {
518            Orientation::Horizontal => {
519                let height_ref = &flexbox_layout.geometry.rect.height_reference;
520                height_ref.as_ref().map(&expr_eval).unwrap_or(0.)
521            }
522            Orientation::Vertical => {
523                let width_ref = &flexbox_layout.geometry.rect.width_reference;
524                width_ref.as_ref().map(&expr_eval).unwrap_or(0.)
525            }
526        });
527        core_layout::flexbox_layout_info_cross_axis(
528            Slice::from(cells_h.as_slice()),
529            Slice::from(cells_v.as_slice()),
530            spacing_h,
531            spacing_v,
532            &padding_h,
533            &padding_v,
534            direction,
535            flex_wrap,
536            constraint_size,
537        )
538        .into()
539    }
540}
541
542fn flexbox_layout_data(
543    flexbox_layout: &i_slint_compiler::layout::FlexboxLayout,
544    component: InstanceRef,
545    expr_eval: &impl Fn(&NamedReference) -> f32,
546    _local_context: &mut EvalLocalContext,
547    width_override: Option<f32>,
548    height_override: Option<f32>,
549) -> (Vec<core_layout::FlexboxLayoutItemInfo>, Vec<core_layout::FlexboxLayoutItemInfo>, Vec<u32>) {
550    let window_adapter = component.window_adapter();
551    let mut cells_h = Vec::with_capacity(flexbox_layout.elems.len());
552    let mut cells_v = Vec::with_capacity(flexbox_layout.elems.len());
553    let mut repeated_indices = Vec::new();
554
555    // First pass: collect horizontal layout_info for all children (no cycle risk)
556    // and flex properties. Store element refs for the second pass.
557    struct ChildInfo {
558        flex_grow: f32,
559        flex_shrink: f32,
560        flex_basis: f32,
561        flex_align_self: i_slint_core::items::FlexboxLayoutAlignSelf,
562        flex_order: i32,
563    }
564    let mut static_children: Vec<Option<ChildInfo>> = Vec::new(); // None = repeater
565
566    for layout_elem in &flexbox_layout.elems {
567        if layout_elem.item.element.borrow().repeated.is_some() {
568            let component_vec = repeater_instances(component, &layout_elem.item.element);
569            repeated_indices.push(cells_h.len() as u32);
570            repeated_indices.push(component_vec.len() as u32);
571            cells_h.extend(component_vec.iter().map(|x| {
572                x.as_pin_ref().flexbox_layout_item_info(to_runtime(Orientation::Horizontal), None)
573            }));
574            cells_v.extend(component_vec.iter().map(|x| {
575                x.as_pin_ref().flexbox_layout_item_info(to_runtime(Orientation::Vertical), None)
576            }));
577            for _ in 0..component_vec.len() {
578                static_children.push(None);
579            }
580        } else {
581            // Dispatch via `layoutinfo-h-with-constraint` for cells that
582            // have one, avoiding the `self.height` read that would cycle.
583            // Use the height-override when set, else `f32::MAX` so the
584            // runtime treats it as "no wrap needed" — gives the natural
585            // max-cell-width result rather than the heuristic.
586            let h_constraint = layout_elem
587                .item
588                .element
589                .borrow()
590                .inherited_layout_info_h_with_constraint()
591                .is_some()
592                .then(|| height_override.unwrap_or(f32::MAX));
593            let mut layout_info_h = get_layout_info_with_constraint(
594                &layout_elem.item.element,
595                component,
596                &window_adapter,
597                Orientation::Horizontal,
598                h_constraint,
599            );
600            fill_layout_info_constraints(
601                &mut layout_info_h,
602                &layout_elem.item.constraints,
603                Orientation::Horizontal,
604                expr_eval,
605            );
606            // Don't collect cells_v in the first pass — it may trigger a circular
607            // dependency for height-for-width items (Text with wrap, Image).
608            // The second pass fills in cells_v with the width constraint.
609            let flex_grow = layout_elem.flex_grow.as_ref().map(&expr_eval).unwrap_or(0.0);
610            let flex_shrink = layout_elem.flex_shrink.as_ref().map(&expr_eval).unwrap_or(1.0);
611            let flex_basis = layout_elem.flex_basis.as_ref().map(&expr_eval).unwrap_or(-1.0);
612            let align_self = layout_elem
613                .align_self
614                .as_ref()
615                .map(|nr| {
616                    eval::load_property(component, &nr.element(), nr.name())
617                        .unwrap()
618                        .try_into()
619                        .unwrap()
620                })
621                .unwrap_or(i_slint_core::items::FlexboxLayoutAlignSelf::default());
622            let order = layout_elem.order.as_ref().map(expr_eval).unwrap_or(0.0) as i32;
623            cells_h.push(core_layout::FlexboxLayoutItemInfo {
624                constraint: layout_info_h,
625                flex_grow,
626                flex_shrink,
627                flex_basis,
628                flex_align_self: align_self,
629                flex_order: order,
630            });
631            // Placeholder for cells_v — filled in second pass
632            cells_v.push(core_layout::FlexboxLayoutItemInfo::default());
633            static_children.push(Some(ChildInfo {
634                flex_grow,
635                flex_shrink,
636                flex_basis,
637                flex_align_self: align_self,
638                flex_order: order,
639            }));
640        }
641    }
642
643    // Second pass: collect vertical layout_info with a width constraint.
644    // For column direction, use the container width (items get stretched to it).
645    // Otherwise use the item's horizontal preferred size.
646    let mut cell_idx = 0usize;
647    for layout_elem in &flexbox_layout.elems {
648        if layout_elem.item.element.borrow().repeated.is_some() {
649            let component_vec = repeater_instances(component, &layout_elem.item.element);
650            cell_idx += component_vec.len();
651            // repeater cells_v already filled in first pass
652        } else {
653            let width_constraint =
654                width_override.unwrap_or_else(|| cells_h[cell_idx].constraint.preferred_bounded());
655            let mut layout_info_v = get_layout_info_with_constraint(
656                &layout_elem.item.element,
657                component,
658                &window_adapter,
659                Orientation::Vertical,
660                Some(width_constraint),
661            );
662            fill_layout_info_constraints(
663                &mut layout_info_v,
664                &layout_elem.item.constraints,
665                Orientation::Vertical,
666                expr_eval,
667            );
668            if let Some(info) = &static_children[cell_idx] {
669                cells_v[cell_idx] = core_layout::FlexboxLayoutItemInfo {
670                    constraint: layout_info_v,
671                    flex_grow: info.flex_grow,
672                    flex_shrink: info.flex_shrink,
673                    flex_basis: info.flex_basis,
674                    flex_align_self: info.flex_align_self,
675                    flex_order: info.flex_order,
676                };
677            }
678            cell_idx += 1;
679        }
680    }
681
682    (cells_h, cells_v, repeated_indices)
683}
684
685/// Determine the evaluated padding and spacing values from the layout geometry
686fn padding_and_spacing(
687    layout_geometry: &LayoutGeometry,
688    orientation: Orientation,
689    expr_eval: &impl Fn(&NamedReference) -> f32,
690) -> (core_layout::Padding, f32) {
691    let spacing = layout_geometry.spacing.orientation(orientation).map_or(0., expr_eval);
692    let (begin, end) = layout_geometry.padding.begin_end(orientation);
693    let padding =
694        core_layout::Padding { begin: begin.map_or(0., expr_eval), end: end.map_or(0., expr_eval) };
695    (padding, spacing)
696}
697
698fn repeater_instances(
699    component: InstanceRef,
700    elem: &ElementRc,
701) -> Vec<crate::dynamic_item_tree::DynamicComponentVRc> {
702    generativity::make_guard!(guard);
703    let rep =
704        crate::dynamic_item_tree::get_repeater_by_name(component, elem.borrow().id.as_str(), guard);
705    rep.0.as_ref().track_instance_changes();
706    rep.0.as_ref().instances_vec()
707}
708
709fn grid_layout_input_data(
710    grid_layout: &i_slint_compiler::layout::GridLayout,
711    ctx: &EvalLocalContext,
712    repeater_steps: &[u32],
713) -> Vec<GridLayoutInputData> {
714    let component = ctx.component_instance;
715    let mut result = Vec::with_capacity(grid_layout.elems.len());
716    let mut after_repeater_in_same_row = false;
717    let mut new_row = true;
718    let mut repeater_idx = 0usize;
719    for elem in grid_layout.elems.iter() {
720        let eval_or_default = |expr: &RowColExpr, component: InstanceRef| match expr {
721            RowColExpr::Literal(value) => *value as f32,
722            RowColExpr::Auto => i_slint_common::ROW_COL_AUTO,
723            RowColExpr::Named(nr) => {
724                // we could check for out-of-bounds here, but organize_grid_layout will also do it
725                eval::load_property(component, &nr.element(), nr.name())
726                    .unwrap()
727                    .try_into()
728                    .unwrap()
729            }
730        };
731
732        let cell_new_row = elem.cell.borrow().new_row;
733        if cell_new_row {
734            after_repeater_in_same_row = false;
735        }
736        if elem.item.element.borrow().repeated.is_some() {
737            let component_vec = repeater_instances(component, &elem.item.element);
738            new_row = cell_new_row;
739            for erased_sub_comp in &component_vec {
740                // Evaluate the row/col/rowspan/colspan expressions in the context of the sub-component
741                generativity::make_guard!(guard);
742                let sub_comp = erased_sub_comp.as_pin_ref();
743                let sub_instance_ref =
744                    unsafe { InstanceRef::from_pin_ref(sub_comp.borrow(), guard) };
745
746                if let Some(children) = elem.cell.borrow().child_items.as_ref() {
747                    // Repeated row
748                    new_row = true;
749                    let start_count = result.len();
750
751                    // Single pass in declaration order: push statics and inner-repeater
752                    // auto-cells interleaved so that column assignments match template order.
753                    // (A two-pass approach that appended all inner-repeater cells after all
754                    // statics would produce wrong column assignments, and only tracking the
755                    // last Repeated entry would miss earlier conditionals/for-loops.)
756                    for child_template in children {
757                        match child_template {
758                            i_slint_compiler::layout::RowChildTemplate::Static(child_item) => {
759                                let (row_val, col_val, rowspan_val, colspan_val) = {
760                                    let element_ref = child_item.element.borrow();
761                                    let child_cell =
762                                        element_ref.grid_layout_cell.as_ref().unwrap().borrow();
763                                    (
764                                        eval_or_default(&child_cell.row_expr, sub_instance_ref),
765                                        eval_or_default(&child_cell.col_expr, sub_instance_ref),
766                                        eval_or_default(&child_cell.rowspan_expr, sub_instance_ref),
767                                        eval_or_default(&child_cell.colspan_expr, sub_instance_ref),
768                                    )
769                                };
770                                result.push(GridLayoutInputData {
771                                    new_row,
772                                    col: col_val,
773                                    row: row_val,
774                                    colspan: colspan_val,
775                                    rowspan: rowspan_val,
776                                });
777                                new_row = false;
778                            }
779                            i_slint_compiler::layout::RowChildTemplate::Repeated {
780                                repeated_element,
781                                ..
782                            } => {
783                                let inner_instances =
784                                    repeater_instances(sub_instance_ref, repeated_element);
785                                for i in 0..inner_instances.len() {
786                                    result.push(GridLayoutInputData {
787                                        new_row: i == 0 && new_row,
788                                        ..Default::default()
789                                    });
790                                }
791                                if !inner_instances.is_empty() {
792                                    new_row = false;
793                                }
794                            }
795                        }
796                    }
797                    // Pad to match max step count for this repeater (handles jagged arrays)
798                    let cells_pushed = result.len() - start_count;
799                    let expected_step =
800                        repeater_steps.get(repeater_idx).copied().unwrap_or(0) as usize;
801                    for _ in cells_pushed..expected_step {
802                        result.push(GridLayoutInputData::default());
803                    }
804                } else {
805                    // Single repeated item
806                    let cell = elem.cell.borrow();
807                    let row = eval_or_default(&cell.row_expr, sub_instance_ref);
808                    let col = eval_or_default(&cell.col_expr, sub_instance_ref);
809                    let rowspan = eval_or_default(&cell.rowspan_expr, sub_instance_ref);
810                    let colspan = eval_or_default(&cell.colspan_expr, sub_instance_ref);
811                    result.push(GridLayoutInputData { new_row, col, row, colspan, rowspan });
812                    new_row = false;
813                }
814            }
815            repeater_idx += 1;
816            after_repeater_in_same_row = true;
817        } else {
818            let new_row =
819                if cell_new_row || !after_repeater_in_same_row { cell_new_row } else { new_row };
820            let row = eval_or_default(&elem.cell.borrow().row_expr, component);
821            let col = eval_or_default(&elem.cell.borrow().col_expr, component);
822            let rowspan = eval_or_default(&elem.cell.borrow().rowspan_expr, component);
823            let colspan = eval_or_default(&elem.cell.borrow().colspan_expr, component);
824            result.push(GridLayoutInputData { new_row, col, row, colspan, rowspan });
825        }
826    }
827    result
828}
829
830/// Count the actual runtime children for a repeated row.
831/// For rows without inner repeaters, this is just the child_items count.
832/// For rows with inner repeaters, the Repeated template expands to actual inner instances.
833fn row_runtime_child_count(
834    child_items: &[i_slint_compiler::layout::RowChildTemplate],
835    sub_instance_ref: InstanceRef,
836) -> usize {
837    let mut count = 0;
838    for child in child_items {
839        if let Some(repeated_element) = child.repeated_element() {
840            count += repeater_instances(sub_instance_ref, repeated_element).len();
841        } else {
842            count += 1;
843        }
844    }
845    count
846}
847
848fn grid_repeater_indices(
849    grid_layout: &i_slint_compiler::layout::GridLayout,
850    ctx: &mut EvalLocalContext,
851    repeater_steps: &[u32],
852) -> Vec<u32> {
853    let component = ctx.component_instance;
854    let mut repeater_indices = Vec::new();
855    let mut num_cells = 0;
856    let mut step_idx = 0;
857    for elem in grid_layout.elems.iter() {
858        if elem.item.element.borrow().repeated.is_some() {
859            let component_vec = repeater_instances(component, &elem.item.element);
860            repeater_indices.push(num_cells as _);
861            repeater_indices.push(component_vec.len() as _);
862            let item_count = repeater_steps[step_idx] as usize;
863            num_cells += component_vec.len() * item_count;
864            step_idx += 1;
865        } else {
866            num_cells += 1;
867        }
868    }
869    repeater_indices
870}
871
872fn grid_repeater_steps(
873    grid_layout: &i_slint_compiler::layout::GridLayout,
874    ctx: &mut EvalLocalContext,
875) -> Vec<u32> {
876    let component = ctx.component_instance;
877    let mut repeater_steps = Vec::new();
878    for elem in grid_layout.elems.iter() {
879        if elem.item.element.borrow().repeated.is_some() {
880            let item_count = match &elem.cell.borrow().child_items {
881                Some(ci)
882                    if ci.iter().any(i_slint_compiler::layout::RowChildTemplate::is_repeated) =>
883                {
884                    // Compute max runtime count across all instances (padding with empty cells didn't happen yet)
885                    let component_vec = repeater_instances(component, &elem.item.element);
886                    component_vec
887                        .iter()
888                        .map(|sub| {
889                            generativity::make_guard!(guard);
890                            let sub_pin = sub.as_pin_ref();
891                            let sub_ref =
892                                unsafe { InstanceRef::from_pin_ref(sub_pin.borrow(), guard) };
893                            row_runtime_child_count(ci, sub_ref)
894                        })
895                        .max()
896                        .unwrap_or(0)
897                }
898                Some(ci) => ci.len(),
899                None => 1,
900            };
901            repeater_steps.push(item_count as u32);
902        }
903    }
904    repeater_steps
905}
906
907fn grid_layout_constraints(
908    grid_layout: &i_slint_compiler::layout::GridLayout,
909    orientation: Orientation,
910    ctx: &mut EvalLocalContext,
911    repeater_steps: &[u32],
912    cross_axis_size: Option<f32>,
913) -> Vec<core_layout::LayoutItemInfo> {
914    let component = ctx.component_instance;
915    let expr_eval = |nr: &NamedReference| -> f32 {
916        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
917    };
918    let mut constraints = Vec::with_capacity(grid_layout.elems.len());
919
920    let mut repeater_idx = 0usize;
921    for layout_elem in grid_layout.elems.iter() {
922        if layout_elem.item.element.borrow().repeated.is_some() {
923            let component_vec = repeater_instances(component, &layout_elem.item.element);
924            let child_items = layout_elem.cell.borrow().child_items.clone();
925            let has_children = child_items.is_some();
926            if has_children {
927                // Repeated row
928                let ci = child_items.as_ref().unwrap();
929                let step = repeater_steps.get(repeater_idx).copied().unwrap_or(0) as usize;
930                for sub_comp in &component_vec {
931                    let per_instance_start = constraints.len();
932                    // Evaluate constraints in the context of the repeated sub-component
933                    generativity::make_guard!(guard);
934                    let sub_pin = sub_comp.as_pin_ref();
935                    let sub_borrow = sub_pin.borrow();
936                    let sub_instance_ref = unsafe { InstanceRef::from_pin_ref(sub_borrow, guard) };
937                    let expr_eval = |nr: &NamedReference| -> f32 {
938                        eval::load_property(sub_instance_ref, &nr.element(), nr.name())
939                            .unwrap()
940                            .try_into()
941                            .unwrap()
942                    };
943
944                    // Iterate over the child templates: static children get their layout info
945                    // from the Row sub-component; nested repeater children get theirs from the
946                    // inner repeater instances.
947                    for child_template in ci.iter() {
948                        match child_template {
949                            i_slint_compiler::layout::RowChildTemplate::Static(child_item) => {
950                                let mut layout_info = crate::eval_layout::get_layout_info(
951                                    &child_item.element,
952                                    sub_instance_ref,
953                                    &sub_instance_ref.window_adapter(),
954                                    orientation,
955                                );
956                                fill_layout_info_constraints(
957                                    &mut layout_info,
958                                    &child_item.constraints,
959                                    orientation,
960                                    &expr_eval,
961                                );
962                                constraints
963                                    .push(core_layout::LayoutItemInfo { constraint: layout_info });
964                            }
965                            i_slint_compiler::layout::RowChildTemplate::Repeated {
966                                item: child_item,
967                                repeated_element,
968                            } => {
969                                // Get the inner repeater instances from within this Row instance
970                                let inner_instances =
971                                    repeater_instances(sub_instance_ref, repeated_element);
972                                for inner_comp in &inner_instances {
973                                    let inner_pin = inner_comp.as_pin_ref();
974                                    let mut layout_info =
975                                        inner_pin.layout_item_info(to_runtime(orientation), None);
976                                    // Constraints' NamedReferences point to elements inside the
977                                    // inner repeated component, so evaluate in that context.
978                                    generativity::make_guard!(inner_guard);
979                                    let inner_borrow = inner_pin.borrow();
980                                    let inner_instance_ref = unsafe {
981                                        InstanceRef::from_pin_ref(inner_borrow, inner_guard)
982                                    };
983                                    let inner_expr_eval = |nr: &NamedReference| -> f32 {
984                                        eval::load_property(
985                                            inner_instance_ref,
986                                            &nr.element(),
987                                            nr.name(),
988                                        )
989                                        .unwrap()
990                                        .try_into()
991                                        .unwrap()
992                                    };
993                                    fill_layout_info_constraints(
994                                        &mut layout_info.constraint,
995                                        &child_item.constraints,
996                                        orientation,
997                                        &inner_expr_eval,
998                                    );
999                                    constraints.push(layout_info);
1000                                }
1001                            }
1002                        }
1003                    }
1004                    // Pad this instance to the step size (handles jagged arrays where
1005                    // inner repeaters have different lengths across outer Row instances).
1006                    let pushed = constraints.len() - per_instance_start;
1007                    for _ in pushed..step {
1008                        constraints.push(core_layout::LayoutItemInfo::default());
1009                    }
1010                }
1011            } else {
1012                // Single repeated item
1013                constraints.extend(
1014                    component_vec
1015                        .iter()
1016                        .map(|x| x.as_pin_ref().layout_item_info(to_runtime(orientation), None)),
1017                );
1018            }
1019            repeater_idx += 1;
1020        } else {
1021            let cross_axis =
1022                cross_axis_size_for_cell(&layout_elem.item.element, orientation, cross_axis_size);
1023            let mut layout_info = get_layout_info_with_constraint(
1024                &layout_elem.item.element,
1025                component,
1026                &component.window_adapter(),
1027                orientation,
1028                cross_axis,
1029            );
1030            fill_layout_info_constraints(
1031                &mut layout_info,
1032                &layout_elem.item.constraints,
1033                orientation,
1034                &expr_eval,
1035            );
1036            constraints.push(core_layout::LayoutItemInfo { constraint: layout_info });
1037        }
1038    }
1039    constraints
1040}
1041
1042/// Collect all elements in this layout and store the LayoutItemInfo of it for further calculation
1043fn box_layout_data(
1044    box_layout: &i_slint_compiler::layout::BoxLayout,
1045    orientation: Orientation,
1046    component: InstanceRef,
1047    expr_eval: &impl Fn(&NamedReference) -> f32,
1048    mut repeater_indices: Option<&mut Vec<u32>>,
1049    cross_axis_size: Option<f32>,
1050) -> (Vec<core_layout::LayoutItemInfo>, i_slint_core::items::LayoutAlignment) {
1051    let window_adapter = component.window_adapter();
1052    let mut cells = Vec::with_capacity(box_layout.elems.len());
1053    for cell in &box_layout.elems {
1054        if cell.element.borrow().repeated.is_some() {
1055            // Collect all repeated elements
1056            let component_vec = repeater_instances(component, &cell.element);
1057            if let Some(ri) = repeater_indices.as_mut() {
1058                ri.push(cells.len() as _);
1059                ri.push(component_vec.len() as _);
1060            }
1061            cells.extend(
1062                component_vec
1063                    .iter()
1064                    .map(|x| x.as_pin_ref().layout_item_info(to_runtime(orientation), None)),
1065            );
1066        } else {
1067            // Collect non repeated elements
1068            let cross_axis = cross_axis_size_for_cell(&cell.element, orientation, cross_axis_size);
1069            let mut layout_info = get_layout_info_with_constraint(
1070                &cell.element,
1071                component,
1072                &window_adapter,
1073                orientation,
1074                cross_axis,
1075            );
1076            fill_layout_info_constraints(
1077                &mut layout_info,
1078                &cell.constraints,
1079                orientation,
1080                &expr_eval,
1081            );
1082            cells.push(core_layout::LayoutItemInfo { constraint: layout_info });
1083        }
1084    }
1085    let alignment = box_layout
1086        .geometry
1087        .alignment
1088        .as_ref()
1089        .map(|nr| {
1090            eval::load_property(component, &nr.element(), nr.name())
1091                .unwrap()
1092                .try_into()
1093                .unwrap_or_default()
1094        })
1095        .unwrap_or_default();
1096    (cells, alignment)
1097}
1098
1099pub(crate) fn fill_layout_info_constraints(
1100    layout_info: &mut core_layout::LayoutInfo,
1101    constraints: &LayoutConstraints,
1102    orientation: Orientation,
1103    expr_eval: &impl Fn(&NamedReference) -> f32,
1104) {
1105    let is_percent =
1106        |nr: &NamedReference| Expression::PropertyReference(nr.clone()).ty() == Type::Percent;
1107
1108    match orientation {
1109        Orientation::Horizontal => {
1110            if let Some(e) = constraints.min_width.as_ref() {
1111                if !is_percent(e) {
1112                    layout_info.min = expr_eval(e)
1113                } else {
1114                    layout_info.min_percent = expr_eval(e)
1115                }
1116            }
1117            if let Some(e) = constraints.max_width.as_ref() {
1118                if !is_percent(e) {
1119                    layout_info.max = expr_eval(e)
1120                } else {
1121                    layout_info.max_percent = expr_eval(e)
1122                }
1123            }
1124            if let Some(e) = constraints.preferred_width.as_ref() {
1125                layout_info.preferred = expr_eval(e);
1126            }
1127            if let Some(e) = constraints.horizontal_stretch.as_ref() {
1128                layout_info.stretch = expr_eval(e);
1129            }
1130        }
1131        Orientation::Vertical => {
1132            if let Some(e) = constraints.min_height.as_ref() {
1133                if !is_percent(e) {
1134                    layout_info.min = expr_eval(e)
1135                } else {
1136                    layout_info.min_percent = expr_eval(e)
1137                }
1138            }
1139            if let Some(e) = constraints.max_height.as_ref() {
1140                if !is_percent(e) {
1141                    layout_info.max = expr_eval(e)
1142                } else {
1143                    layout_info.max_percent = expr_eval(e)
1144                }
1145            }
1146            if let Some(e) = constraints.preferred_height.as_ref() {
1147                layout_info.preferred = expr_eval(e);
1148            }
1149            if let Some(e) = constraints.vertical_stretch.as_ref() {
1150                layout_info.stretch = expr_eval(e);
1151            }
1152        }
1153    }
1154}
1155
1156/// Get the layout info for an element based on the layout_info_prop or the builtin item layout_info
1157pub(crate) fn get_layout_info(
1158    elem: &ElementRc,
1159    component: InstanceRef,
1160    window_adapter: &Rc<dyn WindowAdapter>,
1161    orientation: Orientation,
1162) -> core_layout::LayoutInfo {
1163    get_layout_info_with_constraint(elem, component, window_adapter, orientation, None)
1164}
1165
1166pub(crate) fn get_layout_info_with_constraint(
1167    elem: &ElementRc,
1168    component: InstanceRef,
1169    window_adapter: &Rc<dyn WindowAdapter>,
1170    orientation: Orientation,
1171    cross_axis_constraint: Option<f32>,
1172) -> core_layout::LayoutInfo {
1173    // With a constraint and a parameterized layout-info function on the
1174    // cell, call it instead of reading the cell's perpendicular property.
1175    // Use the inherited lookup so component-instance cells pick up a
1176    // `layoutinfo-{v,h}-with-constraint` declared on the base component's
1177    // root_element (the cell itself doesn't carry the binding).
1178    let parameterized_nr = if cross_axis_constraint.is_some() {
1179        match orientation {
1180            Orientation::Vertical => elem.borrow().inherited_layout_info_v_with_constraint(),
1181            Orientation::Horizontal => elem.borrow().inherited_layout_info_h_with_constraint(),
1182        }
1183    } else {
1184        None
1185    };
1186    if let Some(nr) = parameterized_nr {
1187        let arg = cross_axis_constraint.unwrap();
1188        let v = eval::call_function(
1189            &eval::ComponentInstance::InstanceRef(component),
1190            &nr.element(),
1191            nr.name(),
1192            vec![Value::Number(arg as f64)],
1193        )
1194        .expect("layoutinfo-{h,v}-with-constraint is a synthesized pure function");
1195        return v.try_into().unwrap();
1196    }
1197
1198    let elem = elem.borrow();
1199    if let Some(nr) = elem.layout_info_prop(orientation) {
1200        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
1201    } else {
1202        let item = &component
1203            .description
1204            .items
1205            .get(elem.id.as_str())
1206            .unwrap_or_else(|| panic!("Internal error: Item {} not found", elem.id));
1207        let item_comp = component.self_weak().get().unwrap().upgrade().unwrap();
1208
1209        unsafe {
1210            item.item_from_item_tree(component.as_ptr()).as_ref().layout_info(
1211                to_runtime(orientation),
1212                cross_axis_constraint.unwrap_or(-1.),
1213                window_adapter,
1214                &ItemRc::new(vtable::VRc::into_dyn(item_comp), item.item_index()),
1215            )
1216        }
1217    }
1218}
1219
1220/// Decide the cross-axis (width) constraint to forward to a cell's
1221/// vertical layout-info call: returns `Some` only when this cell is
1222/// height-for-width (a builtin Text with wrap, Image, or a component
1223/// with a synthesized `layoutinfo-v-with-constraint`) AND the parent
1224/// has supplied the cross-axis dimension.
1225fn cross_axis_size_for_cell(
1226    elem: &ElementRc,
1227    orientation: Orientation,
1228    parent_cross_axis_size: Option<f32>,
1229) -> Option<f32> {
1230    if orientation != Orientation::Vertical {
1231        return None;
1232    }
1233    let width = parent_cross_axis_size?;
1234    let elem_b = elem.borrow();
1235    if elem_b.layout_info_v_with_constraint.is_some() {
1236        return Some(width);
1237    }
1238    // For builtin height-for-width items, the existing VTable cross_axis_constraint
1239    // parameter mechanism is what consumes the value; conservatively
1240    // forward it for any element without its own layoutinfo-v property
1241    // (i.e. anything that ends up calling the builtin VTable).
1242    if elem_b.layout_info_prop(Orientation::Vertical).is_none() {
1243        return Some(width);
1244    }
1245    None
1246}