Skip to main content

ferroui_controls/flex_panel/
flex_panel.rs

1use super::{
2    Flex, FlexAlignContent, FlexAlignItems, FlexBasisKind, FlexDirection, FlexJustifyContent, FlexWrap,
3};
4use crate::{Control, ControlImpl, Panel, PanelImpl, PanelImplExt};
5use ferroui_base::collections::NotifyCollectionChangedEventArgs;
6use ferroui_base::input::InputElementImpl;
7use ferroui_base::interactivity::InteractiveImpl;
8use ferroui_base::layout::{HorizontalAlignment, Layoutable, LayoutableImpl, VerticalAlignment};
9use ferroui_base::reactive::{IDisposable, ObservableExt};
10use ferroui_base::utilities::math_utilities::max as math_max;
11use ferroui_base::utilities::MathUtilities;
12use ferroui_base::{
13    ferro_class, ferro_impl_classes, instantiate, FerroObject, FerroObjectExtensions, FerroObjectImpl, FerroProperty, Point, Rect,
14    Ref, Size, StyledElementImpl, StyledProperty, Visual, VisualImpl,
15};
16use std::cell::{Cell, RefCell};
17use std::collections::HashMap;
18use std::rc::Rc;
19
20/// A panel that arranges child controls using CSS FlexBox principles.
21/// It organizes child items in one or more lines along a main-axis (either
22/// row or column) and provides advanced control over their sizing and
23/// layout.
24///
25/// See the CSS FlexBox specification: <https://www.w3.org/TR/css-flexbox-1>
26#[repr(C)]
27pub struct FlexPanel {
28    base: Panel,
29    state: FlexLayoutState,
30    child_visibility_subscriptions: RefCell<HashMap<Ref<Control>, Rc<dyn IDisposable>>>,
31    visible_children: RefCell<Option<Rc<[Ref<Control>]>>>,
32}
33
34ferro_class!(FlexPanel: Panel);
35ferroui_base::ferro_class_info!(FlexPanel { new: FlexPanel::new });
36ferro_impl_classes!(
37    FlexPanel: StyledElementImpl,
38    VisualImpl,
39    InteractiveImpl,
40    InputElementImpl,
41    ControlImpl
42);
43
44ferroui_base::ferro_impl_classes!(FlexPanel: FerroObjectImpl);
45
46ferroui_base::ferro_properties! {
47    impl FlexPanel {
48        /// Defines the `Direction` property.
49        pub fn direction_property() -> StyledProperty<FlexDirection> {
50            FerroProperty::register::<FlexPanel, _>("Direction", FlexDirection::Row)
51        }
52
53        /// Defines the `JustifyContent` property.
54        pub fn justify_content_property() -> StyledProperty<FlexJustifyContent> {
55            FerroProperty::register::<FlexPanel, _>("JustifyContent", FlexJustifyContent::FlexStart)
56        }
57
58        /// Defines the `AlignItems` property.
59        pub fn align_items_property() -> StyledProperty<FlexAlignItems> {
60            FerroProperty::register::<FlexPanel, _>("AlignItems", FlexAlignItems::Stretch)
61        }
62
63        /// Defines the `AlignContent` property.
64        pub fn align_content_property() -> StyledProperty<FlexAlignContent> {
65            FerroProperty::register::<FlexPanel, _>("AlignContent", FlexAlignContent::Stretch)
66        }
67
68        /// Defines the `Wrap` property.
69        pub fn wrap_property() -> StyledProperty<FlexWrap> {
70            FerroProperty::register::<FlexPanel, _>("Wrap", FlexWrap::NoWrap)
71        }
72
73        /// Defines the `ColumnSpacing` property.
74        pub fn column_spacing_property() -> StyledProperty<f64> {
75            FerroProperty::register::<FlexPanel, _>("ColumnSpacing", 0.0)
76        }
77
78        /// Defines the `RowSpacing` property.
79        pub fn row_spacing_property() -> StyledProperty<f64> {
80            FerroProperty::register::<FlexPanel, _>("RowSpacing", 0.0)
81        }
82    }
83}
84
85impl FlexPanel {
86    fn static_constructor() {
87        Layoutable::affects_measure::<FlexPanel>(&[
88            Self::direction_property().as_property(),
89            Self::justify_content_property().as_property(),
90            Self::wrap_property().as_property(),
91            Self::column_spacing_property().as_property(),
92            Self::row_spacing_property().as_property(),
93        ]);
94
95        Layoutable::affects_arrange::<FlexPanel>(&[
96            Self::align_items_property().as_property(),
97            Self::align_content_property().as_property(),
98        ]);
99
100        Panel::affects_parent_measure::<FlexPanel>(&[
101            Layoutable::horizontal_alignment_property().as_property(),
102            Layoutable::vertical_alignment_property().as_property(),
103            Flex::order_property().as_property(),
104            Flex::basis_property().as_property(),
105            Flex::shrink_property().as_property(),
106            Flex::grow_property().as_property(),
107        ]);
108
109        Panel::affects_parent_arrange::<FlexPanel>(&[Flex::align_self_property().as_property()]);
110    }
111
112    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
113    pub fn construct() -> Self {
114        Self {
115            base: Panel::construct(),
116            state: FlexLayoutState::default(),
117            child_visibility_subscriptions: RefCell::new(HashMap::new()),
118            visible_children: RefCell::new(None),
119        }
120    }
121
122    pub fn new() -> Ref<Self> {
123        instantiate(Self::construct())
124    }
125
126    /// The direction of the panel's main-axis, determining the orientation
127    /// in which child controls are laid out.
128    ///
129    /// The default value is [`FlexDirection::Row`]. Equivalent to the CSS
130    /// flex-direction property.
131    pub fn direction(&self) -> FlexDirection {
132        self.get_value(Self::direction_property())
133    }
134
135    pub fn set_direction(&self, value: FlexDirection) {
136        self.set_value(Self::direction_property(), value)
137    }
138
139    /// The main-axis alignment of child items inside a line of the panel.
140    /// Typically used to distribute extra free space leftover after flexible
141    /// lengths and margins have been resolved.
142    ///
143    /// The default value is [`FlexJustifyContent::FlexStart`]. Equivalent to
144    /// the CSS justify-content property.
145    pub fn justify_content(&self) -> FlexJustifyContent {
146        self.get_value(Self::justify_content_property())
147    }
148
149    pub fn set_justify_content(&self, value: FlexJustifyContent) {
150        self.set_value(Self::justify_content_property(), value)
151    }
152
153    /// The cross-axis alignment of all child items inside a line of the
154    /// panel. Similar to `justify_content`, but in the perpendicular
155    /// direction.
156    ///
157    /// The default value is [`FlexAlignItems::Stretch`]. Equivalent to the
158    /// CSS align-items property.
159    pub fn align_items(&self) -> FlexAlignItems {
160        self.get_value(Self::align_items_property())
161    }
162
163    pub fn set_align_items(&self, value: FlexAlignItems) {
164        self.set_value(Self::align_items_property(), value)
165    }
166
167    /// The cross-axis alignment of lines in the panel when there is extra
168    /// space. Similar to `align_items`, but for entire lines. The `wrap`
169    /// property set to [`FlexWrap::Wrap`] mode allows controls to be
170    /// arranged on multiple lines.
171    ///
172    /// The default value is [`FlexAlignContent::Stretch`]. Equivalent to the
173    /// CSS align-content property.
174    pub fn align_content(&self) -> FlexAlignContent {
175        self.get_value(Self::align_content_property())
176    }
177
178    pub fn set_align_content(&self, value: FlexAlignContent) {
179        self.set_value(Self::align_content_property(), value)
180    }
181
182    /// The wrap mode, controlling whether the panel is single-line or
183    /// multi-line. Additionally, it determines the cross-axis stacking
184    /// direction for new lines.
185    ///
186    /// The default value is [`FlexWrap::NoWrap`]. Equivalent to the CSS
187    /// flex-wrap property.
188    pub fn wrap(&self) -> FlexWrap {
189        self.get_value(Self::wrap_property())
190    }
191
192    pub fn set_wrap(&self, value: FlexWrap) {
193        self.set_value(Self::wrap_property(), value)
194    }
195
196    /// The minimum horizontal spacing between child items or lines,
197    /// depending on the main-axis direction of the panel.
198    ///
199    /// The default value is 0. Similar to the CSS column-gap property.
200    pub fn column_spacing(&self) -> f64 {
201        self.get_value(Self::column_spacing_property())
202    }
203
204    pub fn set_column_spacing(&self, value: f64) {
205        self.set_value(Self::column_spacing_property(), value)
206    }
207
208    /// The minimum vertical spacing between child items or lines, depending
209    /// on the main-axis direction of the panel.
210    ///
211    /// The default value is 0. Similar to the CSS row-gap property.
212    pub fn row_spacing(&self) -> f64 {
213        self.get_value(Self::row_spacing_property())
214    }
215
216    pub fn set_row_spacing(&self, value: f64) {
217        self.set_value(Self::row_spacing_property(), value)
218    }
219
220    /// The visible children in layout order, when there are any.
221    fn non_empty_visible_children(&self) -> Option<Rc<[Ref<Control>]>> {
222        self.visible_children.borrow().clone().filter(|children| !children.is_empty())
223    }
224
225    fn measure_child(element: &Control, max: Uv, is_column: bool) -> Uv {
226        let basis = Flex::get_basis(element);
227        let flex_constraint = match basis.kind() {
228            FlexBasisKind::Auto => max.u,
229            FlexBasisKind::Absolute => basis.value(),
230            FlexBasisKind::Relative => max.u * basis.value() / 100.0,
231        };
232        element.measure(Uv::to_size(max.with_u(flex_constraint), is_column));
233
234        let size = Uv::from_size(element.desired_size(), is_column);
235
236        let flex_length = match basis.kind() {
237            FlexBasisKind::Auto => size.u,
238            FlexBasisKind::Absolute | FlexBasisKind::Relative => math_max(size.u, flex_constraint),
239        };
240        let size = size.with_u(flex_length);
241
242        Flex::set_base_length(element, flex_length);
243        Flex::set_current_length(element, flex_length);
244        size
245    }
246
247    fn determine_align_content(
248        current_align_content: FlexAlignContent,
249        free_v: f64,
250        lines_count: usize,
251    ) -> FlexAlignContent {
252        // Determine AlignContent based on available space and line count
253        match current_align_content {
254            // If there's free vertical space, handle distribution based on the content alignment
255            FlexAlignContent::Stretch if free_v > 0.0 => FlexAlignContent::Stretch,
256            FlexAlignContent::SpaceBetween if free_v > 0.0 && lines_count > 1 => FlexAlignContent::SpaceBetween,
257            FlexAlignContent::SpaceAround if free_v > 0.0 && lines_count > 0 => FlexAlignContent::SpaceAround,
258            FlexAlignContent::SpaceEvenly if free_v > 0.0 && lines_count > 0 => FlexAlignContent::SpaceEvenly,
259
260            // Default alignments when there's no free space or not enough lines
261            FlexAlignContent::Stretch => FlexAlignContent::FlexStart,
262            FlexAlignContent::SpaceBetween => FlexAlignContent::FlexStart,
263            FlexAlignContent::SpaceAround => FlexAlignContent::Center,
264            FlexAlignContent::SpaceEvenly => FlexAlignContent::Center,
265            FlexAlignContent::FlexStart | FlexAlignContent::Center | FlexAlignContent::FlexEnd => {
266                current_align_content
267            }
268        }
269    }
270
271    fn get_cross_axis_pos_and_spacing(
272        align_content: FlexAlignContent,
273        spacing: Uv,
274        free_v: f64,
275        lines_count: usize,
276    ) -> (f64, f64) {
277        let lines = lines_count as f64;
278        match align_content {
279            FlexAlignContent::FlexStart => (0.0, spacing.v),
280            FlexAlignContent::FlexEnd => (free_v, spacing.v),
281            FlexAlignContent::Center => (free_v / 2.0, spacing.v),
282            FlexAlignContent::Stretch => (0.0, spacing.v),
283
284            FlexAlignContent::SpaceBetween if lines_count > 1 => (0.0, spacing.v + free_v / (lines - 1.0)),
285            FlexAlignContent::SpaceBetween => (0.0, spacing.v),
286
287            FlexAlignContent::SpaceAround if lines_count > 0 => (free_v / lines / 2.0, spacing.v + free_v / lines),
288            FlexAlignContent::SpaceAround => (free_v / 2.0, spacing.v),
289
290            FlexAlignContent::SpaceEvenly => (free_v / (lines + 1.0), spacing.v + free_v / (lines + 1.0)),
291        }
292    }
293
294    fn get_main_axis_pos_and_spacing(
295        justify_content: FlexJustifyContent,
296        line: &FlexLine,
297        spacing: Uv,
298        remaining_free_u: f64,
299        items_count: usize,
300    ) -> (f64, f64) {
301        if line.grow > 0.0 {
302            return (0.0, spacing.u);
303        }
304
305        let items = items_count as f64;
306        match justify_content {
307            FlexJustifyContent::FlexStart => (0.0, spacing.u),
308            FlexJustifyContent::FlexEnd => (remaining_free_u, spacing.u),
309            FlexJustifyContent::Center => (remaining_free_u / 2.0, spacing.u),
310
311            FlexJustifyContent::SpaceBetween if items_count > 1 => {
312                (0.0, spacing.u + remaining_free_u / (items - 1.0))
313            }
314            FlexJustifyContent::SpaceBetween => (0.0, spacing.u),
315
316            FlexJustifyContent::SpaceAround if items_count > 0 => {
317                (remaining_free_u / items / 2.0, spacing.u + remaining_free_u / items)
318            }
319            FlexJustifyContent::SpaceAround => (remaining_free_u / 2.0, spacing.u),
320
321            FlexJustifyContent::SpaceEvenly if items_count > 0 => {
322                (remaining_free_u / (items + 1.0), spacing.u + remaining_free_u / (items + 1.0))
323            }
324            FlexJustifyContent::SpaceEvenly => (remaining_free_u / 2.0, spacing.u),
325        }
326    }
327
328    /// Returns the number of items of the line and its free main-axis space.
329    fn get_line_measure_u(line: &FlexLine, panel_size_u: f64, spacing_u: f64) -> (usize, f64) {
330        let items_count = line.count();
331        let total_spacing_u = (items_count as f64 - 1.0) * spacing_u;
332        let total_u = line.u + total_spacing_u;
333        let free_u = panel_size_u - total_u;
334        (items_count, free_u)
335    }
336
337    /// Returns the sum of the flex factors that apply to the line, its number
338    /// of auto margins and the free space that remains to be distributed.
339    fn get_line_mult_info(line: &FlexLine, free_u: f64) -> (f64, i32, f64) {
340        let line_mult = if free_u < 0.0 {
341            line.shrink
342        } else if free_u > 0.0 {
343            line.grow
344        } else {
345            0.0
346        };
347        // https://www.w3.org/TR/css-flexbox-1/#remaining-free-space
348        // Sum of flex factors less than 1 reduces remaining free space to be distributed.
349        if line_mult > 0.0 && line_mult < 1.0 {
350            (line_mult, line.auto_margins, free_u * line_mult)
351        } else {
352            (line_mult, line.auto_margins, free_u)
353        }
354    }
355
356    fn get_item_mult(element: &Layoutable, free_u: f64) -> f64 {
357        if free_u < 0.0 {
358            Flex::get_shrink(element)
359        } else if free_u > 0.0 {
360            Flex::get_grow(element)
361        } else {
362            0.0
363        }
364    }
365
366    fn get_item_auto_margins(element: &Layoutable, is_column: bool) -> i32 {
367        if is_column {
368            match element.vertical_alignment() {
369                VerticalAlignment::Stretch => 0,
370                VerticalAlignment::Top | VerticalAlignment::Bottom => 1,
371                VerticalAlignment::Center => 2,
372            }
373        } else {
374            match element.horizontal_alignment() {
375                HorizontalAlignment::Stretch => 0,
376                HorizontalAlignment::Left | HorizontalAlignment::Right => 1,
377                HorizontalAlignment::Center => 2,
378            }
379        }
380    }
381
382    fn on_child_visibility_changed(&self, _is_visible: bool) {
383        self.update_visible_children();
384    }
385
386    fn update_visible_children(&self) {
387        let mut visible_children: Vec<Ref<Control>> =
388            self.children().snapshot().iter().filter(|child| child.is_visible()).cloned().collect();
389        // A stable sort: items with the same order keep their source order.
390        visible_children.sort_by_key(|child| Flex::get_order(child));
391        *self.visible_children.borrow_mut() = Some(visible_children.into());
392
393        self.invalidate_measure();
394    }
395}
396
397impl LayoutableImpl for FlexPanel {
398    fn measure_override(this: &Self, available_size: Size) -> Size {
399        let Some(children) = this.non_empty_visible_children() else {
400            this.state.lines.borrow_mut().clear();
401            this.state.measure_count.set(this.state.measure_count.get().wrapping_add(1));
402            return Size::default();
403        };
404
405        let direction = this.direction();
406        let is_column = matches!(direction, FlexDirection::Column | FlexDirection::ColumnReverse);
407        let wrap = this.wrap();
408
409        let max = Uv::from_size(available_size, is_column);
410        let spacing = Uv::from_width_height(this.column_spacing(), this.row_spacing(), is_column);
411
412        let mut line_data = LineData::default();
413        let (mut child_index, mut first_child_index, mut item_index) = (0usize, 0usize, 0usize);
414
415        // The lines are taken out of the state while children are measured:
416        // no borrow is held across their layout code.
417        let mut lines = std::mem::take(&mut *this.state.lines.borrow_mut());
418        lines.clear();
419
420        let mut panel_size_u = 0.0;
421        let max_selector = |l: &FlexLine| l.u + (l.count() as f64 - 1.0) * spacing.u;
422
423        for element in children.iter() {
424            let size = Self::measure_child(element, max, is_column);
425
426            if wrap != FlexWrap::NoWrap
427                && MathUtilities::greater_than_or_close(line_data.u + size.u + item_index as f64 * spacing.u, max.u)
428            {
429                // As upstream, a first item that does not fit closes an empty
430                // line (its last index is one before its first).
431                let line = FlexLine::new(first_child_index as isize, child_index as isize - 1, line_data);
432                panel_size_u = math_max(panel_size_u, max_selector(&line));
433                lines.push(line);
434                line_data = LineData::default();
435                first_child_index = child_index;
436                item_index = 0;
437            }
438
439            line_data.u += size.u;
440            line_data.v = math_max(line_data.v, size.v);
441            line_data.shrink += Flex::get_shrink(element);
442            line_data.grow += Flex::get_grow(element);
443            line_data.auto_margins += Self::get_item_auto_margins(element, is_column);
444            item_index += 1;
445            child_index += 1;
446        }
447
448        if item_index != 0 {
449            let line =
450                FlexLine::new(first_child_index as isize, (first_child_index + item_index) as isize - 1, line_data);
451            panel_size_u = math_max(panel_size_u, max_selector(&line));
452            lines.push(line);
453        }
454
455        let total_spacing_v = (lines.len() as f64 - 1.0) * spacing.v;
456
457        // Resizing along main axis using grow and shrink factors can affect cross axis, so remeasure affected items and lines.
458        for flex_line in lines.iter_mut() {
459            let (_items_count, free_u) = Self::get_line_measure_u(flex_line, max.u, spacing.u);
460            let (line_mult, _auto_margins, remaining_free_u) = Self::get_line_mult_info(flex_line, free_u);
461
462            if max.u.is_finite() && line_mult != 0.0 && remaining_free_u != 0.0 {
463                let mut max_v: f64 = 0.0;
464                for element in &children[flex_line.range()] {
465                    let base_length = Flex::get_base_length(element);
466                    let mult = Self::get_item_mult(element, free_u);
467                    let length = math_max(0.0, base_length + remaining_free_u * mult / line_mult);
468                    element.measure(Uv::to_size(max.with_u(length), is_column));
469
470                    max_v = math_max(Uv::from_size(element.desired_size(), is_column).v, max_v);
471                }
472
473                flex_line.v = max_v;
474            }
475        }
476
477        if wrap == FlexWrap::WrapReverse {
478            lines.reverse();
479        }
480
481        let total_line_v: f64 = lines.iter().map(|l| l.v).sum();
482        let panel_size =
483            if lines.is_empty() { Uv::default() } else { Uv::new(panel_size_u, total_line_v + total_spacing_v) };
484
485        *this.state.lines.borrow_mut() = lines;
486        this.state.measure_count.set(this.state.measure_count.get().wrapping_add(1));
487
488        Uv::to_size(panel_size, is_column)
489    }
490
491    fn arrange_override(this: &Self, final_size: Size) -> Size {
492        let Some(children) = this.non_empty_visible_children() else {
493            return final_size;
494        };
495        if this.state.lines.borrow().is_empty() {
496            return final_size;
497        }
498
499        let direction = this.direction();
500        let is_column = matches!(direction, FlexDirection::Column | FlexDirection::ColumnReverse);
501        let is_reverse = matches!(direction, FlexDirection::RowReverse | FlexDirection::ColumnReverse);
502
503        let panel_size = Uv::from_size(final_size, is_column);
504        let spacing = Uv::from_width_height(this.column_spacing(), this.row_spacing(), is_column);
505        let justify_content = this.justify_content();
506        let align_items = this.align_items();
507
508        // The lines are taken out of the state while children are arranged:
509        // no borrow is held across their layout code.
510        let lines = std::mem::take(&mut *this.state.lines.borrow_mut());
511        let measure_count = this.state.measure_count.get();
512
513        let lines_count = lines.len();
514        let total_line_v: f64 = lines.iter().map(|s| s.v).sum();
515        let total_spacing_v = (lines_count as f64 - 1.0) * spacing.v;
516        let total_v = total_line_v + total_spacing_v;
517        let free_v = panel_size.v - total_v;
518
519        let align_content = Self::determine_align_content(this.align_content(), free_v, lines_count);
520
521        let (mut v, spacing_v) = Self::get_cross_axis_pos_and_spacing(align_content, spacing, free_v, lines_count);
522
523        let scale_v = if align_content == FlexAlignContent::Stretch && total_line_v != 0.0 {
524            (panel_size.v - total_spacing_v) / total_line_v
525        } else {
526            1.0
527        };
528
529        for line in &lines {
530            let line_children = &children[line.range()];
531            let line_v = scale_v * line.v;
532            let (items_count, free_u) = Self::get_line_measure_u(line, panel_size.u, spacing.u);
533            let (line_mult, line_auto_margins, mut remaining_free_u) = Self::get_line_mult_info(line, free_u);
534
535            let mut current_free_u = remaining_free_u;
536            if line_mult != 0.0 && remaining_free_u != 0.0 {
537                for element in line_children {
538                    let base_length = Flex::get_base_length(element);
539                    let mult = Self::get_item_mult(element, free_u);
540                    if mult != 0.0 {
541                        let length = math_max(0.0, base_length + remaining_free_u * mult / line_mult);
542                        Flex::set_current_length(element, length);
543                        current_free_u -= length - base_length;
544                    }
545                }
546            }
547            remaining_free_u = current_free_u;
548
549            if line_auto_margins != 0 && remaining_free_u != 0.0 {
550                for element in line_children {
551                    let base_length = Flex::get_current_length(element);
552                    let auto_margins = Self::get_item_auto_margins(element, is_column);
553                    if auto_margins != 0 {
554                        let length = math_max(
555                            0.0,
556                            base_length + remaining_free_u * auto_margins as f64 / line_auto_margins as f64,
557                        );
558                        Flex::set_current_length(element, length);
559                        current_free_u -= length - base_length;
560                    }
561                }
562            }
563            remaining_free_u = current_free_u;
564
565            let (mut u, spacing_u) =
566                Self::get_main_axis_pos_and_spacing(justify_content, line, spacing, remaining_free_u, items_count);
567
568            for element in line_children {
569                let size = Uv::from_size(element.desired_size(), is_column).with_u(Flex::get_current_length(element));
570                let align = Flex::get_align_self(element).unwrap_or(align_items);
571
572                let position_v = match align {
573                    FlexAlignItems::FlexStart => v,
574                    FlexAlignItems::FlexEnd => v + line_v - size.v,
575                    FlexAlignItems::Center => v + (line_v - size.v) / 2.0,
576                    FlexAlignItems::Stretch => v,
577                };
578
579                let size = size.with_v(if align == FlexAlignItems::Stretch { line_v } else { size.v });
580                let position = Uv::new(if is_reverse { panel_size.u - size.u - u } else { u }, position_v);
581                let origin = Uv::to_point(position, is_column);
582                let extent = Uv::to_size(size, is_column);
583                element.arrange(Rect::new(origin.x, origin.y, extent.width, extent.height));
584
585                u += size.u + spacing_u;
586            }
587
588            v += line_v + spacing_v;
589        }
590
591        // A measure pass that ran while the children were arranged has
592        // stored new lines; they win.
593        if this.state.measure_count.get() == measure_count {
594            *this.state.lines.borrow_mut() = lines;
595        }
596
597        final_size
598    }
599}
600
601impl PanelImpl for FlexPanel {
602    fn children_changed(this: &Self, e: &NotifyCollectionChangedEventArgs<'_, Ref<Control>>) {
603        Self::parent_children_changed(this, e);
604
605        for old in e.old_items {
606            let disposable = this.child_visibility_subscriptions.borrow_mut().remove(old);
607            if let Some(disposable) = disposable {
608                disposable.dispose();
609            }
610        }
611
612        for new_item in e.new_items {
613            let weak = this.to_ref().downgrade();
614            let object: &FerroObject = new_item;
615            let subscription = FerroObjectExtensions::get_observable(object, Visual::is_visible_property())
616                .subscribe_fn(move |is_visible| {
617                    if let Some(this) = weak.upgrade() {
618                        this.on_child_visibility_changed(is_visible);
619                    }
620                });
621            let previous = this.child_visibility_subscriptions.borrow_mut().insert(new_item.clone(), subscription);
622            if previous.is_some() {
623                panic!("An item with the same key has already been added.");
624            }
625        }
626
627        this.update_visible_children();
628    }
629}
630
631#[derive(Default)]
632struct FlexLayoutState {
633    lines: RefCell<Vec<FlexLine>>,
634    /// Counts the measure passes, so that an arrange pass can tell whether
635    /// the lines it works on are still the current ones.
636    measure_count: Cell<u64>,
637}
638
639#[derive(Clone, Copy, Default)]
640struct LineData {
641    u: f64,
642    v: f64,
643    shrink: f64,
644    grow: f64,
645    auto_margins: i32,
646}
647
648#[derive(Clone, Copy)]
649struct FlexLine {
650    /// First item index.
651    first: isize,
652    /// Last item index.
653    last: isize,
654    /// Sum of main sizes of items.
655    u: f64,
656    /// Max of cross sizes of items.
657    v: f64,
658    /// Sum of shrink factors of flexible items.
659    shrink: f64,
660    /// Sum of grow factors of flexible items.
661    grow: f64,
662    /// Number of "auto margins" along main axis.
663    auto_margins: i32,
664}
665
666impl FlexLine {
667    fn new(first: isize, last: isize, l: LineData) -> Self {
668        Self { first, last, u: l.u, v: l.v, shrink: l.shrink, grow: l.grow, auto_margins: l.auto_margins }
669    }
670
671    /// Number of items.
672    fn count(&self) -> usize {
673        (self.last - self.first + 1).max(0) as usize
674    }
675
676    /// The indexes of the items of the line.
677    fn range(&self) -> std::ops::Range<usize> {
678        let first = self.first.max(0) as usize;
679        first..first + self.count()
680    }
681}
682
683#[derive(Clone, Copy, Default)]
684struct Uv {
685    u: f64,
686    v: f64,
687}
688
689impl Uv {
690    fn new(u: f64, v: f64) -> Self {
691        Self { u, v }
692    }
693
694    fn from_width_height(width: f64, height: f64, swap: bool) -> Self {
695        Self::new(if swap { height } else { width }, if swap { width } else { height })
696    }
697
698    fn from_size(size: Size, swap: bool) -> Self {
699        Self::from_width_height(size.width, size.height, swap)
700    }
701
702    fn to_point(uv: Uv, swap: bool) -> Point {
703        Point::new(if swap { uv.v } else { uv.u }, if swap { uv.u } else { uv.v })
704    }
705
706    fn to_size(uv: Uv, swap: bool) -> Size {
707        Size::new(if swap { uv.v } else { uv.u }, if swap { uv.u } else { uv.v })
708    }
709
710    fn with_u(self, u: f64) -> Self {
711        Self::new(u, self.v)
712    }
713
714    fn with_v(self, v: f64) -> Self {
715        Self::new(self.u, v)
716    }
717}