Skip to main content

ferroui_controls/
definition_base.rs

1// This source file is adapted from the Windows Presentation Foundation project.
2// (https://github.com/dotnet/wpf/)
3
4use crate::grid::LayoutTimeSizeType;
5use crate::{Control, Grid, GridLength, GridUnitType};
6use ferroui_base::reactive::IDisposable;
7use ferroui_base::utilities::math_utilities::{max as math_max, MathUtilities};
8use ferroui_base::{
9    ferro_class, ferro_property, AttachedProperty, FerroObject, FerroObjectImpl, FerroProperty,
10    FerroPropertyChangedEventArgs, Ref, StyledPropertyOptions, WeakRef,
11};
12use std::cell::{Cell, RefCell};
13use std::collections::HashMap;
14use std::rc::{Rc, Weak};
15
16/// When set, a definition takes into account the minimum of its shared state.
17const FLAG_USE_SHARED_MINIMUM: u8 = 0x20;
18/// Set every time the parent grid is measured.
19const FLAG_LAYOUT_WAS_UPDATED: u8 = 0x40;
20
21/// Provides the core functionality used internally by `Grid` and the column
22/// and row definition collections.
23///
24/// This class is abstract: only its subclasses [`crate::ColumnDefinition`]
25/// and [`crate::RowDefinition`] can be created.
26#[repr(C)]
27pub struct DefinitionBase {
28    base: FerroObject,
29    /// Flags reflecting various aspects of the internal state.
30    flags: Cell<u8>,
31    /// The index of this instance in the parent's collection.
32    parent_index: Cell<i32>,
33    /// The layout-time user size type. It may differ from the unit type of
34    /// the user size when calculating "to content".
35    size_type: Cell<LayoutTimeSizeType>,
36    /// Used during measure to accumulate the size of "Auto" and "Star"
37    /// definitions.
38    min_size: Cell<f64>,
39    /// The size calculated to be the input constraint size for the measure
40    /// of a child.
41    measure_size: Cell<f64>,
42    /// A cache used for various purposes (sorting, caching, etc) during
43    /// calculations.
44    size_cache: Cell<f64>,
45    /// The offset of the definition from the left / top corner.
46    offset: Cell<f64>,
47    /// The shared state object this instance is registered with.
48    shared_state: RefCell<Option<Rc<SharedSizeState>>>,
49    parent: RefCell<Option<WeakRef<Grid>>>,
50}
51
52ferro_class! {
53    DefinitionBase: FerroObject, virtuals DefinitionBaseImpl: FerroObjectImpl {
54        /// Internal helper to access the up-to-date user size property value.
55        #[doc(hidden)]
56        fn user_size_value_cache(this) -> GridLength;
57        /// Internal helper to access the up-to-date user minimum size
58        /// property value.
59        #[doc(hidden)]
60        fn user_min_size_value_cache(this) -> f64;
61        /// Internal helper to access the up-to-date user maximum size
62        /// property value.
63        #[doc(hidden)]
64        fn user_max_size_value_cache(this) -> f64;
65    }
66}
67
68ferroui_base::ferro_impl_classes!(DefinitionBase: FerroObjectImpl);
69
70impl DefinitionBaseImpl for DefinitionBase {
71    fn user_size_value_cache(_this: &Self) -> GridLength {
72        panic!("DefinitionBase is abstract.")
73    }
74
75    fn user_min_size_value_cache(_this: &Self) -> f64 {
76        panic!("DefinitionBase is abstract.")
77    }
78
79    fn user_max_size_value_cache(_this: &Self) -> f64 {
80        panic!("DefinitionBase is abstract.")
81    }
82}
83
84impl Drop for DefinitionBase {
85    fn drop(&mut self) {
86        // A definition that is dropped while still registered (its grid was
87        // dropped without detaching it) leaves its shared state.
88        if let Some(shared_state) = self.shared_state.get_mut().take() {
89            shared_state.remove_dropped_members();
90        }
91    }
92}
93
94ferroui_base::ferro_properties! { impl DefinitionBase {
95    ferro_property!(
96        /// Holds the collection of shared state objects of a given shared
97        /// size scope; see `on_is_shared_size_scope_property_changed`.
98        pub(crate) fn private_shared_size_scope_property() -> AttachedProperty<Option<Rc<SharedSizeScope>>> {
99            FerroProperty::register_attached_with::<DefinitionBase, Control, _>(
100                "PrivateSharedSizeScope",
101                StyledPropertyOptions::new(None).inherits(true),
102            )
103        }
104    );
105
106    ferro_property!(
107        /// Defines the `SharedSizeGroup` property, which marks a column / row
108        /// definition as belonging to a group "Foo" or "Bar".
109        ///
110        /// The value must satisfy the following rules:
111        ///
112        /// * the string must not be empty;
113        /// * the string must consist of letters, digits and underscore ('_')
114        ///   only;
115        /// * the string must not start with a digit.
116        pub fn shared_size_group_property() -> AttachedProperty<Option<String>> {
117            FerroProperty::register_attached_with::<DefinitionBase, Control, _>(
118                "SharedSizeGroup",
119                StyledPropertyOptions::new(None).validate(DefinitionBase::shared_size_group_property_value_valid),
120            )
121        }
122    );
123} }
124
125impl DefinitionBase {
126    fn static_constructor() {
127        Self::shared_size_group_property()
128            .changed()
129            .add_class_handler::<DefinitionBase>(Self::on_shared_size_group_property_changed);
130        Self::private_shared_size_scope_property()
131            .changed()
132            .add_class_handler::<DefinitionBase>(Self::on_private_shared_size_scope_property_changed);
133    }
134
135    /// Creates the class data; see [`FerroObject::construct`].
136    pub(crate) fn construct() -> Self {
137        Self {
138            base: FerroObject::construct(),
139            flags: Cell::new(0),
140            parent_index: Cell::new(-1),
141            size_type: Cell::new(LayoutTimeSizeType::NONE),
142            min_size: Cell::new(0.0),
143            measure_size: Cell::new(0.0),
144            size_cache: Cell::new(0.0),
145            offset: Cell::new(0.0),
146            shared_state: RefCell::new(None),
147            parent: RefCell::new(None),
148        }
149    }
150
151    /// The shared size group of the definition.
152    pub fn shared_size_group(&self) -> Option<String> {
153        self.get_value(Self::shared_size_group_property())
154    }
155
156    pub fn set_shared_size_group(&self, value: Option<&str>) {
157        self.set_value(Self::shared_size_group_property(), value.map(str::to_owned))
158    }
159
160    /// Callback to notify about entering the model tree.
161    pub(crate) fn on_enter_parent_tree(&self) {
162        self.set_inheritance_parent(self.parent().map(Ref::upcast::<FerroObject>));
163        if self.shared_state.borrow().is_none() {
164            // Start with getting the shared size group value. This property
165            // is NOT inherited which should result in better overall
166            // performance.
167            if let Some(shared_size_group_id) = self.shared_size_group() {
168                if let Some(private_shared_size_scope) = self.get_value(Self::private_shared_size_scope_property()) {
169                    self.register_with_shared_state(&private_shared_size_scope, &shared_size_group_id);
170                }
171            }
172        }
173
174        if let Some(parent) = self.parent() {
175            parent.invalidate_measure();
176        }
177    }
178
179    /// Callback to notify about exiting the model tree.
180    pub(crate) fn on_exit_parent_tree(&self) {
181        self.offset.set(0.0);
182        self.unregister_from_shared_state();
183
184        if let Some(parent) = self.parent() {
185            parent.invalidate_measure();
186        }
187
188        // While this link survives the definition still reads the grid's
189        // inherited shared size scope, and the grid holds it as an
190        // inheritance child.
191        self.set_inheritance_parent(None::<Ref<FerroObject>>);
192    }
193
194    /// Prepares the definition to enter the layout calculation mode.
195    pub(crate) fn on_before_layout(&self, grid: &Grid) {
196        // Reset the layout state.
197        self.min_size.set(0.0);
198        self.set_layout_was_updated(true);
199
200        // Defer the verification for shared definitions.
201        let shared_state = self.shared_state.borrow().clone();
202        if let Some(shared_state) = shared_state {
203            shared_state.ensure_deferred_validation(grid);
204        }
205    }
206
207    /// Updates the minimum size.
208    #[inline]
209    pub(crate) fn update_min_size(&self, min_size: f64) {
210        self.min_size.set(math_max(self.min_size.get(), min_size));
211    }
212
213    /// Sets the minimum size.
214    #[inline]
215    pub(crate) fn set_min_size(&self, min_size: f64) {
216        self.min_size.set(min_size);
217    }
218
219    /// Reflects the state of the `IsSharedSizeScope` property of a control
220    /// by setting / clearing the private shared size scope property, whose
221    /// value is the collection of shared states objects of the scope.
222    pub(crate) fn on_is_shared_size_scope_property_changed(d: &Control, e: &FerroPropertyChangedEventArgs<'_>) {
223        if e.get_new_value::<bool>() {
224            let shared_states_collection = SharedSizeScope::new();
225            d.set_value(
226                Self::private_shared_size_scope_property(),
227                Some(shared_states_collection),
228            );
229        } else {
230            d.clear_value(Self::private_shared_size_scope_property());
231        }
232    }
233
234    /// Notifies the parent grid or the size scope that the size of a
235    /// definition has been changed.
236    pub(crate) fn on_user_size_property_changed(definition: &DefinitionBase, e: &FerroPropertyChangedEventArgs<'_>) {
237        let Some(parent) = definition.parent() else {
238            return;
239        };
240
241        let shared_state = definition.shared_state.borrow().clone();
242        if let Some(shared_state) = shared_state {
243            shared_state.invalidate();
244        } else {
245            let (old_value, new_value) = e.get_old_and_new_value::<GridLength>();
246
247            if old_value.grid_unit_type() != new_value.grid_unit_type() {
248                parent.invalidate();
249            } else {
250                parent.invalidate_measure();
251            }
252        }
253    }
254
255    /// Whether this definition is a part of a shared group.
256    #[inline]
257    pub(crate) fn is_shared(&self) -> bool {
258        self.shared_state.borrow().is_some()
259    }
260
261    /// Internal accessor to the user size.
262    pub(crate) fn user_size(&self) -> GridLength {
263        let shared_state = self.shared_state.borrow().clone();
264        match shared_state {
265            Some(shared_state) => shared_state.user_size(),
266            None => self.user_size_value_cache(),
267        }
268    }
269
270    /// Internal accessor to the user minimum size.
271    #[inline]
272    pub(crate) fn user_min_size(&self) -> f64 {
273        self.user_min_size_value_cache()
274    }
275
276    /// Internal accessor to the user maximum size.
277    #[inline]
278    pub(crate) fn user_max_size(&self) -> f64 {
279        self.user_max_size_value_cache()
280    }
281
282    /// The index of the definition in the parent's collection, or -1.
283    #[inline]
284    pub(crate) fn index(&self) -> i32 {
285        self.parent_index.get()
286    }
287
288    #[inline]
289    pub(crate) fn set_index(&self, value: i32) {
290        debug_assert!(value >= -1);
291        self.parent_index.set(value);
292    }
293
294    /// The layout-time user size type.
295    #[inline]
296    pub(crate) fn size_type(&self) -> LayoutTimeSizeType {
297        self.size_type.get()
298    }
299
300    #[inline]
301    pub(crate) fn set_size_type(&self, value: LayoutTimeSizeType) {
302        self.size_type.set(value);
303    }
304
305    /// The measure size of the definition.
306    #[inline]
307    pub(crate) fn measure_size(&self) -> f64 {
308        self.measure_size.get()
309    }
310
311    #[inline]
312    pub(crate) fn set_measure_size(&self, value: f64) {
313        self.measure_size.set(value);
314    }
315
316    /// The layout time type sensitive preferred size of the definition.
317    ///
318    /// The returned value is guaranteed to be the true preferred size.
319    pub(crate) fn preferred_size(&self) -> f64 {
320        let mut preferred_size = self.min_size();
321        if self.size_type.get() != LayoutTimeSizeType::AUTO && preferred_size < self.measure_size.get() {
322            preferred_size = self.measure_size.get();
323        }
324        preferred_size
325    }
326
327    /// The size cache of the definition.
328    #[inline]
329    pub(crate) fn size_cache(&self) -> f64 {
330        self.size_cache.get()
331    }
332
333    #[inline]
334    pub(crate) fn set_size_cache(&self, value: f64) {
335        self.size_cache.set(value);
336    }
337
338    /// The minimum size.
339    pub(crate) fn min_size(&self) -> f64 {
340        let mut min_size = self.min_size.get();
341        if self.use_shared_minimum() {
342            if let Some(shared_state) = self.shared_state.borrow().as_ref() {
343                let shared_min_size = shared_state.min_size();
344                if min_size < shared_min_size {
345                    min_size = shared_min_size;
346                }
347            }
348        }
349        min_size
350    }
351
352    /// The minimum size, always taking into account the shared state.
353    pub(crate) fn min_size_for_arrange(&self) -> f64 {
354        let mut min_size = self.min_size.get();
355        if let Some(shared_state) = self.shared_state.borrow().as_ref() {
356            if self.use_shared_minimum() || !self.layout_was_updated() {
357                let shared_min_size = shared_state.min_size();
358                if min_size < shared_min_size {
359                    min_size = shared_min_size;
360                }
361            }
362        }
363        min_size
364    }
365
366    /// The minimum size, never taking into account the shared state.
367    ///
368    /// This is the definition's own intrinsic contribution to its group. It
369    /// is the counterpart of `set_min_size`: code that saves and restores a
370    /// minimum size across a measure must round-trip this value, because
371    /// feeding the group minimum back into the minimum size would make the
372    /// group unable to shrink.
373    #[inline]
374    pub(crate) fn raw_min_size(&self) -> f64 {
375        self.min_size.get()
376    }
377
378    /// The final offset.
379    #[inline]
380    pub(crate) fn final_offset(&self) -> f64 {
381        self.offset.get()
382    }
383
384    #[inline]
385    pub(crate) fn set_final_offset(&self, value: f64) {
386        self.offset.set(value);
387    }
388
389    /// The grid the definition belongs to.
390    pub(crate) fn parent(&self) -> Option<Ref<Grid>> {
391        self.parent.borrow().as_ref().and_then(WeakRef::upgrade)
392    }
393
394    pub(crate) fn set_parent(&self, value: Option<&Ref<Grid>>) {
395        *self.parent.borrow_mut() = value.map(Ref::downgrade);
396    }
397
398    /// Registers the definition with the shared state of a group.
399    fn register_with_shared_state(&self, private_shared_size_scope: &Rc<SharedSizeScope>, shared_size_group: &str) {
400        let shared_state = private_shared_size_scope.ensure_shared_state(shared_size_group);
401        *self.shared_state.borrow_mut() = Some(shared_state.clone());
402        shared_state.add_member(self);
403    }
404
405    /// Un-registers the definition from its current shared state, if any.
406    fn unregister_from_shared_state(&self) {
407        let shared_state = self.shared_state.borrow().clone();
408        if let Some(shared_state) = shared_state {
409            shared_state.remove_member(self);
410            *self.shared_state.borrow_mut() = None;
411        }
412    }
413
414    fn on_shared_size_group_property_changed(definition: &DefinitionBase, e: &FerroPropertyChangedEventArgs<'_>) {
415        if definition.parent().is_some() {
416            let shared_size_group_id = e.get_new_value::<Option<String>>();
417
418            // If the definition is already registered AND the shared size
419            // group id is changing, then un-register the definition from the
420            // current shared size state object.
421            definition.unregister_from_shared_state();
422
423            if let Some(shared_size_group_id) = shared_size_group_id {
424                if let Some(private_shared_size_scope) =
425                    definition.get_value(Self::private_shared_size_scope_property())
426                {
427                    // If the definition is not registered and both the shared
428                    // size group id AND the private shared scope are
429                    // available, then register the definition.
430                    definition.register_with_shared_state(&private_shared_size_scope, &shared_size_group_id);
431                }
432            }
433        }
434    }
435
436    /// Verifies that a shared size group string
437    /// a) is not empty;
438    /// b) contains only letters, digits and underscore ('_');
439    /// c) does not start with a digit.
440    fn shared_size_group_property_value_valid(id: &Option<String>) -> bool {
441        // None is the default value.
442        let Some(id) = id else {
443            return true;
444        };
445
446        if id.is_empty() {
447            return false;
448        }
449
450        id.chars().enumerate().all(|(i, c)| {
451            let is_digit = c.is_numeric();
452            !(i == 0 && is_digit) && (is_digit || c.is_alphabetic() || c == '_')
453        })
454    }
455
456    /// Called when a new scope enters or the existing scope just left. In
457    /// both cases if the definition is already registered in a shared state,
458    /// it should un-register and register itself in a new one.
459    fn on_private_shared_size_scope_property_changed(
460        definition: &DefinitionBase,
461        e: &FerroPropertyChangedEventArgs<'_>,
462    ) {
463        if definition.parent().is_some() {
464            // If the definition is already registered AND the shared size
465            // scope is changing, then un-register the definition from the
466            // current shared size state object.
467            definition.unregister_from_shared_state();
468
469            if let Some(private_shared_size_scope) = e.get_new_value::<Option<Rc<SharedSizeScope>>>() {
470                if let Some(shared_size_group) = definition.shared_size_group() {
471                    // If the definition is not registered and both the shared
472                    // size group id AND the private shared scope are
473                    // available, then register the definition.
474                    definition.register_with_shared_state(&private_shared_size_scope, &shared_size_group);
475                }
476            }
477        }
478    }
479
480    #[inline]
481    fn set_flags(&self, value: bool, flags: u8) {
482        self.flags.set(if value {
483            self.flags.get() | flags
484        } else {
485            self.flags.get() & !flags
486        });
487    }
488
489    #[inline]
490    fn check_flags_and(&self, flags: u8) -> bool {
491        (self.flags.get() & flags) == flags
492    }
493
494    #[inline]
495    fn use_shared_minimum(&self) -> bool {
496        self.check_flags_and(FLAG_USE_SHARED_MINIMUM)
497    }
498
499    #[inline]
500    fn set_use_shared_minimum(&self, value: bool) {
501        self.set_flags(value, FLAG_USE_SHARED_MINIMUM)
502    }
503
504    #[inline]
505    fn layout_was_updated(&self) -> bool {
506        self.check_flags_and(FLAG_LAYOUT_WAS_UPDATED)
507    }
508
509    #[inline]
510    fn set_layout_was_updated(&self, value: bool) {
511        self.set_flags(value, FLAG_LAYOUT_WAS_UPDATED)
512    }
513
514    /// Marks a property on a definition as affecting the parent grid's
515    /// measurement.
516    pub(crate) fn affects_parent_measure(properties: &[&'static FerroProperty]) {
517        for property in properties {
518            property.changed().subscribe(|e| {
519                if let Some(parent) = e
520                    .sender()
521                    .downcast_ref::<DefinitionBase>()
522                    .and_then(DefinitionBase::parent)
523                {
524                    parent.invalidate_measure();
525                }
526            });
527        }
528    }
529}
530
531/// The collection of shared states objects of a single scope.
532pub(crate) struct SharedSizeScope {
533    this: Weak<SharedSizeScope>,
534    /// The storage for the shared state objects.
535    registry: RefCell<HashMap<String, Rc<SharedSizeState>>>,
536}
537
538impl PartialEq for SharedSizeScope {
539    fn eq(&self, other: &Self) -> bool {
540        std::ptr::eq(self, other)
541    }
542}
543
544impl SharedSizeScope {
545    fn new() -> Rc<SharedSizeScope> {
546        Rc::new_cyclic(|this| SharedSizeScope {
547            this: this.clone(),
548            registry: RefCell::new(HashMap::new()),
549        })
550    }
551
552    /// Returns the shared state object of a given group, creating it if
553    /// necessary.
554    fn ensure_shared_state(&self, shared_size_group: &str) -> Rc<SharedSizeState> {
555        if let Some(shared_state) = self.registry.borrow().get(shared_size_group) {
556            return shared_state.clone();
557        }
558
559        let shared_state = SharedSizeState::new(self.this.clone(), shared_size_group);
560        self.registry
561            .borrow_mut()
562            .insert(shared_size_group.to_owned(), shared_state.clone());
563        shared_state
564    }
565
566    /// Removes an entry in the registry by the given key.
567    fn remove(&self, key: &str) {
568        self.registry.borrow_mut().remove(key);
569    }
570}
571
572/// The control for which a layout updated handler is registered, and the
573/// registration.
574type LayoutUpdatedHost = (WeakRef<Grid>, Rc<dyn IDisposable>);
575
576/// The per shared group state object.
577pub(crate) struct SharedSizeState {
578    this: Weak<SharedSizeState>,
579    /// The scope this state belongs to.
580    shared_size_scope: Weak<SharedSizeScope>,
581    /// The id of the shared size group this object is servicing.
582    shared_size_group_id: String,
583    /// The registry of participating definitions.
584    registry: RefCell<Vec<WeakRef<DefinitionBase>>>,
585    /// Set while a layout updated handler is registered.
586    layout_updated_host: RefCell<Option<LayoutUpdatedHost>>,
587    /// True when broadcasting of an invalidation is needed.
588    broadcast_invalidation: Cell<bool>,
589    /// True when the user size is up to date.
590    user_size_valid: Cell<bool>,
591    user_size: Cell<GridLength>,
592    min_size: Cell<f64>,
593}
594
595impl SharedSizeState {
596    fn new(shared_size_scope: Weak<SharedSizeScope>, shared_size_group_id: &str) -> Rc<Self> {
597        Rc::new_cyclic(|this| SharedSizeState {
598            this: this.clone(),
599            shared_size_scope,
600            shared_size_group_id: shared_size_group_id.to_owned(),
601            registry: RefCell::new(Vec::new()),
602            layout_updated_host: RefCell::new(None),
603            broadcast_invalidation: Cell::new(true),
604            user_size_valid: Cell::new(false),
605            user_size: Cell::new(GridLength::default()),
606            min_size: Cell::new(0.0),
607        })
608    }
609
610    /// The registered definition at `index`, unless it has been dropped.
611    fn member(&self, index: usize) -> Option<Ref<DefinitionBase>> {
612        self.registry.borrow().get(index).and_then(WeakRef::upgrade)
613    }
614
615    fn member_count(&self) -> usize {
616        self.registry.borrow().len()
617    }
618
619    /// Adds / registers a definition instance.
620    fn add_member(&self, member: &DefinitionBase) {
621        let member = member.to_ref();
622        debug_assert!(!self.registry.borrow().iter().any(|m| m.points_to(&member)));
623        self.registry.borrow_mut().push(member.downgrade());
624        self.invalidate();
625    }
626
627    /// Removes / un-registers a definition instance.
628    ///
629    /// If the collection of registered definitions becomes empty, the state
630    /// removes itself from the collection of its owner.
631    fn remove_member(&self, member: &DefinitionBase) {
632        self.invalidate();
633        let member = member.to_ref();
634        self.registry
635            .borrow_mut()
636            .retain(|m| !m.points_to(&member) && m.upgrade().is_some());
637        self.remove_from_scope_if_empty();
638    }
639
640    /// Forgets the registered definitions that no longer exist.
641    fn remove_dropped_members(&self) {
642        self.registry.borrow_mut().retain(|m| m.upgrade().is_some());
643        self.remove_from_scope_if_empty();
644    }
645
646    fn remove_from_scope_if_empty(&self) {
647        if self.registry.borrow().is_empty() {
648            if let Some(shared_size_scope) = self.shared_size_scope.upgrade() {
649                shared_size_scope.remove(&self.shared_size_group_id);
650            }
651        }
652    }
653
654    /// Propagates invalidations for all registered definitions and resets
655    /// its own state.
656    fn invalidate(&self) {
657        self.user_size_valid.set(false);
658
659        if self.broadcast_invalidation.get() {
660            for i in 0..self.member_count() {
661                if let Some(parent_grid) = self.member(i).and_then(|m| m.parent()) {
662                    parent_grid.invalidate();
663                }
664            }
665            self.broadcast_invalidation.set(false);
666        }
667    }
668
669    /// Makes sure that one and only one layout updated handler is registered
670    /// for this shared state.
671    fn ensure_deferred_validation(&self, layout_updated_host: &Grid) {
672        let has_host = self
673            .layout_updated_host
674            .borrow()
675            .as_ref()
676            .is_some_and(|(host, _)| host.upgrade().is_some());
677        if !has_host {
678            let this = self.this.clone();
679            let subscription = layout_updated_host.layout_updated(move || {
680                if let Some(this) = this.upgrade() {
681                    this.on_layout_updated();
682                }
683            });
684            *self.layout_updated_host.borrow_mut() = Some((layout_updated_host.to_ref().downgrade(), subscription));
685        }
686    }
687
688    fn min_size(&self) -> f64 {
689        if !self.user_size_valid.get() {
690            self.ensure_user_size_valid();
691        }
692        self.min_size.get()
693    }
694
695    fn user_size(&self) -> GridLength {
696        if !self.user_size_valid.get() {
697            self.ensure_user_size_valid();
698        }
699        self.user_size.get()
700    }
701
702    fn ensure_user_size_valid(&self) {
703        let mut user_size = GridLength::new(1.0, GridUnitType::Auto);
704
705        for i in 0..self.member_count() {
706            debug_assert!(
707                user_size.grid_unit_type() == GridUnitType::Auto || user_size.grid_unit_type() == GridUnitType::Pixel
708            );
709
710            let Some(member) = self.member(i) else {
711                continue;
712            };
713            let current_grid_length = member.user_size_value_cache();
714            if current_grid_length.grid_unit_type() == GridUnitType::Pixel
715                && (user_size.grid_unit_type() == GridUnitType::Auto || user_size.value() < current_grid_length.value())
716            {
717                user_size = current_grid_length;
718            }
719        }
720        self.user_size.set(user_size);
721
722        // Taking the maximum with the user size effectively prevents
723        // squishy-ness. This is a "solution" to avoid shared definitions from
724        // being sized to a different final size at arrange time, if / when
725        // different grids receive different final sizes.
726        self.min_size.set(if user_size.is_absolute() {
727            user_size.value()
728        } else {
729            0.0
730        });
731
732        self.user_size_valid.set(true);
733    }
734
735    /// The layout updated handler. Validates that all participating
736    /// definitions have an updated minimum size value. Forces another layout
737    /// update cycle if needed.
738    fn on_layout_updated(&self) {
739        let mut shared_min_size: f64 = 0.0;
740
741        // Accumulate the minimum size of all participating definitions.
742        for i in 0..self.member_count() {
743            if let Some(member) = self.member(i) {
744                shared_min_size = math_max(shared_min_size, member.min_size.get());
745            }
746        }
747
748        let shared_min_size_changed = !MathUtilities::are_close(self.min_size.get(), shared_min_size);
749
750        // Compare the accumulated minimum size with the minimum sizes of the
751        // individual definitions.
752        for i in 0..self.member_count() {
753            let Some(definition_base) = self.member(i) else {
754                continue;
755            };
756
757            // We'll set the definition's "use shared minimum" flag to
758            // maintain the invariant:
759            //      d.use_shared_minimum iff d.min_size < this.min_size
760            // i.e. iff d is not a "long-pole" definition.
761            //
762            // Measure/Arrange of d's grid uses d.min_size for long-pole
763            // definitions, and max(d.min_size, shared size) for short-pole
764            // definitions. This distinction allows us to react to changes in
765            // "long-pole-ness" more efficiently and correctly, by avoiding
766            // remeasures when a long-pole definition changes.
767            let use_shared_minimum = !MathUtilities::are_close(definition_base.min_size.get(), shared_min_size);
768
769            // Before doing that, determine whether d's grid needs to be
770            // remeasured. It's important _not_ to remeasure if the last
771            // measure is still valid, otherwise infinite loops are possible.
772            let measure_is_valid = if !definition_base.use_shared_minimum() {
773                // d was a long-pole. The measure is valid iff it's still a
774                // long-pole, since the previous measure didn't use the shared
775                // size.
776                !use_shared_minimum
777            } else if use_shared_minimum {
778                // d was a short-pole, and still is. The measure is valid iff
779                // the shared size didn't change.
780                !shared_min_size_changed
781            } else {
782                // d was a short-pole, but is now a long-pole. This can happen
783                // in several ways:
784                //  a. d's min size increased to or past the old shared size
785                //  b. other long-pole definitions decreased, leaving d as the
786                //     new winner
787                // In the former case, the measure is valid - it used d's new
788                // larger min size. In the latter case, the measure is invalid
789                // - it used the old shared size, which is larger than d's
790                // (possibly changed) min size.
791                definition_base.layout_was_updated()
792                    && MathUtilities::greater_than_or_close(definition_base.min_size.get(), self.min_size())
793            };
794
795            if !measure_is_valid {
796                if let Some(parent) = definition_base.parent() {
797                    parent.invalidate_measure();
798                }
799            } else if !MathUtilities::are_close(shared_min_size, definition_base.size_cache()) {
800                // If the measure is valid then also need to check arrange.
801                // Note: the size cache is volatile but at this point it
802                // contains the up-to-date final size.
803                if let Some(parent) = definition_base.parent() {
804                    parent.invalidate_arrange();
805                }
806            }
807
808            // Now we can restore the invariant, and clear the layout flag.
809            definition_base.set_use_shared_minimum(use_shared_minimum);
810            definition_base.set_layout_was_updated(false);
811        }
812
813        self.min_size.set(shared_min_size);
814
815        let layout_updated_host = self.layout_updated_host.borrow_mut().take();
816        if let Some((_, subscription)) = layout_updated_host {
817            subscription.dispose();
818        }
819
820        self.broadcast_invalidation.set(true);
821    }
822}