Skip to main content

ferroui_controls/
grid.rs

1// This source file is adapted from the Windows Presentation Foundation project.
2// (https://github.com/dotnet/wpf/)
3
4use crate::{
5    ColumnDefinition, ColumnDefinitions, Control, ControlImpl, DefinitionBase, GridUnitType, Panel, PanelImpl,
6    PanelImplExt, RowDefinition, RowDefinitions,
7};
8use ferroui_base::collections::NotifyCollectionChangedEventArgs;
9use ferroui_base::input::InputElementImpl;
10use ferroui_base::interactivity::InteractiveImpl;
11use ferroui_base::layout::{LayoutHelper, Layoutable, LayoutableImpl};
12use ferroui_base::utilities::math_utilities::{max as math_max, min as math_min, MathUtilities};
13use ferroui_base::{
14    ferro_class, ferro_impl_classes, ferro_property, instantiate, AttachedProperty, FerroObjectImpl, FerroProperty, FerroPropertyChangedEventArgs, Rect, Ref, Size, StyledElementImpl,
15    StyledProperty, StyledPropertyOptions, VisualImpl,
16};
17use std::cell::{Cell, OnceCell, RefCell};
18use std::cmp::Ordering;
19use std::ops::{BitOr, BitOrAssign, Index};
20use std::rc::Rc;
21
22//
23//  The grid validity / property caches dirtiness flags.
24//
25//  The following flags let the grid track dirtiness in a more granular
26//  manner:
27//  * Valid???Structure flags indicate that elements were added or removed.
28//  * Valid???Layout flags indicate that the layout time portion of the
29//    information stored on the objects should be updated.
30//
31#[allow(dead_code)]
32const FLAG_VALID_DEFINITIONS_U_STRUCTURE: u32 = 0x0000_0001;
33#[allow(dead_code)]
34const FLAG_VALID_DEFINITIONS_V_STRUCTURE: u32 = 0x0000_0002;
35const FLAG_VALID_CELLS_STRUCTURE: u32 = 0x0000_0004;
36
37//  Boolean properties state.
38/// Show grid lines?
39const FLAG_SHOW_GRID_LINES_PROPERTY_VALUE: u32 = 0x0000_0100;
40
41//  Boolean flags.
42/// "0" when all notifications are ignored.
43const FLAG_LISTEN_TO_NOTIFICATIONS: u32 = 0x0000_1000;
44/// "1" if calculating to content in the U direction.
45const FLAG_SIZE_TO_CONTENT_U: u32 = 0x0000_2000;
46/// "1" if calculating to content in the V direction.
47const FLAG_SIZE_TO_CONTENT_V: u32 = 0x0000_4000;
48/// "1" if at least one cell belongs to a Star column.
49const FLAG_HAS_STAR_CELLS_U: u32 = 0x0000_8000;
50/// "1" if at least one cell belongs to a Star row.
51const FLAG_HAS_STAR_CELLS_V: u32 = 0x0001_0000;
52/// "1" if at least one cell of group 3 belongs to an Auto row.
53const FLAG_HAS_GROUP3_CELLS_IN_AUTO_ROWS: u32 = 0x0002_0000;
54/// "1" while in the context of the measure override.
55const FLAG_MEASURE_OVERRIDE_IN_PROGRESS: u32 = 0x0004_0000;
56/// "1" while in the context of the arrange override.
57const FLAG_ARRANGE_OVERRIDE_IN_PROGRESS: u32 = 0x0008_0000;
58
59/// 5 is an arbitrary constant chosen to end the measure loop.
60const LAYOUT_LOOP_MAX_COUNT: i32 = 5;
61
62/// The index that terminates a chain of cells.
63const NO_CELL: usize = i32::MAX as usize;
64
65ferroui_base::ferro_thread_local! {
66    /// The temporary array used during layout for various purposes. It holds
67    /// indices into the definitions being processed, or -1.
68    static TEMP_DEFINITIONS: RefCell<Vec<i32>> = const { RefCell::new(Vec::new()) };
69}
70
71/// Used internally; reflects the layout-time size type.
72#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
73pub(crate) struct LayoutTimeSizeType(u8);
74
75impl LayoutTimeSizeType {
76    pub(crate) const NONE: Self = Self(0x00);
77    pub(crate) const PIXEL: Self = Self(0x01);
78    pub(crate) const AUTO: Self = Self(0x02);
79    pub(crate) const STAR: Self = Self(0x04);
80
81    #[inline]
82    fn has_all_flags(self, flags: Self) -> bool {
83        (self.0 & flags.0) == flags.0
84    }
85}
86
87impl BitOr for LayoutTimeSizeType {
88    type Output = Self;
89
90    #[inline]
91    fn bitor(self, rhs: Self) -> Self {
92        Self(self.0 | rhs.0)
93    }
94}
95
96impl BitOrAssign for LayoutTimeSizeType {
97    #[inline]
98    fn bitor_assign(&mut self, rhs: Self) {
99        self.0 |= rhs.0;
100    }
101}
102
103/// Stores the calculated values of
104/// 1. the attached cell positioning properties;
105/// 2. the size type;
106/// 3. the index of the next cell in the group.
107#[derive(Clone, Copy, Default)]
108struct CellCache {
109    column_index: usize,
110    row_index: usize,
111    column_span: usize,
112    row_span: usize,
113    size_type_u: LayoutTimeSizeType,
114    size_type_v: LayoutTimeSizeType,
115    next: usize,
116}
117
118impl CellCache {
119    #[inline]
120    fn is_star_u(&self) -> bool {
121        self.size_type_u.has_all_flags(LayoutTimeSizeType::STAR)
122    }
123
124    #[inline]
125    fn is_auto_u(&self) -> bool {
126        self.size_type_u.has_all_flags(LayoutTimeSizeType::AUTO)
127    }
128
129    #[inline]
130    fn is_star_v(&self) -> bool {
131        self.size_type_v.has_all_flags(LayoutTimeSizeType::STAR)
132    }
133
134    #[inline]
135    fn is_auto_v(&self) -> bool {
136        self.size_type_v.has_all_flags(LayoutTimeSizeType::AUTO)
137    }
138}
139
140/// The key of a span in the span store.
141#[derive(Clone, Copy, PartialEq, Eq)]
142struct SpanKey {
143    /// The starting index of the span.
144    start: usize,
145    /// The span count.
146    count: usize,
147    /// True for columns; false for rows.
148    u: bool,
149}
150
151/// The collection of definitions used during the calculations: either the
152/// collection supplied by the user or a default collection of one element.
153#[derive(Clone)]
154enum DefinitionsSource {
155    Columns(ColumnDefinitions),
156    Rows(RowDefinitions),
157    Single(Ref<DefinitionBase>),
158}
159
160impl DefinitionsSource {
161    fn definitions(&self) -> Definitions {
162        match self {
163            DefinitionsSource::Columns(definitions) => Definitions::Columns(definitions.snapshot()),
164            DefinitionsSource::Rows(definitions) => Definitions::Rows(definitions.snapshot()),
165            DefinitionsSource::Single(definition) => Definitions::Single(definition.clone()),
166        }
167    }
168}
169
170/// A read-only list of definitions.
171enum Definitions {
172    Columns(Rc<Vec<Ref<ColumnDefinition>>>),
173    Rows(Rc<Vec<Ref<RowDefinition>>>),
174    Single(Ref<DefinitionBase>),
175}
176
177impl Definitions {
178    #[inline]
179    fn count(&self) -> usize {
180        match self {
181            Definitions::Columns(definitions) => definitions.len(),
182            Definitions::Rows(definitions) => definitions.len(),
183            Definitions::Single(_) => 1,
184        }
185    }
186}
187
188impl Index<usize> for Definitions {
189    type Output = DefinitionBase;
190
191    #[inline]
192    fn index(&self, index: usize) -> &DefinitionBase {
193        match self {
194            Definitions::Columns(definitions) => {
195                let definition: &ColumnDefinition = &definitions[index];
196                definition
197            }
198            Definitions::Rows(definitions) => {
199                let definition: &RowDefinition = &definitions[index];
200                definition
201            }
202            Definitions::Single(definition) => {
203                assert!(index == 0, "index out of range");
204                definition
205            }
206        }
207    }
208}
209
210/// The extended data instantiated on demand, when the grid handles a
211/// non-trivial case.
212struct ExtendedData {
213    /// The collection of column definitions (logical tree support).
214    column_definitions: RefCell<Option<ColumnDefinitions>>,
215    /// The collection of row definitions (logical tree support).
216    row_definitions: RefCell<Option<RowDefinitions>>,
217    /// The collection of column definitions used during the calculations.
218    definitions_u: RefCell<Option<DefinitionsSource>>,
219    /// The collection of row definitions used during the calculations.
220    definitions_v: RefCell<Option<DefinitionsSource>>,
221    /// The backing store for the logical children.
222    cell_caches_collection: RefCell<Vec<CellCache>>,
223    /// The index of the first cell in the first cell group.
224    cell_group1: Cell<usize>,
225    /// The index of the first cell in the second cell group.
226    cell_group2: Cell<usize>,
227    /// The index of the first cell in the third cell group.
228    cell_group3: Cell<usize>,
229    /// The index of the first cell in the fourth cell group.
230    cell_group4: Cell<usize>,
231}
232
233impl ExtendedData {
234    fn new() -> Self {
235        Self {
236            column_definitions: RefCell::new(None),
237            row_definitions: RefCell::new(None),
238            definitions_u: RefCell::new(None),
239            definitions_v: RefCell::new(None),
240            cell_caches_collection: RefCell::new(Vec::new()),
241            cell_group1: Cell::new(0),
242            cell_group2: Cell::new(0),
243            cell_group3: Cell::new(0),
244            cell_group4: Cell::new(0),
245        }
246    }
247}
248
249/// Clears a flag of a grid when dropped.
250struct FlagGuard<'a>(&'a Grid, u32);
251
252impl Drop for FlagGuard<'_> {
253    fn drop(&mut self) {
254        self.0.set_flags(false, self.1);
255    }
256}
257
258/// Compares two doubles: NaN is smaller than everything else and equal to
259/// itself.
260fn compare_to(x: f64, y: f64) -> Ordering {
261    x.partial_cmp(&y).unwrap_or_else(|| match (x.is_nan(), y.is_nan()) {
262        (true, true) => Ordering::Equal,
263        (true, false) => Ordering::Less,
264        _ => Ordering::Greater,
265    })
266}
267
268/// The logarithm of `a` in base 2, computed as the quotient of natural
269/// logarithms.
270#[inline]
271fn log2(a: f64) -> f64 {
272    a.ln() / 2.0_f64.ln()
273}
274
275/// Defines a flexible grid area that consists of columns and rows.
276#[repr(C)]
277pub struct Grid {
278    base: Panel,
279    /// The extended data instantiated on demand, for the handling of the
280    /// non-trivial case only.
281    ext_data: OnceCell<ExtendedData>,
282    /// The grid validity / property caches dirtiness flags.
283    flags: Cell<u32>,
284    /// Keeps track of definition indices.
285    definition_indices: RefCell<Option<Vec<i32>>>,
286    /// Stores unrounded values and rounding errors during layout rounding.
287    rounding_errors: RefCell<Option<Vec<f64>>>,
288    /// The visual that draws the grid lines, while they are shown.
289    grid_lines_renderer: RefCell<Option<Ref<GridLinesRenderer>>>,
290}
291
292ferro_class!(Grid: Panel);
293ferroui_base::ferro_class_info!(Grid { new: Grid::new });
294ferro_impl_classes!(Grid: StyledElementImpl, VisualImpl, InteractiveImpl, InputElementImpl, ControlImpl);
295
296ferroui_base::ferro_impl_classes!(Grid: FerroObjectImpl);
297
298impl LayoutableImpl for Grid {
299    /// Content measurement.
300    fn measure_override(this: &Self, constraint: Size) -> Size {
301        let grid_desired_size;
302        let ext_data = this.ext_data.get();
303
304        this.set_listen_to_notifications(true);
305        this.set_flags(true, FLAG_MEASURE_OVERRIDE_IN_PROGRESS);
306        let _in_progress = FlagGuard(this, FLAG_MEASURE_OVERRIDE_IN_PROGRESS);
307
308        match ext_data {
309            None => {
310                let mut desired_size = Size::default();
311                let children = this.children().snapshot();
312
313                for child in children.iter() {
314                    child.measure(constraint);
315                    let child_desired_size = child.desired_size();
316                    desired_size = Size::new(
317                        math_max(desired_size.width, child_desired_size.width),
318                        math_max(desired_size.height, child_desired_size.height),
319                    );
320                }
321                grid_desired_size = desired_size;
322            }
323            Some(ext_data) => {
324                {
325                    let size_to_content_u = constraint.width == f64::INFINITY;
326                    let size_to_content_v = constraint.height == f64::INFINITY;
327
328                    // Clear index information and rounding errors.
329                    if this.row_definitions_dirty() || this.column_definitions_dirty() {
330                        *this.definition_indices.borrow_mut() = None;
331
332                        if this.use_layout_rounding() {
333                            *this.rounding_errors.borrow_mut() = None;
334                        }
335                    }
336
337                    this.validate_definitions_u_structure();
338                    this.validate_definitions_layout(&this.definitions_u(), size_to_content_u);
339
340                    this.validate_definitions_v_structure();
341                    this.validate_definitions_layout(&this.definitions_v(), size_to_content_v);
342
343                    if this.size_to_content_u() != size_to_content_u || this.size_to_content_v() != size_to_content_v {
344                        this.set_cells_structure_dirty(true);
345                    }
346
347                    this.set_flags(size_to_content_u, FLAG_SIZE_TO_CONTENT_U);
348                    this.set_flags(size_to_content_v, FLAG_SIZE_TO_CONTENT_V);
349                }
350
351                this.validate_cells();
352
353                let definitions_u = this.definitions_u();
354                let definitions_v = this.definitions_v();
355
356                debug_assert!(definitions_u.count() > 0 && definitions_v.count() > 0);
357
358                //  Grid classifies cells into four groups depending on
359                //  the column / row type a cell belongs to (number corresponds to
360                //  group number):
361                //
362                //                   Px      Auto     Star
363                //               +--------+--------+--------+
364                //               |        |        |        |
365                //            Px |    1   |    1   |    3   |
366                //               |        |        |        |
367                //               +--------+--------+--------+
368                //               |        |        |        |
369                //          Auto |    1   |    1   |    3   |
370                //               |        |        |        |
371                //               +--------+--------+--------+
372                //               |        |        |        |
373                //          Star |    4   |    2   |    4   |
374                //               |        |        |        |
375                //               +--------+--------+--------+
376                //
377                //  The group number indicates the order in which cells are measured.
378                //  Certain order is necessary to be able to dynamically resolve star
379                //  columns / rows sizes which are used as input for measuring of
380                //  the cells belonging to them.
381                //
382                //  However, there are cases when topology of a grid causes cyclical
383                //  size dependencies. For example:
384                //
385                //
386                //                         column width="Auto"      column width="*"
387                //                      +----------------------+----------------------+
388                //                      |                      |                      |
389                //                      |                      |                      |
390                //                      |                      |                      |
391                //                      |                      |                      |
392                //  row height="Auto"   |                      |      cell 1 2        |
393                //                      |                      |                      |
394                //                      |                      |                      |
395                //                      |                      |                      |
396                //                      |                      |                      |
397                //                      +----------------------+----------------------+
398                //                      |                      |                      |
399                //                      |                      |                      |
400                //                      |                      |                      |
401                //                      |                      |                      |
402                //  row height="*"      |       cell 2 1       |                      |
403                //                      |                      |                      |
404                //                      |                      |                      |
405                //                      |                      |                      |
406                //                      |                      |                      |
407                //                      +----------------------+----------------------+
408                //
409                //  In order to accurately calculate constraint width for "cell 1 2"
410                //  (which is the remaining of grid's available width and calculated
411                //  value of Auto column), "cell 2 1" needs to be calculated first,
412                //  as it contributes to the Auto column's calculated value.
413                //  At the same time in order to accurately calculate constraint
414                //  height for "cell 2 1", "cell 1 2" needs to be calculated first,
415                //  as it contributes to Auto row height, which is used in the
416                //  computation of Star row resolved height.
417                //
418                //  to "break" this cyclical dependency we are making (arbitrary)
419                //  decision to treat cells like "cell 2 1" as if they appear in Auto
420                //  rows. And then recalculate them one more time when star row
421                //  heights are resolved.
422                //
423                //  (Or more strictly) the code below implement the following logic:
424                //
425                //                       +---------+
426                //                       |  enter  |
427                //                       +---------+
428                //                            |
429                //                            V
430                //                    +----------------+
431                //                    | Measure Group1 |
432                //                    +----------------+
433                //                            |
434                //                            V
435                //                          / - \
436                //                        /       \
437                //                  Y   /    Can    \    N
438                //            +--------|   Resolve   |-----------+
439                //            |         \  StarsV?  /            |
440                //            |           \       /              |
441                //            |             \ - /                |
442                //            V                                  V
443                //    +----------------+                       / - \
444                //    | Resolve StarsV |                     /       \
445                //    +----------------+               Y   /    Can    \    N
446                //            |                      +----|   Resolve   |------+
447                //            V                      |     \  StarsU?  /       |
448                //    +----------------+             |       \       /         |
449                //    | Measure Group2 |             |         \ - /           |
450                //    +----------------+             |                         V
451                //            |                      |                 +-----------------+
452                //            V                      |                 | Measure Group2' |
453                //    +----------------+             |                 +-----------------+
454                //    | Resolve StarsU |             |                         |
455                //    +----------------+             V                         V
456                //            |              +----------------+        +----------------+
457                //            V              | Resolve StarsU |        | Resolve StarsU |
458                //    +----------------+     +----------------+        +----------------+
459                //    | Measure Group3 |             |                         |
460                //    +----------------+             V                         V
461                //            |              +----------------+        +----------------+
462                //            |              | Measure Group3 |        | Measure Group3 |
463                //            |              +----------------+        +----------------+
464                //            |                      |                         |
465                //            |                      V                         V
466                //            |              +----------------+        +----------------+
467                //            |              | Resolve StarsV |        | Resolve StarsV |
468                //            |              +----------------+        +----------------+
469                //            |                      |                         |
470                //            |                      |                         V
471                //            |                      |                +------------------+
472                //            |                      |                | Measure Group2'' |
473                //            |                      |                +------------------+
474                //            |                      |                         |
475                //            +----------------------+-------------------------+
476                //                                   |
477                //                                   V
478                //                           +----------------+
479                //                           | Measure Group4 |
480                //                           +----------------+
481                //                                   |
482                //                                   V
483                //                               +--------+
484                //                               |  exit  |
485                //                               +--------+
486                //
487                //  where:
488                //  *   all [Measure GroupN] - regular children measure process -
489                //      each cell is measured given constraint size as an input
490                //      and each cell's desired size is accumulated on the
491                //      corresponding column / row;
492                //  *   [Measure Group2'] - is when each cell is measured with
493                //      infinite height as a constraint and a cell's desired
494                //      height is ignored;
495                //  *   [Measure Groups''] - is when each cell is measured (second
496                //      time during single measure override) regularly but its
497                //      returned width is ignored;
498                //
499                //  This algorithm is believed to be as close to ideal as possible.
500                //  It has the following drawbacks:
501                //  *   cells belonging to Group2 can be called to measure twice;
502                //  *   iff during second measure a cell belonging to Group2 returns
503                //      desired width greater than desired width returned the first
504                //      time, such a cell is going to be clipped, even though it
505                //      appears in Auto column.
506                //
507
508                this.measure_cells_group(ext_data.cell_group1.get(), false, false);
509                let combined_row_spacing = this.row_spacing() * (definitions_v.count() as f64 - 1.0);
510                let combined_column_spacing = this.column_spacing() * (definitions_u.count() as f64 - 1.0);
511                let inner_available_size = Size::new(
512                    constraint.width - combined_column_spacing,
513                    constraint.height - combined_row_spacing,
514                );
515                {
516                    //  After Group1 is measured, only Group3 may have cells
517                    //  belonging to Auto rows.
518                    let can_resolve_stars_v = !this.has_group3_cells_in_auto_rows();
519
520                    if can_resolve_stars_v {
521                        if this.has_star_cells_v() {
522                            this.resolve_star(&definitions_v, inner_available_size.height);
523                        }
524                        this.measure_cells_group(ext_data.cell_group2.get(), false, false);
525                        if this.has_star_cells_u() {
526                            this.resolve_star(&definitions_u, inner_available_size.width);
527                        }
528                        this.measure_cells_group(ext_data.cell_group3.get(), false, false);
529                    } else {
530                        //  If at least one cell exists in Group2, it must be
531                        //  measured before StarsU can be resolved.
532                        let can_resolve_stars_u = ext_data.cell_group2.get() > this.private_cells_len();
533                        if can_resolve_stars_u {
534                            if this.has_star_cells_u() {
535                                this.resolve_star(&definitions_u, inner_available_size.width);
536                            }
537                            this.measure_cells_group(ext_data.cell_group3.get(), false, false);
538                            if this.has_star_cells_v() {
539                                this.resolve_star(&definitions_v, inner_available_size.height);
540                            }
541                        } else {
542                            // This is a revision to the algorithm employed for
543                            // the cyclic dependency case described above. We
544                            // now repeatedly measure Group3 and Group2 until
545                            // their sizes settle. We also use a count
546                            // heuristic to break a loop in case of one.
547
548                            let mut has_desired_size_u_changed = false;
549                            let mut cnt = 0;
550
551                            // Cache Group2MinWidths & Group3MinHeights.
552                            let group2_min_sizes = this.cache_min_sizes(ext_data.cell_group2.get(), false);
553                            let group3_min_sizes = this.cache_min_sizes(ext_data.cell_group3.get(), true);
554
555                            this.measure_cells_group(ext_data.cell_group2.get(), false, true);
556
557                            loop {
558                                if has_desired_size_u_changed {
559                                    // Reset cached Group3Heights.
560                                    this.apply_cached_min_sizes(&group3_min_sizes, true);
561                                }
562
563                                if this.has_star_cells_u() {
564                                    this.resolve_star(&definitions_u, inner_available_size.width);
565                                }
566                                this.measure_cells_group(ext_data.cell_group3.get(), false, false);
567
568                                // Reset cached Group2Widths.
569                                this.apply_cached_min_sizes(&group2_min_sizes, false);
570
571                                if this.has_star_cells_v() {
572                                    this.resolve_star(&definitions_v, inner_available_size.height);
573                                }
574                                has_desired_size_u_changed = this.measure_cells_group(
575                                    ext_data.cell_group2.get(),
576                                    cnt == LAYOUT_LOOP_MAX_COUNT,
577                                    false,
578                                );
579
580                                if !has_desired_size_u_changed {
581                                    break;
582                                }
583                                cnt += 1;
584                                if cnt > LAYOUT_LOOP_MAX_COUNT {
585                                    break;
586                                }
587                            }
588                        }
589                    }
590                }
591
592                this.measure_cells_group(ext_data.cell_group4.get(), false, false);
593
594                grid_desired_size = Size::new(
595                    Self::calculate_desired_size(&definitions_u)
596                        + this.column_spacing() * (definitions_u.count() as f64 - 1.0),
597                    Self::calculate_desired_size(&definitions_v)
598                        + this.row_spacing() * (definitions_v.count() as f64 - 1.0),
599                );
600            }
601        }
602
603        grid_desired_size
604    }
605
606    /// Content arrangement.
607    fn arrange_override(this: &Self, arrange_size: Size) -> Size {
608        this.set_flags(true, FLAG_ARRANGE_OVERRIDE_IN_PROGRESS);
609        let _in_progress = FlagGuard(this, FLAG_ARRANGE_OVERRIDE_IN_PROGRESS);
610
611        if this.ext_data.get().is_none() {
612            let children = this.children().snapshot();
613
614            for child in children.iter() {
615                child.arrange(Rect::from_size(arrange_size));
616            }
617        } else {
618            let definitions_u = this.definitions_u();
619            let definitions_v = this.definitions_v();
620
621            debug_assert!(definitions_u.count() > 0 && definitions_v.count() > 0);
622            let row_spacing = this.row_spacing();
623            let column_spacing = this.column_spacing();
624            let combined_row_spacing = row_spacing * (definitions_v.count() as f64 - 1.0);
625            let combined_column_spacing = column_spacing * (definitions_u.count() as f64 - 1.0);
626            this.set_final_size(&definitions_u, arrange_size.width - combined_column_spacing, true);
627            this.set_final_size(&definitions_v, arrange_size.height - combined_row_spacing, false);
628
629            let children = this.children().snapshot();
630
631            for current_cell in 0..this.private_cells_len() {
632                let cell = &children[current_cell];
633                let cache = this.private_cell(current_cell);
634
635                let column_index = cache.column_index;
636                let row_index = cache.row_index;
637                let column_span = cache.column_span;
638                let row_span = cache.row_span;
639
640                let cell_rect = Rect::new(
641                    if column_index == 0 {
642                        0.0
643                    } else {
644                        definitions_u[column_index].final_offset()
645                    },
646                    if row_index == 0 {
647                        0.0
648                    } else {
649                        definitions_v[row_index].final_offset()
650                    },
651                    Self::get_final_size_for_range(&definitions_u, column_index, column_span, column_spacing),
652                    Self::get_final_size_for_range(&definitions_v, row_index, row_span, row_spacing),
653                );
654
655                cell.arrange(cell_rect);
656            }
657
658            // Update the render bound on the grid lines renderer visual.
659            if let Some(grid_lines_renderer) = this.ensure_grid_lines_renderer() {
660                grid_lines_renderer.update_render_bounds(arrange_size);
661            }
662        }
663
664        arrange_size
665    }
666}
667
668impl PanelImpl for Grid {
669    fn children_changed(this: &Self, e: &NotifyCollectionChangedEventArgs<'_, Ref<Control>>) {
670        this.set_cells_structure_dirty(true);
671        Self::parent_children_changed(this, e);
672    }
673}
674
675ferroui_base::ferro_properties! { impl Grid {
676    ferro_property!(
677        /// Defines the `ShowGridLines` property. This property is used mostly
678        /// for the simplification of visual debugging. When it is set to
679        /// true, grid lines are drawn to visualize the location of the grid
680        /// lines.
681        pub fn show_grid_lines_property() -> StyledProperty<bool> {
682            FerroProperty::register::<Grid, _>("ShowGridLines", false)
683        }
684    );
685
686    ferro_property!(
687        /// Defines the `RowSpacing` property.
688        pub fn row_spacing_property() -> StyledProperty<f64> {
689            FerroProperty::register::<Grid, _>("RowSpacing", 0.0)
690        }
691    );
692
693    ferro_property!(
694        /// Defines the `ColumnSpacing` property.
695        pub fn column_spacing_property() -> StyledProperty<f64> {
696            FerroProperty::register::<Grid, _>("ColumnSpacing", 0.0)
697        }
698    );
699
700    ferro_property!(
701        /// Defines the `Column` attached property, which can be set on any
702        /// element treated as a cell. It specifies the position of the child
703        /// with respect to the columns.
704        ///
705        /// Columns are 0-based: in order to appear in the first column, an
706        /// element should have the property set to 0, which is the default
707        /// value.
708        pub fn column_property() -> AttachedProperty<i32> {
709            FerroProperty::register_attached_with::<Grid, Control, _>(
710                "Column",
711                StyledPropertyOptions::new(0).validate(|v| *v >= 0),
712            )
713        }
714    );
715
716    ferro_property!(
717        /// Defines the `Row` attached property, which can be set on any
718        /// element treated as a cell. It specifies the position of the child
719        /// with respect to the rows.
720        ///
721        /// Rows are 0-based: in order to appear in the first row, an element
722        /// should have the property set to 0, which is the default value.
723        pub fn row_property() -> AttachedProperty<i32> {
724            FerroProperty::register_attached_with::<Grid, Control, _>(
725                "Row",
726                StyledPropertyOptions::new(0).validate(|v| *v >= 0),
727            )
728        }
729    );
730
731    ferro_property!(
732        /// Defines the `ColumnSpan` attached property, which can be set on
733        /// any element treated as a cell. It specifies the width of the child
734        /// with respect to the columns: a value of 2 means that the child
735        /// spans across two columns.
736        ///
737        /// The default value is 1.
738        pub fn column_span_property() -> AttachedProperty<i32> {
739            FerroProperty::register_attached_with::<Grid, Control, _>(
740                "ColumnSpan",
741                StyledPropertyOptions::new(1).validate(|v| *v > 0),
742            )
743        }
744    );
745
746    ferro_property!(
747        /// Defines the `RowSpan` attached property, which can be set on any
748        /// element treated as a cell. It specifies the height of the child
749        /// with respect to the row grid lines: a value of 3 means that the
750        /// child spans across three rows.
751        ///
752        /// The default value is 1.
753        pub fn row_span_property() -> AttachedProperty<i32> {
754            FerroProperty::register_attached_with::<Grid, Control, _>(
755                "RowSpan",
756                StyledPropertyOptions::new(1).validate(|v| *v > 0),
757            )
758        }
759    );
760
761    ferro_property!(
762        /// Defines the `IsSharedSizeScope` attached property, which marks the
763        /// scoping element for shared sizes.
764        pub fn is_shared_size_scope_property() -> AttachedProperty<bool> {
765            FerroProperty::register_attached::<Grid, Control, _>("IsSharedSizeScope", false)
766        }
767    );
768} }
769
770impl Grid {
771    fn static_constructor() {
772        let show_grid_lines = Self::show_grid_lines_property();
773        let column_spacing = Self::column_spacing_property();
774        let row_spacing = Self::row_spacing_property();
775        let is_shared_size_scope = Self::is_shared_size_scope_property();
776        let column = Self::column_property();
777        let column_span = Self::column_span_property();
778        let row = Self::row_property();
779        let row_span = Self::row_span_property();
780
781        show_grid_lines
782            .changed()
783            .add_class_handler::<Grid>(Self::on_show_grid_lines_property_changed);
784        column_spacing
785            .changed()
786            .add_class_handler::<Grid>(Self::on_spacing_property_changed);
787        row_spacing
788            .changed()
789            .add_class_handler::<Grid>(Self::on_spacing_property_changed);
790
791        is_shared_size_scope
792            .changed()
793            .add_class_handler::<Control>(DefinitionBase::on_is_shared_size_scope_property_changed);
794        column
795            .changed()
796            .add_class_handler::<Control>(Self::on_cell_attached_property_changed);
797        column_span
798            .changed()
799            .add_class_handler::<Control>(Self::on_cell_attached_property_changed);
800        row.changed()
801            .add_class_handler::<Control>(Self::on_cell_attached_property_changed);
802        row_span
803            .changed()
804            .add_class_handler::<Control>(Self::on_cell_attached_property_changed);
805
806        Layoutable::affects_measure::<Grid>(&[column_spacing.as_property(), row_spacing.as_property()]);
807        Panel::affects_parent_measure::<Grid>(&[
808            column.as_property(),
809            column_span.as_property(),
810            row.as_property(),
811            row_span.as_property(),
812        ]);
813    }
814
815    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
816    pub fn construct() -> Self {
817        Self {
818            base: Panel::construct(),
819            ext_data: OnceCell::new(),
820            flags: Cell::new(0),
821            definition_indices: RefCell::new(None),
822            rounding_errors: RefCell::new(None),
823            grid_lines_renderer: RefCell::new(None),
824        }
825    }
826
827    pub fn new() -> Ref<Self> {
828        instantiate(Self::construct())
829    }
830
831    /// Sets the `Column` property on a control.
832    pub fn set_column(element: &Control, value: i32) {
833        element.set_value(Self::column_property(), value);
834    }
835
836    /// Reads the `Column` property from a control.
837    pub fn get_column(element: &Control) -> i32 {
838        element.get_value(Self::column_property())
839    }
840
841    /// Sets the `Row` property on a control.
842    pub fn set_row(element: &Control, value: i32) {
843        element.set_value(Self::row_property(), value);
844    }
845
846    /// Reads the `Row` property from a control.
847    pub fn get_row(element: &Control) -> i32 {
848        element.get_value(Self::row_property())
849    }
850
851    /// Sets the `ColumnSpan` property on a control.
852    pub fn set_column_span(element: &Control, value: i32) {
853        element.set_value(Self::column_span_property(), value);
854    }
855
856    /// Reads the `ColumnSpan` property from a control.
857    pub fn get_column_span(element: &Control) -> i32 {
858        element.get_value(Self::column_span_property())
859    }
860
861    /// Sets the `RowSpan` property on a control.
862    pub fn set_row_span(element: &Control, value: i32) {
863        element.set_value(Self::row_span_property(), value);
864    }
865
866    /// Reads the `RowSpan` property from a control.
867    pub fn get_row_span(element: &Control) -> i32 {
868        element.get_value(Self::row_span_property())
869    }
870
871    /// Sets the `IsSharedSizeScope` property on a control.
872    pub fn set_is_shared_size_scope(element: &Control, value: bool) {
873        element.set_value(Self::is_shared_size_scope_property(), value);
874    }
875
876    /// Reads the `IsSharedSizeScope` property from a control.
877    pub fn get_is_shared_size_scope(element: &Control) -> bool {
878        element.get_value(Self::is_shared_size_scope_property())
879    }
880
881    /// Whether the grid lines are shown.
882    pub fn show_grid_lines(&self) -> bool {
883        self.get_value(Self::show_grid_lines_property())
884    }
885
886    pub fn set_show_grid_lines(&self, value: bool) {
887        self.set_value(Self::show_grid_lines_property(), value)
888    }
889
890    /// The size of the spacing to place between grid rows.
891    pub fn row_spacing(&self) -> f64 {
892        self.get_value(Self::row_spacing_property())
893    }
894
895    pub fn set_row_spacing(&self, value: f64) {
896        self.set_value(Self::row_spacing_property(), value)
897    }
898
899    /// The size of the spacing to place between grid columns.
900    pub fn column_spacing(&self) -> f64 {
901        self.get_value(Self::column_spacing_property())
902    }
903
904    pub fn set_column_spacing(&self, value: f64) {
905        self.set_value(Self::column_spacing_property(), value)
906    }
907
908    /// The collection of column definitions.
909    pub fn column_definitions(&self) -> ColumnDefinitions {
910        let ext_data = self.ext();
911        if let Some(column_definitions) = ext_data.column_definitions.borrow().as_ref() {
912            return column_definitions.clone();
913        }
914
915        let column_definitions = ColumnDefinitions::new();
916        column_definitions.set_parent(Some(&self.to_ref()));
917        *ext_data.column_definitions.borrow_mut() = Some(column_definitions.clone());
918        column_definitions
919    }
920
921    pub fn set_column_definitions(&self, value: ColumnDefinitions) {
922        let ext_data = self.ext();
923        //  Otherwise the outgoing definitions stay registered with their
924        //  shared size group and keep contributing to its minimum.
925        let old_definitions = ext_data.column_definitions.borrow().clone();
926        if let Some(old_definitions) = old_definitions {
927            if old_definitions != value {
928                old_definitions.set_parent(None);
929            }
930        }
931        *ext_data.column_definitions.borrow_mut() = Some(value.clone());
932        value.set_parent(Some(&self.to_ref()));
933        self.invalidate_measure();
934    }
935
936    /// The collection of row definitions.
937    pub fn row_definitions(&self) -> RowDefinitions {
938        let ext_data = self.ext();
939        if let Some(row_definitions) = ext_data.row_definitions.borrow().as_ref() {
940            return row_definitions.clone();
941        }
942
943        let row_definitions = RowDefinitions::new();
944        row_definitions.set_parent(Some(&self.to_ref()));
945        *ext_data.row_definitions.borrow_mut() = Some(row_definitions.clone());
946        row_definitions
947    }
948
949    pub fn set_row_definitions(&self, value: RowDefinitions) {
950        let ext_data = self.ext();
951        //  Otherwise the outgoing definitions stay registered with their
952        //  shared size group and keep contributing to its minimum.
953        let old_definitions = ext_data.row_definitions.borrow().clone();
954        if let Some(old_definitions) = old_definitions {
955            if old_definitions != value {
956                old_definitions.set_parent(None);
957            }
958        }
959        *ext_data.row_definitions.borrow_mut() = Some(value.clone());
960        value.set_parent(Some(&self.to_ref()));
961        self.invalidate_measure();
962    }
963
964    /// Invalidates the grid caches and makes the grid dirty for measure.
965    pub(crate) fn invalidate(&self) {
966        self.set_cells_structure_dirty(true);
967        self.invalidate_measure();
968    }
969
970    /// Returns the final width of a column.
971    ///
972    /// Used from the actual width of a column definition. Calculates the
973    /// final width using the offset data.
974    pub(crate) fn get_final_column_definition_width(&self, column_index: i32) -> f64 {
975        let mut value = 0.0;
976
977        //  Actual value calculations require the structure to be up-to-date.
978        if !self.column_definitions_dirty() {
979            let definitions = self.definitions_u();
980            let column_index = column_index as usize;
981            value = definitions[(column_index + 1) % definitions.count()].final_offset();
982            if column_index != 0 {
983                value -= definitions[column_index].final_offset();
984            }
985        }
986        value
987    }
988
989    /// Returns the final height of a row.
990    ///
991    /// Used from the actual height of a row definition. Calculates the final
992    /// height using the offset data.
993    pub(crate) fn get_final_row_definition_height(&self, row_index: i32) -> f64 {
994        let mut value = 0.0;
995
996        //  Actual value calculations require the structure to be up-to-date.
997        if !self.row_definitions_dirty() {
998            let definitions = self.definitions_v();
999            let row_index = row_index as usize;
1000            value = definitions[(row_index + 1) % definitions.count()].final_offset();
1001            if row_index != 0 {
1002                value -= definitions[row_index].final_offset();
1003            }
1004        }
1005        value
1006    }
1007
1008    /// Whether the measure override is in progress.
1009    #[allow(dead_code)]
1010    pub(crate) fn measure_override_in_progress(&self) -> bool {
1011        self.check_flags(FLAG_MEASURE_OVERRIDE_IN_PROGRESS)
1012    }
1013
1014    /// Whether the arrange override is in progress.
1015    #[allow(dead_code)]
1016    pub(crate) fn arrange_override_in_progress(&self) -> bool {
1017        self.check_flags(FLAG_ARRANGE_OVERRIDE_IN_PROGRESS)
1018    }
1019
1020    /// Whether the structure of the column definitions changed.
1021    pub(crate) fn column_definitions_dirty(&self) -> bool {
1022        self.column_definitions().is_dirty()
1023    }
1024
1025    pub(crate) fn set_column_definitions_dirty(&self, value: bool) {
1026        self.column_definitions().set_is_dirty(value)
1027    }
1028
1029    /// Whether the structure of the row definitions changed.
1030    pub(crate) fn row_definitions_dirty(&self) -> bool {
1031        self.row_definitions().is_dirty()
1032    }
1033
1034    pub(crate) fn set_row_definitions_dirty(&self, value: bool) {
1035        self.row_definitions().set_is_dirty(value)
1036    }
1037
1038    /// The extended data, created on demand.
1039    fn ext(&self) -> &ExtendedData {
1040        self.ext_data.get_or_init(ExtendedData::new)
1041    }
1042
1043    /// Lays out cells according to rows and columns, and creates lookup
1044    /// grids.
1045    fn validate_cells(&self) {
1046        if self.cells_structure_dirty() {
1047            self.validate_cells_core();
1048            self.set_cells_structure_dirty(false);
1049        }
1050    }
1051
1052    fn validate_cells_core(&self) {
1053        let children = self.children().snapshot();
1054        let ext_data = self.ext();
1055        let definitions_u = self.definitions_u();
1056        let definitions_v = self.definitions_v();
1057
1058        let mut cells = vec![CellCache::default(); children.len()];
1059        let mut cell_group1 = NO_CELL;
1060        let mut cell_group2 = NO_CELL;
1061        let mut cell_group3 = NO_CELL;
1062        let mut cell_group4 = NO_CELL;
1063
1064        let mut has_star_cells_u = false;
1065        let mut has_star_cells_v = false;
1066        let mut has_group3_cells_in_auto_rows = false;
1067
1068        for i in (0..cells.len()).rev() {
1069            let child = &children[i];
1070
1071            let mut cell = CellCache::default();
1072
1073            //  Read indices from the corresponding properties:
1074            //      clamp to value < number_of_columns
1075            //      column >= 0 is guaranteed by the property value validation callback
1076            cell.column_index = (Self::get_column(child) as usize).min(definitions_u.count() - 1);
1077            //      clamp to value < number_of_rows
1078            //      row >= 0 is guaranteed by the property value validation callback
1079            cell.row_index = (Self::get_row(child) as usize).min(definitions_v.count() - 1);
1080
1081            //  Read span properties:
1082            //      clamp to not exceed beyond the right side of the grid
1083            //      column_span > 0 is guaranteed by the property value validation callback
1084            cell.column_span = (Self::get_column_span(child) as usize).min(definitions_u.count() - cell.column_index);
1085
1086            //      clamp to not exceed beyond the bottom side of the grid
1087            //      row_span > 0 is guaranteed by the property value validation callback
1088            cell.row_span = (Self::get_row_span(child) as usize).min(definitions_v.count() - cell.row_index);
1089
1090            debug_assert!(cell.column_index < definitions_u.count());
1091            debug_assert!(cell.row_index < definitions_v.count());
1092
1093            //  Calculate and cache the length types for the child.
1094
1095            cell.size_type_u = Self::get_length_type_for_range(&definitions_u, cell.column_index, cell.column_span);
1096            cell.size_type_v = Self::get_length_type_for_range(&definitions_v, cell.row_index, cell.row_span);
1097
1098            has_star_cells_u |= cell.is_star_u();
1099            has_star_cells_v |= cell.is_star_v();
1100
1101            //  Distribute cells into four groups.
1102
1103            if !cell.is_star_v() {
1104                if !cell.is_star_u() {
1105                    cell.next = cell_group1;
1106                    cell_group1 = i;
1107                } else {
1108                    cell.next = cell_group3;
1109                    cell_group3 = i;
1110
1111                    //  Remember if this cell belongs to an auto row.
1112                    has_group3_cells_in_auto_rows |= cell.is_auto_v();
1113                }
1114            } else if cell.is_auto_u()
1115                //  Note below: if it spans through a Star column it is NOT Auto.
1116                && !cell.is_star_u()
1117            {
1118                cell.next = cell_group2;
1119                cell_group2 = i;
1120            } else {
1121                cell.next = cell_group4;
1122                cell_group4 = i;
1123            }
1124
1125            cells[i] = cell;
1126        }
1127
1128        *ext_data.cell_caches_collection.borrow_mut() = cells;
1129        ext_data.cell_group1.set(cell_group1);
1130        ext_data.cell_group2.set(cell_group2);
1131        ext_data.cell_group3.set(cell_group3);
1132        ext_data.cell_group4.set(cell_group4);
1133
1134        self.set_flags(has_star_cells_u, FLAG_HAS_STAR_CELLS_U);
1135        self.set_flags(has_star_cells_v, FLAG_HAS_STAR_CELLS_V);
1136        self.set_flags(has_group3_cells_in_auto_rows, FLAG_HAS_GROUP3_CELLS_IN_AUTO_ROWS);
1137    }
1138
1139    /// Initializes the U definitions either to the user supplied collection
1140    /// of column definitions or to a default single element collection.
1141    ///
1142    /// This is one of two methods where the column definitions and the U
1143    /// definitions are directly accessed. All the rest of the measure /
1144    /// arrange code must use the U definitions.
1145    fn validate_definitions_u_structure(&self) {
1146        if self.column_definitions_dirty() {
1147            let ext_data = self.ext();
1148            let column_definitions = ext_data.column_definitions.borrow().clone();
1149
1150            match column_definitions {
1151                None => {
1152                    if ext_data.definitions_u.borrow().is_none() {
1153                        *ext_data.definitions_u.borrow_mut() = Some(self.mockup_column());
1154                    }
1155                }
1156                Some(column_definitions) => {
1157                    if column_definitions.count() == 0 {
1158                        //  If the column definitions collection is empty,
1159                        //  mockup an array with one column.
1160                        *ext_data.definitions_u.borrow_mut() = Some(self.mockup_column());
1161                    } else {
1162                        *ext_data.definitions_u.borrow_mut() = Some(DefinitionsSource::Columns(column_definitions));
1163                    }
1164                }
1165            }
1166
1167            self.set_column_definitions_dirty(false);
1168        }
1169
1170        debug_assert!(self.definitions_u().count() > 0);
1171    }
1172
1173    /// Initializes the V definitions either to the user supplied collection
1174    /// of row definitions or to a default single element collection.
1175    ///
1176    /// This is one of two methods where the row definitions and the V
1177    /// definitions are directly accessed. All the rest of the measure /
1178    /// arrange code must use the V definitions.
1179    fn validate_definitions_v_structure(&self) {
1180        if self.row_definitions_dirty() {
1181            let ext_data = self.ext();
1182            let row_definitions = ext_data.row_definitions.borrow().clone();
1183
1184            match row_definitions {
1185                None => {
1186                    if ext_data.definitions_v.borrow().is_none() {
1187                        *ext_data.definitions_v.borrow_mut() = Some(self.mockup_row());
1188                    }
1189                }
1190                Some(row_definitions) => {
1191                    if row_definitions.count() == 0 {
1192                        //  If the row definitions collection is empty,
1193                        //  mockup an array with one row.
1194                        *ext_data.definitions_v.borrow_mut() = Some(self.mockup_row());
1195                    } else {
1196                        *ext_data.definitions_v.borrow_mut() = Some(DefinitionsSource::Rows(row_definitions));
1197                    }
1198                }
1199            }
1200
1201            self.set_row_definitions_dirty(false);
1202        }
1203
1204        debug_assert!(self.definitions_v().count() > 0);
1205    }
1206
1207    fn mockup_column(&self) -> DefinitionsSource {
1208        let definition = ColumnDefinition::new();
1209        definition.set_parent(Some(&self.to_ref()));
1210        DefinitionsSource::Single(definition.upcast())
1211    }
1212
1213    fn mockup_row(&self) -> DefinitionsSource {
1214        let definition = RowDefinition::new();
1215        definition.set_parent(Some(&self.to_ref()));
1216        DefinitionsSource::Single(definition.upcast())
1217    }
1218
1219    /// Validates the layout time size type information on the given array of
1220    /// definitions. Sets the minimum size and the measure sizes.
1221    ///
1222    /// If `treat_star_as_auto` is true then star definitions are treated as
1223    /// Auto.
1224    fn validate_definitions_layout(&self, definitions: &Definitions, treat_star_as_auto: bool) {
1225        for i in 0..definitions.count() {
1226            let definition = &definitions[i];
1227            definition.on_before_layout(self);
1228
1229            let mut user_min_size = definition.user_min_size();
1230            let user_max_size = definition.user_max_size();
1231            let user_size;
1232
1233            let definition_user_size = definition.user_size();
1234            match definition_user_size.grid_unit_type() {
1235                GridUnitType::Pixel => {
1236                    definition.set_size_type(LayoutTimeSizeType::PIXEL);
1237                    user_size = definition_user_size.value();
1238                    // This was brought with NewLayout and defeats the squishy
1239                    // behavior.
1240                    user_min_size = math_max(user_min_size, math_min(user_size, user_max_size));
1241                }
1242                GridUnitType::Auto => {
1243                    definition.set_size_type(LayoutTimeSizeType::AUTO);
1244                    user_size = f64::INFINITY;
1245                }
1246                GridUnitType::Star => {
1247                    if treat_star_as_auto {
1248                        definition.set_size_type(LayoutTimeSizeType::AUTO);
1249                        user_size = f64::INFINITY;
1250                    } else {
1251                        definition.set_size_type(LayoutTimeSizeType::STAR);
1252                        user_size = f64::INFINITY;
1253                    }
1254                }
1255            }
1256
1257            definition.update_min_size(user_min_size);
1258            definition.set_measure_size(math_max(user_min_size, math_min(user_size, user_max_size)));
1259        }
1260    }
1261
1262    fn cache_min_sizes(&self, cells_head: usize, is_rows: bool) -> Vec<f64> {
1263        let definitions = if is_rows {
1264            self.definitions_v()
1265        } else {
1266            self.definitions_u()
1267        };
1268        let mut min_sizes = vec![-1.0; definitions.count()];
1269
1270        let cells_len = self.private_cells_len();
1271        let mut i = cells_head;
1272        loop {
1273            let cell = self.private_cell(i);
1274            if is_rows {
1275                min_sizes[cell.row_index] = definitions[cell.row_index].raw_min_size();
1276            } else {
1277                min_sizes[cell.column_index] = definitions[cell.column_index].raw_min_size();
1278            }
1279
1280            i = cell.next;
1281            if i >= cells_len {
1282                break;
1283            }
1284        }
1285
1286        min_sizes
1287    }
1288
1289    fn apply_cached_min_sizes(&self, min_sizes: &[f64], is_rows: bool) {
1290        let definitions = if is_rows {
1291            self.definitions_v()
1292        } else {
1293            self.definitions_u()
1294        };
1295        for (i, min_size) in min_sizes.iter().enumerate() {
1296            if MathUtilities::greater_than_or_close(*min_size, 0.0) {
1297                definitions[i].set_min_size(*min_size);
1298            }
1299        }
1300    }
1301
1302    /// Measures one group of cells.
1303    ///
1304    /// * `cells_head`: the head index of the cells chain.
1305    /// * `ignore_desired_size_u`: when true the desired width of the cells is
1306    ///   not registered in the columns.
1307    /// * `force_infinity_v`: passed through to `measure_cell`. When true the
1308    ///   desired height of the cells is not registered in the rows.
1309    ///
1310    /// Returns whether the desired width of a cell has changed.
1311    fn measure_cells_group(&self, cells_head: usize, ignore_desired_size_u: bool, force_infinity_v: bool) -> bool {
1312        let mut has_desired_size_u_changed = false;
1313
1314        let cells_len = self.private_cells_len();
1315        if cells_head >= cells_len {
1316            return has_desired_size_u_changed;
1317        }
1318
1319        let children = self.children().snapshot();
1320        let definitions_u = self.definitions_u();
1321        let definitions_v = self.definitions_v();
1322        let mut span_store: Option<Vec<(SpanKey, f64)>> = None;
1323        let ignore_desired_size_v = force_infinity_v;
1324
1325        let mut i = cells_head;
1326        loop {
1327            let child = &children[i];
1328            let cell = self.private_cell(i);
1329            let old_width = child.desired_size().width;
1330
1331            self.measure_cell(&definitions_u, &definitions_v, child, &cell, force_infinity_v);
1332
1333            let desired_size = child.desired_size();
1334            has_desired_size_u_changed |= !MathUtilities::are_close(old_width, desired_size.width);
1335
1336            if !ignore_desired_size_u {
1337                if cell.column_span == 1 {
1338                    let definition = &definitions_u[cell.column_index];
1339                    definition.update_min_size(math_min(desired_size.width, definition.user_max_size()));
1340                } else {
1341                    Self::register_span(
1342                        &mut span_store,
1343                        cell.column_index,
1344                        cell.column_span,
1345                        true,
1346                        desired_size.width,
1347                    );
1348                }
1349            }
1350
1351            if !ignore_desired_size_v {
1352                if cell.row_span == 1 {
1353                    let definition = &definitions_v[cell.row_index];
1354                    definition.update_min_size(math_min(desired_size.height, definition.user_max_size()));
1355                } else {
1356                    Self::register_span(
1357                        &mut span_store,
1358                        cell.row_index,
1359                        cell.row_span,
1360                        false,
1361                        desired_size.height,
1362                    );
1363                }
1364            }
1365
1366            i = cell.next;
1367            if i >= cells_len {
1368                break;
1369            }
1370        }
1371
1372        if let Some(span_store) = span_store {
1373            for (key, desired_size) in span_store {
1374                self.ensure_min_size_in_definition_range(
1375                    if key.u { &definitions_u } else { &definitions_v },
1376                    key.start,
1377                    key.count,
1378                    if key.u {
1379                        self.column_spacing()
1380                    } else {
1381                        self.row_spacing()
1382                    },
1383                    desired_size,
1384                );
1385            }
1386        }
1387
1388        has_desired_size_u_changed
1389    }
1390
1391    /// Registers a span information for delayed processing. If an entry
1392    /// already exists the biggest value is stored.
1393    ///
1394    /// `u` is true if this is a column span and false if this is a row span.
1395    fn register_span(store: &mut Option<Vec<(SpanKey, f64)>>, start: usize, count: usize, u: bool, value: f64) {
1396        let store = store.get_or_insert_with(Vec::new);
1397
1398        let key = SpanKey { start, count, u };
1399        match store.iter_mut().find(|(k, _)| *k == key) {
1400            Some((_, o)) => {
1401                if value > *o {
1402                    *o = value;
1403                }
1404            }
1405            None => store.push((key, value)),
1406        }
1407    }
1408
1409    /// Takes care of measuring a single cell.
1410    ///
1411    /// If `force_infinity_v` is true then the cell is always calculated to
1412    /// an infinite height.
1413    fn measure_cell(
1414        &self,
1415        definitions_u: &Definitions,
1416        definitions_v: &Definitions,
1417        child: &Control,
1418        cell: &CellCache,
1419        force_infinity_v: bool,
1420    ) {
1421        let cell_measure_width = if cell.is_auto_u() && !cell.is_star_u() {
1422            //  If the cell belongs to at least one Auto column and not a
1423            //  single Star column then it should be calculated "to content",
1424            //  thus it is possible to "shortcut" the calculations and simply
1425            //  assign infinity here.
1426            f64::INFINITY
1427        } else {
1428            //  Otherwise...
1429            Self::get_measure_size_for_range(
1430                definitions_u,
1431                cell.column_index,
1432                cell.column_span,
1433                self.column_spacing(),
1434            )
1435        };
1436
1437        let cell_measure_height = if force_infinity_v {
1438            f64::INFINITY
1439        } else if cell.is_auto_v() && !cell.is_star_v() {
1440            //  If the cell belongs to at least one Auto row and not a single
1441            //  Star row then it should be calculated "to content", thus it is
1442            //  possible to "shortcut" the calculations and simply assign
1443            //  infinity here.
1444            f64::INFINITY
1445        } else {
1446            Self::get_measure_size_for_range(definitions_v, cell.row_index, cell.row_span, self.row_spacing())
1447        };
1448
1449        let child_constraint = Size::new(cell_measure_width, cell_measure_height);
1450        child.measure(child_constraint);
1451    }
1452
1453    /// Calculates the one dimensional measure size of the given range of
1454    /// definitions.
1455    ///
1456    /// For "Auto" definitions the minimum size is used in place of the
1457    /// preferred size.
1458    fn get_measure_size_for_range(definitions: &Definitions, start: usize, count: usize, spacing: f64) -> f64 {
1459        debug_assert!(0 < count && (start + count) <= definitions.count());
1460
1461        let mut measure_size = -spacing;
1462
1463        for i in (start..start + count).rev() {
1464            let definition = &definitions[i];
1465            measure_size += spacing
1466                + if definition.size_type() == LayoutTimeSizeType::AUTO {
1467                    definition.min_size()
1468                } else {
1469                    definition.measure_size()
1470                };
1471        }
1472
1473        measure_size
1474    }
1475
1476    /// Accumulates the length type information of the given range of
1477    /// definitions.
1478    fn get_length_type_for_range(definitions: &Definitions, start: usize, count: usize) -> LayoutTimeSizeType {
1479        debug_assert!(0 < count && (start + count) <= definitions.count());
1480
1481        let mut length_type = LayoutTimeSizeType::NONE;
1482
1483        for i in (start..start + count).rev() {
1484            length_type |= definitions[i].size_type();
1485        }
1486
1487        length_type
1488    }
1489
1490    /// Distributes a minimum size back to a range of an array of
1491    /// definitions.
1492    ///
1493    /// `desired_size` is the minimum size that should "fit" into the
1494    /// definitions range.
1495    fn ensure_min_size_in_definition_range(
1496        &self,
1497        definitions: &Definitions,
1498        start: usize,
1499        count: usize,
1500        spacing: f64,
1501        desired_size: f64,
1502    ) {
1503        debug_assert!(1 < count && (start + count) <= definitions.count());
1504
1505        // The spacing between the definitions that this element spans through
1506        // must not be distributed.
1507        let requested_size = math_max(desired_size - spacing * (count as f64 - 1.0), 0.0);
1508
1509        //  Avoid processing when asked to distribute "0".
1510        if MathUtilities::is_zero(requested_size) {
1511            return;
1512        }
1513
1514        //  The temp array is used to remember definitions for sorting.
1515        let mut temp_definitions = Self::take_temp_definitions(count);
1516        let end = start + count;
1517        let mut auto_definitions_count = 0;
1518        let mut range_min_size = 0.0;
1519        let mut range_preferred_size = 0.0;
1520        let mut range_max_size = 0.0;
1521        let mut max_max_size = 0.0; //  maximum of maximum sizes
1522
1523        //  First accumulate the necessary information:
1524        //  a) sum up the sizes in the range;
1525        //  b) count the number of auto definitions in the range;
1526        //  c) initialize the temp array
1527        //  d) cache the maximum size into the size cache
1528        //  e) accumulate the max of max sizes
1529        for i in start..end {
1530            let definition = &definitions[i];
1531            let min_size = definition.min_size();
1532            let preferred_size = definition.preferred_size();
1533            let max_size = math_max(definition.user_max_size(), min_size);
1534
1535            range_min_size += min_size;
1536            range_preferred_size += preferred_size;
1537            range_max_size += max_size;
1538
1539            definition.set_size_cache(max_size);
1540
1541            //  Sanity check: no matter what, but the min size must always be
1542            //  the smaller; the max size must be the biggest; and the
1543            //  preferred should be in between.
1544            debug_assert!(
1545                min_size <= preferred_size
1546                    && preferred_size <= max_size
1547                    && range_min_size <= range_preferred_size
1548                    && range_preferred_size <= range_max_size
1549            );
1550
1551            if max_max_size < max_size {
1552                max_max_size = max_size;
1553            }
1554            if definition.user_size().is_auto() {
1555                auto_definitions_count += 1;
1556            }
1557            temp_definitions[i - start] = i as i32;
1558        }
1559
1560        //  Avoid processing if the range is already big enough.
1561        if requested_size > range_min_size {
1562            if requested_size <= range_preferred_size {
1563                //
1564                //  The requested size fits into the preferred size of the range.
1565                //  Distribute according to the following logic:
1566                //  * do not distribute into auto definitions - they should
1567                //    continue to stay "tight";
1568                //  * for all non-auto definitions distribute to equi-size min
1569                //    sizes, without exceeding the preferred size.
1570                //
1571                //  In order to achieve that, definitions are sorted in a way
1572                //  that all auto definitions are first, then definitions
1573                //  follow ascending order with the preferred size as the key
1574                //  of sorting.
1575                //
1576                temp_definitions[..count].sort_by(|x, y| {
1577                    Self::span_preferred_distribution_order(&definitions[*x as usize], &definitions[*y as usize])
1578                });
1579
1580                let mut size_to_distribute = requested_size;
1581                let mut i = 0;
1582                while i < auto_definitions_count {
1583                    let temp_definition = &definitions[temp_definitions[i] as usize];
1584
1585                    //  Sanity check: only auto definitions allowed in this loop.
1586                    debug_assert!(temp_definition.user_size().is_auto());
1587
1588                    //  Adjust the size to distribute by subtracting the auto
1589                    //  definition min size.
1590                    size_to_distribute -= temp_definition.min_size();
1591                    i += 1;
1592                }
1593
1594                while i < count {
1595                    let temp_definition = &definitions[temp_definitions[i] as usize];
1596
1597                    //  Sanity check: no auto definitions allowed in this loop.
1598                    debug_assert!(!temp_definition.user_size().is_auto());
1599
1600                    let new_min_size = math_min(
1601                        size_to_distribute / (count - i) as f64,
1602                        temp_definition.preferred_size(),
1603                    );
1604                    if new_min_size > temp_definition.min_size() {
1605                        temp_definition.update_min_size(new_min_size);
1606                    }
1607                    size_to_distribute -= new_min_size;
1608                    i += 1;
1609                }
1610
1611                //  Sanity check: the requested size must all be distributed.
1612                debug_assert!(MathUtilities::is_zero(size_to_distribute));
1613            } else if requested_size <= range_max_size {
1614                //
1615                //  The requested size is bigger than the preferred size, but
1616                //  fits into the max size of the range. Distribute according
1617                //  to the following logic:
1618                //  * do not distribute into auto definitions, if possible -
1619                //    they should continue to stay "tight";
1620                //  * for all non-auto definitions distribute to equi-size min
1621                //    sizes, without exceeding the max size.
1622                //
1623                //  In order to achieve that, definitions are sorted in a way
1624                //  that all non-auto definitions are last, then definitions
1625                //  follow ascending order with the max size as the key of
1626                //  sorting.
1627                //
1628                temp_definitions[..count].sort_by(|x, y| {
1629                    Self::span_max_distribution_order(&definitions[*x as usize], &definitions[*y as usize])
1630                });
1631
1632                let mut size_to_distribute = requested_size - range_preferred_size;
1633                let mut i = 0;
1634                while i < count - auto_definitions_count {
1635                    let temp_definition = &definitions[temp_definitions[i] as usize];
1636
1637                    //  Sanity check: no auto definitions allowed in this loop.
1638                    debug_assert!(!temp_definition.user_size().is_auto());
1639
1640                    let preferred_size = temp_definition.preferred_size();
1641                    let new_min_size =
1642                        preferred_size + size_to_distribute / (count - auto_definitions_count - i) as f64;
1643                    temp_definition.update_min_size(math_min(new_min_size, temp_definition.size_cache()));
1644                    size_to_distribute -= temp_definition.min_size() - preferred_size;
1645                    i += 1;
1646                }
1647
1648                while i < count {
1649                    let temp_definition = &definitions[temp_definitions[i] as usize];
1650
1651                    //  Sanity check: only auto definitions allowed in this loop.
1652                    debug_assert!(temp_definition.user_size().is_auto());
1653
1654                    let preferred_size = temp_definition.min_size();
1655                    let new_min_size = preferred_size + size_to_distribute / (count - i) as f64;
1656                    temp_definition.update_min_size(math_min(new_min_size, temp_definition.size_cache()));
1657                    size_to_distribute -= temp_definition.min_size() - preferred_size;
1658                    i += 1;
1659                }
1660
1661                //  Sanity check: the requested size must all be distributed.
1662                debug_assert!(MathUtilities::is_zero(size_to_distribute));
1663            } else {
1664                //
1665                //  The requested size is bigger than the max size of the range.
1666                //  Distribute according to the following logic:
1667                //  * for all definitions distribute to equi-size min sizes.
1668                //
1669                let equal_size = requested_size / count as f64;
1670
1671                if equal_size < max_max_size && !MathUtilities::are_close(equal_size, max_max_size) {
1672                    //  The equi-size is less than the maximum of max sizes.
1673                    //  In this case distribute so that smaller definitions
1674                    //  grow faster than bigger ones.
1675                    let total_remaining_size = max_max_size * count as f64 - range_max_size;
1676                    let size_to_distribute = requested_size - range_max_size;
1677
1678                    //  Sanity check: the total remaining size and the size to
1679                    //  distribute must be real positive numbers.
1680                    debug_assert!(
1681                        total_remaining_size.is_finite()
1682                            && total_remaining_size > 0.0
1683                            && size_to_distribute.is_finite()
1684                            && size_to_distribute > 0.0
1685                    );
1686
1687                    for temp_definition in temp_definitions[..count].iter() {
1688                        let temp_definition = &definitions[*temp_definition as usize];
1689
1690                        let delta_size =
1691                            (max_max_size - temp_definition.size_cache()) * size_to_distribute / total_remaining_size;
1692                        temp_definition.update_min_size(temp_definition.size_cache() + delta_size);
1693                    }
1694                } else {
1695                    //
1696                    //  The equi-size is greater or equal to the maximum of max
1697                    //  sizes. All definitions receive the equal size as their
1698                    //  min sizes.
1699                    //
1700                    for temp_definition in temp_definitions[..count].iter() {
1701                        definitions[*temp_definition as usize].update_min_size(equal_size);
1702                    }
1703                }
1704            }
1705        }
1706
1707        Self::return_temp_definitions(temp_definitions);
1708    }
1709
1710    /// Resolves the stars of the given array of definitions.
1711    ///
1712    /// Must initialize the layout size for all Star entries in the given
1713    /// array of definitions.
1714    fn resolve_star(&self, definitions: &Definitions, available_size: f64) {
1715        self.resolve_star_max_discrepancy(definitions, available_size);
1716    }
1717
1718    // New implementation as of 4.7. Several improvements:
1719    // 1. Allocate to *-defs hitting their min or max constraints, before allocating
1720    //      to other *-defs.  A def that hits its min uses more space than its
1721    //      proportional share, reducing the space available to everyone else.
1722    //      The legacy algorithm deducted this space only from defs processed
1723    //      after the min;  the new algorithm deducts it proportionally from all
1724    //      defs.   This avoids the "*-defs exceed available space" problem,
1725    //      and other related problems where *-defs don't receive proportional
1726    //      allocations even though no constraints are preventing it.
1727    // 2. When multiple defs hit min or max, resolve the one with maximum
1728    //      discrepancy (defined below).   This avoids discontinuities - small
1729    //      change in available space resulting in large change to one def's allocation.
1730    // 3. Correct handling of large *-values, including Infinity.
1731    fn resolve_star_max_discrepancy(&self, definitions: &Definitions, available_size: f64) {
1732        let def_count = definitions.count();
1733        let mut temp_definitions = Self::take_temp_definitions(def_count * 2);
1734        let mut min_count;
1735        let mut max_count;
1736        let mut taken_size = 0.0;
1737        let mut total_star_weight;
1738        let mut star_count = 0; // number of unresolved *-definitions
1739        let mut scale = 1.0; // scale factor applied to each *-weight;  negative means "Infinity is present"
1740
1741        // Phase 1.  Determine the maximum *-weight and prepare to adjust *-weights
1742        let mut max_star = 0.0;
1743        for i in 0..def_count {
1744            let def = &definitions[i];
1745
1746            if def.size_type() == LayoutTimeSizeType::STAR {
1747                star_count += 1;
1748                def.set_measure_size(1.0); // meaning "not yet resolved in phase 3"
1749                let value = def.user_size().value();
1750                if value > max_star {
1751                    max_star = value;
1752                }
1753            }
1754        }
1755
1756        if max_star == f64::INFINITY {
1757            // negative scale means one or more of the weights was Infinity
1758            scale = -1.0;
1759        } else if star_count > 0 {
1760            // if maxStar * starCount > Double.Max, summing all the weights could cause
1761            // floating-point overflow.  To avoid that, scale the weights by a factor to keep
1762            // the sum within limits.  Choose a power of 2, to preserve precision.
1763            let power = log2(f64::MAX / max_star / star_count as f64).floor();
1764            if power < 0.0 {
1765                scale = 2.0_f64.powf(power - 4.0); // -4 is just for paranoia
1766            }
1767        }
1768
1769        // normally Phases 2 and 3 execute only once.  But certain unusual combinations of weights
1770        // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3.
1771        // More explanation below...
1772        let mut run_phase_2_and_3 = true;
1773        while run_phase_2_and_3 {
1774            // Phase 2.   Compute total *-weight W and available space S.
1775            // For *-items that have Min or Max constraints, compute the ratios used to decide
1776            // whether proportional space is too big or too small and add the item to the
1777            // corresponding list.  (The "min" list is in the first half of the temp definitions,
1778            // the "max" list in the second half.  The temp definitions have capacity at least
1779            // 2*defCount, so there's room for both lists.)
1780            total_star_weight = 0.0;
1781            taken_size = 0.0;
1782            min_count = 0;
1783            max_count = 0;
1784
1785            for i in 0..def_count {
1786                let def = &definitions[i];
1787
1788                let size_type = def.size_type();
1789                if size_type == LayoutTimeSizeType::AUTO {
1790                    taken_size += def.min_size();
1791                } else if size_type == LayoutTimeSizeType::PIXEL {
1792                    taken_size += def.measure_size();
1793                } else if size_type == LayoutTimeSizeType::STAR {
1794                    if def.measure_size() < 0.0 {
1795                        taken_size += -def.measure_size(); // already resolved
1796                    } else {
1797                        let star_weight = Self::star_weight(def, scale);
1798                        total_star_weight += star_weight;
1799
1800                        if def.min_size() > 0.0 {
1801                            // store ratio w/min in MeasureSize (for now)
1802                            temp_definitions[min_count] = i as i32;
1803                            min_count += 1;
1804                            def.set_measure_size(star_weight / def.min_size());
1805                        }
1806
1807                        let effective_max_size = math_max(def.min_size(), def.user_max_size());
1808                        if effective_max_size != f64::INFINITY {
1809                            // store ratio w/max in SizeCache (for now)
1810                            temp_definitions[def_count + max_count] = i as i32;
1811                            max_count += 1;
1812                            def.set_size_cache(star_weight / effective_max_size);
1813                        }
1814                    }
1815                }
1816            }
1817
1818            // Phase 3.  Resolve *-items whose proportional sizes are too big or too small.
1819            let min_count_phase2 = min_count;
1820            let max_count_phase2 = max_count;
1821            let mut taken_star_weight = 0.0;
1822            let mut remaining_available_size = available_size - taken_size;
1823            let mut remaining_star_weight = total_star_weight - taken_star_weight;
1824            // Sort by w/min (stored in MeasureSize), descending. The list is
1825            // queried from the back, i.e. in ascending order of w/min.
1826            temp_definitions[..min_count].sort_by(|x, y| {
1827                compare_to(
1828                    definitions[*y as usize].measure_size(),
1829                    definitions[*x as usize].measure_size(),
1830                )
1831            });
1832            // Sort by w/max (stored in SizeCache), ascending. The list is
1833            // queried from the back, i.e. in descending order of w/max.
1834            temp_definitions[def_count..def_count + max_count].sort_by(|x, y| {
1835                compare_to(
1836                    definitions[*x as usize].size_cache(),
1837                    definitions[*y as usize].size_cache(),
1838                )
1839            });
1840
1841            while min_count + max_count > 0 && remaining_available_size > 0.0 {
1842                // the calculation
1843                //            remainingStarWeight = totalStarWeight - takenStarWeight
1844                // is subject to catastrophic cancellation if the two terms are nearly equal,
1845                // which leads to meaningless results.   Check for that, and recompute from
1846                // the remaining definitions.   [This leads to quadratic behavior in really
1847                // pathological cases - but they'd never arise in practice.]
1848                const STAR_FACTOR: f64 = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate
1849                if remaining_star_weight < total_star_weight * STAR_FACTOR {
1850                    taken_star_weight = 0.0;
1851                    total_star_weight = 0.0;
1852
1853                    for i in 0..def_count {
1854                        let def = &definitions[i];
1855                        if def.size_type() == LayoutTimeSizeType::STAR && def.measure_size() > 0.0 {
1856                            total_star_weight += Self::star_weight(def, scale);
1857                        }
1858                    }
1859
1860                    remaining_star_weight = total_star_weight - taken_star_weight;
1861                }
1862
1863                let min_ratio = if min_count > 0 {
1864                    definitions[temp_definitions[min_count - 1] as usize].measure_size()
1865                } else {
1866                    f64::INFINITY
1867                };
1868                let max_ratio = if max_count > 0 {
1869                    definitions[temp_definitions[def_count + max_count - 1] as usize].size_cache()
1870                } else {
1871                    -1.0
1872                };
1873
1874                // choose the def with larger ratio to the current proportion ("max discrepancy")
1875                let proportion = remaining_star_weight / remaining_available_size;
1876                let choose_min = Self::choose(min_ratio, max_ratio, proportion);
1877
1878                // if no def was chosen, advance to phase 4;  the current proportion doesn't
1879                // conflict with any min or max values.
1880                let Some(choose_min) = choose_min else {
1881                    break;
1882                };
1883
1884                // get the chosen definition and its resolved size
1885                let resolved_def;
1886                let resolved_size;
1887                if choose_min {
1888                    resolved_def = &definitions[temp_definitions[min_count - 1] as usize];
1889                    resolved_size = resolved_def.min_size();
1890                    min_count -= 1;
1891                } else {
1892                    resolved_def = &definitions[temp_definitions[def_count + max_count - 1] as usize];
1893                    resolved_size = math_max(resolved_def.min_size(), resolved_def.user_max_size());
1894                    max_count -= 1;
1895                }
1896
1897                // resolve the chosen def, deduct its contributions from W and S.
1898                // Defs resolved in phase 3 are marked by storing the negative of their resolved
1899                // size in MeasureSize, to distinguish them from a pending def.
1900                taken_size += resolved_size;
1901                resolved_def.set_measure_size(-resolved_size);
1902                taken_star_weight += Self::star_weight(resolved_def, scale);
1903                star_count -= 1;
1904
1905                remaining_available_size = available_size - taken_size;
1906                remaining_star_weight = total_star_weight - taken_star_weight;
1907
1908                // advance to the next candidate defs, removing ones that have been resolved.
1909                // Both counts are advanced, as a def might appear in both lists.
1910                while min_count > 0 && definitions[temp_definitions[min_count - 1] as usize].measure_size() < 0.0 {
1911                    min_count -= 1;
1912                    temp_definitions[min_count] = -1;
1913                }
1914                while max_count > 0
1915                    && definitions[temp_definitions[def_count + max_count - 1] as usize].measure_size() < 0.0
1916                {
1917                    max_count -= 1;
1918                    temp_definitions[def_count + max_count] = -1;
1919                }
1920            }
1921
1922            // decide whether to run Phase2 and Phase3 again.  There are 3 cases:
1923            // 1. There is space available, and *-defs remaining.  This is the
1924            //      normal case - move on to Phase 4 to allocate the remaining
1925            //      space proportionally to the remaining *-defs.
1926            // 2. There is space available, but no *-defs.  This implies at least one
1927            //      def was resolved as 'max', taking less space than its proportion.
1928            //      If there are also 'min' defs, reconsider them - we can give
1929            //      them more space.   If not, all the *-defs are 'max', so there's
1930            //      no way to use all the available space.
1931            // 3. We allocated too much space.   This implies at least one def was
1932            //      resolved as 'min'.  If there are also 'max' defs, reconsider
1933            //      them, otherwise the over-allocation is an inevitable consequence
1934            //      of the given min constraints.
1935            // Note that if we return to Phase2, at least one *-def will have been
1936            // resolved.  This guarantees we don't run Phase2+3 infinitely often.
1937            run_phase_2_and_3 = false;
1938            if star_count == 0 && taken_size < available_size {
1939                // if no *-defs remain and we haven't allocated all the space, reconsider the defs
1940                // resolved as 'min'.   Their allocation can be increased to make up the gap.
1941                for i in min_count..min_count_phase2 {
1942                    if temp_definitions[i] >= 0 {
1943                        let def = &definitions[temp_definitions[i] as usize];
1944                        def.set_measure_size(1.0); // mark as 'not yet resolved'
1945                        star_count += 1;
1946                        run_phase_2_and_3 = true; // found a candidate, so re-run Phases 2 and 3
1947                    }
1948                }
1949            }
1950
1951            if taken_size > available_size {
1952                // if we've allocated too much space, reconsider the defs
1953                // resolved as 'max'.   Their allocation can be decreased to make up the gap.
1954                for i in max_count..max_count_phase2 {
1955                    if temp_definitions[def_count + i] >= 0 {
1956                        let def = &definitions[temp_definitions[def_count + i] as usize];
1957                        def.set_measure_size(1.0); // mark as 'not yet resolved'
1958                        star_count += 1;
1959                        run_phase_2_and_3 = true; // found a candidate, so re-run Phases 2 and 3
1960                    }
1961                }
1962            }
1963        }
1964
1965        // Phase 4.  Resolve the remaining defs proportionally.
1966        let mut star_count = 0;
1967        for i in 0..def_count {
1968            let def = &definitions[i];
1969
1970            if def.size_type() == LayoutTimeSizeType::STAR {
1971                if def.measure_size() < 0.0 {
1972                    // this def was resolved in phase 3 - fix up its measure size
1973                    def.set_measure_size(-def.measure_size());
1974                } else {
1975                    // this def needs resolution, add it to the list, sorted by *-weight
1976                    temp_definitions[star_count] = i as i32;
1977                    star_count += 1;
1978                    def.set_measure_size(Self::star_weight(def, scale));
1979                }
1980            }
1981        }
1982
1983        if star_count > 0 {
1984            // Sort by *-weight (stored in MeasureSize), ascending.
1985            temp_definitions[..star_count].sort_by(|x, y| {
1986                compare_to(
1987                    definitions[*x as usize].measure_size(),
1988                    definitions[*y as usize].measure_size(),
1989                )
1990            });
1991
1992            // compute the partial sums of *-weight, in increasing order of weight
1993            // for minimal loss of precision.
1994            let mut total_star_weight = 0.0;
1995            for i in 0..star_count {
1996                let def = &definitions[temp_definitions[i] as usize];
1997                total_star_weight += def.measure_size();
1998                def.set_size_cache(total_star_weight);
1999            }
2000
2001            // resolve the defs, in decreasing order of weight
2002            for i in (0..star_count).rev() {
2003                let def = &definitions[temp_definitions[i] as usize];
2004                let mut resolved_size = if def.measure_size() > 0.0 {
2005                    math_max(available_size - taken_size, 0.0) * (def.measure_size() / def.size_cache())
2006                } else {
2007                    0.0
2008                };
2009
2010                // min and max should have no effect by now, but just in case...
2011                resolved_size = math_min(resolved_size, def.user_max_size());
2012                resolved_size = math_max(def.min_size(), resolved_size);
2013
2014                def.set_measure_size(resolved_size);
2015                taken_size += resolved_size;
2016            }
2017        }
2018
2019        Self::return_temp_definitions(temp_definitions);
2020    }
2021
2022    /// Calculates the desired size of the given array of definitions.
2023    fn calculate_desired_size(definitions: &Definitions) -> f64 {
2024        let mut desired_size = 0.0;
2025
2026        for i in 0..definitions.count() {
2027            desired_size += definitions[i].min_size();
2028        }
2029
2030        desired_size
2031    }
2032
2033    /// Calculates and sets the final size of all definitions in the given
2034    /// array.
2035    ///
2036    /// `columns` is true if sizing column definitions and false for rows.
2037    fn set_final_size(&self, definitions: &Definitions, final_size: f64, columns: bool) {
2038        self.set_final_size_max_discrepancy(definitions, final_size, columns);
2039    }
2040
2041    // new implementation, as of 4.7.  This incorporates the same algorithm
2042    // as in ResolveStarMaxDiscrepancy.  It differs in the same way that SetFinalSizeLegacy
2043    // differs from ResolveStarLegacy, namely (a) leaves results in def.SizeCache
2044    // instead of def.MeasureSize, (b) implements LayoutRounding if requested,
2045    // (c) stores intermediate results differently.
2046    // The LayoutRounding logic is improved:
2047    // 1. Use pre-rounded values during proportional allocation.  This avoids the
2048    //      same kind of problems arising from interaction with min/max that
2049    //      motivated the new algorithm in the first place.
2050    // 2. Use correct "nudge" amount when distributing roundoff space.   This
2051    //      comes into play at high DPI - greater than 134.
2052    // 3. Applies rounding only to real pixel values (not to ratios)
2053    fn set_final_size_max_discrepancy(&self, definitions: &Definitions, final_size: f64, columns: bool) {
2054        let def_count = definitions.count();
2055        let mut definition_indices = self.take_definition_indices();
2056        let mut min_count;
2057        let mut max_count;
2058        let mut taken_size = 0.0;
2059        let mut total_star_weight;
2060        let mut star_count = 0; // number of unresolved *-definitions
2061        let mut scale = 1.0; // scale factor applied to each *-weight;  negative means "Infinity is present"
2062
2063        // Phase 1.  Determine the maximum *-weight and prepare to adjust *-weights
2064        let mut max_star = 0.0;
2065        for i in 0..def_count {
2066            let def = &definitions[i];
2067
2068            let user_size = def.user_size();
2069            if user_size.is_star() {
2070                star_count += 1;
2071                def.set_measure_size(1.0); // meaning "not yet resolved in phase 3"
2072                if user_size.value() > max_star {
2073                    max_star = user_size.value();
2074                }
2075            }
2076        }
2077
2078        if max_star == f64::INFINITY {
2079            // negative scale means one or more of the weights was Infinity
2080            scale = -1.0;
2081        } else if star_count > 0 {
2082            // if maxStar * starCount > Double.Max, summing all the weights could cause
2083            // floating-point overflow.  To avoid that, scale the weights by a factor to keep
2084            // the sum within limits.  Choose a power of 2, to preserve precision.
2085            let power = log2(f64::MAX / max_star / star_count as f64).floor();
2086            if power < 0.0 {
2087                scale = 2.0_f64.powf(power - 4.0); // -4 is just for paranoia
2088            }
2089        }
2090
2091        // normally Phases 2 and 3 execute only once.  But certain unusual combinations of weights
2092        // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3.
2093        // More explanation below...
2094        let mut run_phase_2_and_3 = true;
2095        while run_phase_2_and_3 {
2096            // Phase 2.   Compute total *-weight W and available space S.
2097            // For *-items that have Min or Max constraints, compute the ratios used to decide
2098            // whether proportional space is too big or too small and add the item to the
2099            // corresponding list.  (The "min" list is in the first half of definitionIndices,
2100            // the "max" list in the second half.  DefinitionIndices has capacity at least
2101            // 2*defCount, so there's room for both lists.)
2102            total_star_weight = 0.0;
2103            taken_size = 0.0;
2104            min_count = 0;
2105            max_count = 0;
2106
2107            for i in 0..def_count {
2108                let def = &definitions[i];
2109
2110                let def_user_size = def.user_size();
2111                if def_user_size.is_star() {
2112                    debug_assert!(!def.is_shared(), "*-defs cannot be shared");
2113
2114                    if def.measure_size() < 0.0 {
2115                        taken_size += -def.measure_size(); // already resolved
2116                    } else {
2117                        let star_weight = Self::star_weight(def, scale);
2118                        total_star_weight += star_weight;
2119
2120                        if def.min_size_for_arrange() > 0.0 {
2121                            // store ratio w/min in MeasureSize (for now)
2122                            definition_indices[min_count] = i as i32;
2123                            min_count += 1;
2124                            def.set_measure_size(star_weight / def.min_size_for_arrange());
2125                        }
2126
2127                        let effective_max_size = math_max(def.min_size_for_arrange(), def.user_max_size());
2128                        if effective_max_size != f64::INFINITY {
2129                            // store ratio w/max in SizeCache (for now)
2130                            definition_indices[def_count + max_count] = i as i32;
2131                            max_count += 1;
2132                            def.set_size_cache(star_weight / effective_max_size);
2133                        }
2134                    }
2135                } else {
2136                    let user_size = match def_user_size.grid_unit_type() {
2137                        GridUnitType::Pixel => def_user_size.value(),
2138                        GridUnitType::Auto => def.min_size_for_arrange(),
2139                        GridUnitType::Star => 0.0,
2140                    };
2141
2142                    let user_max_size = if def.is_shared() {
2143                        //  overriding userMaxSize effectively prevents squishy-ness.
2144                        //  this is a "solution" to avoid shared definitions from been sized to
2145                        //  different final size at arrange time, if / when different grids receive
2146                        //  different final sizes.
2147                        user_size
2148                    } else {
2149                        def.user_max_size()
2150                    };
2151
2152                    def.set_size_cache(math_max(def.min_size_for_arrange(), math_min(user_size, user_max_size)));
2153                    taken_size += def.size_cache();
2154                }
2155            }
2156
2157            // Phase 3.  Resolve *-items whose proportional sizes are too big or too small.
2158            let min_count_phase2 = min_count;
2159            let max_count_phase2 = max_count;
2160            let mut taken_star_weight = 0.0;
2161            let mut remaining_available_size = final_size - taken_size;
2162            let mut remaining_star_weight = total_star_weight - taken_star_weight;
2163
2164            definition_indices[..min_count].sort_by(|x, y| {
2165                compare_to(
2166                    definitions[*y as usize].measure_size(),
2167                    definitions[*x as usize].measure_size(),
2168                )
2169            });
2170            definition_indices[def_count..def_count + max_count].sort_by(|x, y| {
2171                compare_to(
2172                    definitions[*x as usize].size_cache(),
2173                    definitions[*y as usize].size_cache(),
2174                )
2175            });
2176
2177            while min_count + max_count > 0 && remaining_available_size > 0.0 {
2178                // the calculation
2179                //            remainingStarWeight = totalStarWeight - takenStarWeight
2180                // is subject to catastrophic cancellation if the two terms are nearly equal,
2181                // which leads to meaningless results.   Check for that, and recompute from
2182                // the remaining definitions.   [This leads to quadratic behavior in really
2183                // pathological cases - but they'd never arise in practice.]
2184                const STAR_FACTOR: f64 = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate
2185                if remaining_star_weight < total_star_weight * STAR_FACTOR {
2186                    taken_star_weight = 0.0;
2187                    total_star_weight = 0.0;
2188
2189                    for i in 0..def_count {
2190                        let def = &definitions[i];
2191                        if def.user_size().is_star() && def.measure_size() > 0.0 {
2192                            total_star_weight += Self::star_weight(def, scale);
2193                        }
2194                    }
2195
2196                    remaining_star_weight = total_star_weight - taken_star_weight;
2197                }
2198
2199                let min_ratio = if min_count > 0 {
2200                    definitions[definition_indices[min_count - 1] as usize].measure_size()
2201                } else {
2202                    f64::INFINITY
2203                };
2204                let max_ratio = if max_count > 0 {
2205                    definitions[definition_indices[def_count + max_count - 1] as usize].size_cache()
2206                } else {
2207                    -1.0
2208                };
2209
2210                // choose the def with larger ratio to the current proportion ("max discrepancy")
2211                let proportion = remaining_star_weight / remaining_available_size;
2212                let choose_min = Self::choose(min_ratio, max_ratio, proportion);
2213
2214                // if no def was chosen, advance to phase 4;  the current proportion doesn't
2215                // conflict with any min or max values.
2216                let Some(choose_min) = choose_min else {
2217                    break;
2218                };
2219
2220                // get the chosen definition and its resolved size
2221                let resolved_index;
2222                let resolved_def;
2223                let resolved_size;
2224                if choose_min {
2225                    resolved_index = definition_indices[min_count - 1] as usize;
2226                    resolved_def = &definitions[resolved_index];
2227                    resolved_size = resolved_def.min_size_for_arrange();
2228                    min_count -= 1;
2229                } else {
2230                    resolved_index = definition_indices[def_count + max_count - 1] as usize;
2231                    resolved_def = &definitions[resolved_index];
2232                    resolved_size = math_max(resolved_def.min_size_for_arrange(), resolved_def.user_max_size());
2233                    max_count -= 1;
2234                }
2235
2236                // resolve the chosen def, deduct its contributions from W and S.
2237                // Defs resolved in phase 3 are marked by storing the negative of their resolved
2238                // size in MeasureSize, to distinguish them from a pending def.
2239                taken_size += resolved_size;
2240                resolved_def.set_measure_size(-resolved_size);
2241                taken_star_weight += Self::star_weight(resolved_def, scale);
2242                star_count -= 1;
2243
2244                remaining_available_size = final_size - taken_size;
2245                remaining_star_weight = total_star_weight - taken_star_weight;
2246
2247                // advance to the next candidate defs, removing ones that have been resolved.
2248                // Both counts are advanced, as a def might appear in both lists.
2249                while min_count > 0 && definitions[definition_indices[min_count - 1] as usize].measure_size() < 0.0 {
2250                    min_count -= 1;
2251                    definition_indices[min_count] = -1;
2252                }
2253                while max_count > 0
2254                    && definitions[definition_indices[def_count + max_count - 1] as usize].measure_size() < 0.0
2255                {
2256                    max_count -= 1;
2257                    definition_indices[def_count + max_count] = -1;
2258                }
2259            }
2260
2261            // decide whether to run Phase2 and Phase3 again.  There are 3 cases:
2262            // 1. There is space available, and *-defs remaining.  This is the
2263            //      normal case - move on to Phase 4 to allocate the remaining
2264            //      space proportionally to the remaining *-defs.
2265            // 2. There is space available, but no *-defs.  This implies at least one
2266            //      def was resolved as 'max', taking less space than its proportion.
2267            //      If there are also 'min' defs, reconsider them - we can give
2268            //      them more space.   If not, all the *-defs are 'max', so there's
2269            //      no way to use all the available space.
2270            // 3. We allocated too much space.   This implies at least one def was
2271            //      resolved as 'min'.  If there are also 'max' defs, reconsider
2272            //      them, otherwise the over-allocation is an inevitable consequence
2273            //      of the given min constraints.
2274            // Note that if we return to Phase2, at least one *-def will have been
2275            // resolved.  This guarantees we don't run Phase2+3 infinitely often.
2276            run_phase_2_and_3 = false;
2277            if star_count == 0 && taken_size < final_size {
2278                // if no *-defs remain and we haven't allocated all the space, reconsider the defs
2279                // resolved as 'min'.   Their allocation can be increased to make up the gap.
2280                for i in min_count..min_count_phase2 {
2281                    if definition_indices[i] >= 0 {
2282                        let def = &definitions[definition_indices[i] as usize];
2283                        def.set_measure_size(1.0); // mark as 'not yet resolved'
2284                        star_count += 1;
2285                        run_phase_2_and_3 = true; // found a candidate, so re-run Phases 2 and 3
2286                    }
2287                }
2288            }
2289
2290            if taken_size > final_size {
2291                // if we've allocated too much space, reconsider the defs
2292                // resolved as 'max'.   Their allocation can be decreased to make up the gap.
2293                for i in max_count..max_count_phase2 {
2294                    if definition_indices[def_count + i] >= 0 {
2295                        let def = &definitions[definition_indices[def_count + i] as usize];
2296                        def.set_measure_size(1.0); // mark as 'not yet resolved'
2297                        star_count += 1;
2298                        run_phase_2_and_3 = true; // found a candidate, so re-run Phases 2 and 3
2299                    }
2300                }
2301            }
2302        }
2303
2304        // Phase 4.  Resolve the remaining defs proportionally.
2305        let mut star_count = 0;
2306        for i in 0..def_count {
2307            let def = &definitions[i];
2308
2309            if def.user_size().is_star() {
2310                if def.measure_size() < 0.0 {
2311                    // this def was resolved in phase 3 - fix up its size
2312                    def.set_size_cache(-def.measure_size());
2313                } else {
2314                    // this def needs resolution, add it to the list, sorted by *-weight
2315                    definition_indices[star_count] = i as i32;
2316                    star_count += 1;
2317                    def.set_measure_size(Self::star_weight(def, scale));
2318                }
2319            }
2320        }
2321
2322        if star_count > 0 {
2323            definition_indices[..star_count].sort_by(|x, y| {
2324                compare_to(
2325                    definitions[*x as usize].measure_size(),
2326                    definitions[*y as usize].measure_size(),
2327                )
2328            });
2329
2330            // compute the partial sums of *-weight, in increasing order of weight
2331            // for minimal loss of precision.
2332            let mut total_star_weight = 0.0;
2333            for i in 0..star_count {
2334                let def = &definitions[definition_indices[i] as usize];
2335                total_star_weight += def.measure_size();
2336                def.set_size_cache(total_star_weight);
2337            }
2338
2339            // resolve the defs, in decreasing order of weight.
2340            for i in (0..star_count).rev() {
2341                let def = &definitions[definition_indices[i] as usize];
2342                let mut resolved_size = if def.measure_size() > 0.0 {
2343                    math_max(final_size - taken_size, 0.0) * (def.measure_size() / def.size_cache())
2344                } else {
2345                    0.0
2346                };
2347
2348                // min and max should have no effect by now, but just in case...
2349                resolved_size = math_min(resolved_size, def.user_max_size());
2350                resolved_size = math_max(def.min_size_for_arrange(), resolved_size);
2351
2352                // Use the raw (unrounded) sizes to update takenSize, so that
2353                // proportions are computed in the same terms as in phase 3;
2354                // this avoids errors arising from min/max constraints.
2355                taken_size += resolved_size;
2356                def.set_size_cache(resolved_size);
2357            }
2358        }
2359
2360        // Phase 5.  Apply layout rounding.  We do this after fully allocating
2361        // unrounded sizes, to avoid breaking assumptions in the previous phases
2362        if self.use_layout_rounding() {
2363            let dpi = self.get_layout_root().map_or(1.0, |root| root.layout_scaling());
2364            let mut rounding_errors = self.take_rounding_errors();
2365            let mut rounded_taken_size = 0.0;
2366
2367            // round each of the allocated sizes, keeping track of the deltas
2368            for i in 0..def_count {
2369                let def = &definitions[i];
2370                let rounded_size = LayoutHelper::round_layout_value(def.size_cache(), dpi);
2371                rounding_errors[i] = rounded_size - def.size_cache();
2372                def.set_size_cache(rounded_size);
2373                rounded_taken_size += rounded_size;
2374            }
2375
2376            // The total allocation might differ from finalSize due to rounding
2377            // effects.  Tweak the allocations accordingly.
2378
2379            // Theoretical and historical note.  The problem at hand - allocating
2380            // space to columns (or rows) with *-weights, min and max constraints,
2381            // and layout rounding - has a long history.  Especially the special
2382            // case of 50 columns with min=1 and available space=435 - allocating
2383            // seats in the U.S. House of Representatives to the 50 states in
2384            // proportion to their population.  There are numerous algorithms
2385            // and papers dating back to the 1700's, including the book:
2386            // Balinski, M. and H. Young, Fair Representation, Yale University Press, New Haven, 1982.
2387            //
2388            // One surprising result of all this research is that *any* algorithm
2389            // will suffer from one or more undesirable features such as the
2390            // "population paradox" or the "Alabama paradox", where (to use our terminology)
2391            // increasing the available space by one pixel might actually decrease
2392            // the space allocated to a given column, or increasing the weight of
2393            // a column might decrease its allocation.   This is worth knowing
2394            // in case someone complains about this behavior;  it's not a bug so
2395            // much as something inherent to the problem.  Cite the book mentioned
2396            // above or one of the 100s of references, and resolve as WontFix.
2397            //
2398            // Fortunately, our scenarios tend to have a small number of columns (~10 or fewer)
2399            // each being allocated a large number of pixels (~50 or greater), and
2400            // people don't even notice the kind of 1-pixel anomalies that are
2401            // theoretically inevitable, or don't care if they do.  At least they shouldn't
2402            // care - no one should be using the results of the grid layout to make
2403            // quantitative decisions; its job is to produce a reasonable display, not
2404            // to allocate seats in Congress.
2405            //
2406            // Our algorithm is more susceptible to paradox than the one currently
2407            // used for Congressional allocation ("Huntington-Hill" algorithm), but
2408            // it is faster to run:  O(N log N) vs. O(S * N), where N=number of
2409            // definitions, S = number of available pixels.  And it produces
2410            // adequate results in practice, as mentioned above.
2411            //
2412            // To reiterate one point:  all this only applies when layout rounding
2413            // is in effect.  When fractional sizes are allowed, the algorithm
2414            // behaves as well as possible, subject to the min/max constraints
2415            // and precision of floating-point computation.  (However, the resulting
2416            // display is subject to anti-aliasing problems.   TANSTAAFL.)
2417
2418            if !MathUtilities::are_close(rounded_taken_size, final_size) {
2419                // Compute deltas
2420                for (i, index) in definition_indices[..def_count].iter_mut().enumerate() {
2421                    *index = i as i32;
2422                }
2423
2424                // Sort rounding errors
2425                definition_indices[..def_count]
2426                    .sort_by(|x, y| compare_to(rounding_errors[*x as usize], rounding_errors[*y as usize]));
2427                let mut adjusted_size = rounded_taken_size;
2428                let dpi_increment = 1.0 / dpi;
2429
2430                if rounded_taken_size > final_size {
2431                    let mut i = def_count;
2432                    while (adjusted_size > final_size && !MathUtilities::are_close(adjusted_size, final_size)) && i > 0
2433                    {
2434                        i -= 1;
2435                        let definition = &definitions[definition_indices[i] as usize];
2436                        let mut final_ = definition.size_cache() - dpi_increment;
2437                        final_ = math_max(final_, definition.min_size_for_arrange());
2438                        if final_ < definition.size_cache() {
2439                            adjusted_size -= dpi_increment;
2440                        }
2441                        definition.set_size_cache(final_);
2442                    }
2443                } else if rounded_taken_size < final_size {
2444                    let mut i = 0;
2445                    while (adjusted_size < final_size && !MathUtilities::are_close(adjusted_size, final_size))
2446                        && i < def_count
2447                    {
2448                        let definition = &definitions[definition_indices[i] as usize];
2449                        let mut final_ = definition.size_cache() + dpi_increment;
2450                        final_ = math_max(final_, definition.min_size_for_arrange());
2451                        if final_ > definition.size_cache() {
2452                            adjusted_size += dpi_increment;
2453                        }
2454                        definition.set_size_cache(final_);
2455                        i += 1;
2456                    }
2457                }
2458            }
2459
2460            *self.rounding_errors.borrow_mut() = Some(rounding_errors);
2461        }
2462
2463        let spacing = if columns {
2464            self.column_spacing()
2465        } else {
2466            self.row_spacing()
2467        };
2468        // Phase 6.  Compute final offsets
2469        definitions[0].set_final_offset(0.0);
2470        for i in 0..def_count {
2471            definitions[(i + 1) % def_count]
2472                .set_final_offset(definitions[i].final_offset() + definitions[i].size_cache() + spacing);
2473        }
2474
2475        *self.definition_indices.borrow_mut() = Some(definition_indices);
2476    }
2477
2478    /// Chooses the ratio with the maximum discrepancy from the current
2479    /// proportion. Returns:
2480    ///
2481    /// * `Some(true)` if the proportion fails a min constraint but not a
2482    ///   max, or if the min constraint has the higher discrepancy;
2483    /// * `Some(false)` if the proportion fails a max constraint but not a
2484    ///   min, or if the max constraint has the higher discrepancy;
2485    /// * `None` if the proportion doesn't fail a min or max constraint.
2486    ///
2487    /// The discrepancy is the ratio of the proportion to the max- or
2488    /// min-ratio. When both ratios hit the constraint,
2489    /// `min_ratio < proportion < max_ratio`, and the min ratio has the higher
2490    /// discrepancy if `(proportion / min_ratio) > (max_ratio / proportion)`.
2491    fn choose(min_ratio: f64, max_ratio: f64, proportion: f64) -> Option<bool> {
2492        if min_ratio < proportion {
2493            if max_ratio > proportion {
2494                // compare proportion/minRatio : maxRatio/proportion, but
2495                // do it carefully to avoid floating-point overflow or underflow
2496                // and divide-by-0.
2497                let min_power = log2(min_ratio).floor();
2498                let max_power = log2(max_ratio).floor();
2499                let f = 2.0_f64.powf(((min_power + max_power) / 2.0).floor());
2500                Some((proportion / f) * (proportion / f) > (min_ratio / f) * (max_ratio / f))
2501            } else {
2502                Some(true)
2503            }
2504        } else if max_ratio > proportion {
2505            Some(false)
2506        } else {
2507            None
2508        }
2509    }
2510
2511    /// Calculates the final (aka arrange) size of the given range.
2512    fn get_final_size_for_range(definitions: &Definitions, start: usize, count: usize, spacing: f64) -> f64 {
2513        let mut size = -spacing;
2514
2515        for i in (start..start + count).rev() {
2516            size += spacing + definitions[i].size_cache();
2517        }
2518
2519        size
2520    }
2521
2522    /// Sets or unsets one or multiple flags on the object.
2523    #[inline]
2524    fn set_flags(&self, value: bool, flags: u32) {
2525        self.flags.set(if value {
2526            self.flags.get() | flags
2527        } else {
2528            self.flags.get() & !flags
2529        });
2530    }
2531
2532    /// Returns true if all the flags in the given bitmask are set on the
2533    /// object.
2534    #[inline]
2535    fn check_flags(&self, flags: u32) -> bool {
2536        (self.flags.get() & flags) == flags
2537    }
2538
2539    /// Synchronizes the grid lines renderer with the `ShowGridLines`
2540    /// property by adding / removing the renderer visual. Returns the
2541    /// renderer visual, if the lines are shown.
2542    fn ensure_grid_lines_renderer(&self) -> Option<Ref<GridLinesRenderer>> {
2543        let show_grid_lines = self.show_grid_lines();
2544        let current = self.grid_lines_renderer.borrow().clone();
2545
2546        if show_grid_lines && current.is_none() {
2547            let renderer = GridLinesRenderer::new();
2548            *self.grid_lines_renderer.borrow_mut() = Some(renderer.clone());
2549            self.visual_children().add(renderer.upcast());
2550        }
2551
2552        if !show_grid_lines {
2553            if let Some(renderer) = current {
2554                self.visual_children().remove(&renderer.upcast());
2555                *self.grid_lines_renderer.borrow_mut() = None;
2556            }
2557        }
2558
2559        self.grid_lines_renderer.borrow().clone()
2560    }
2561
2562    fn on_show_grid_lines_property_changed(grid: &Grid, e: &FerroPropertyChangedEventArgs<'_>) {
2563        // A trivial grid is 1 by 1: there are no grid lines anyway.
2564        if grid.ext_data.get().is_some() && grid.listen_to_notifications() {
2565            grid.invalidate_arrange();
2566        }
2567
2568        grid.set_flags(e.get_new_value::<bool>(), FLAG_SHOW_GRID_LINES_PROPERTY_VALUE);
2569    }
2570
2571    fn on_spacing_property_changed(grid: &Grid, _e: &FerroPropertyChangedEventArgs<'_>) {
2572        if grid.ext_data.get().is_some() && grid.listen_to_notifications() {
2573            grid.set_cells_structure_dirty(true);
2574        }
2575    }
2576
2577    fn on_cell_attached_property_changed(child: &Control, _e: &FerroPropertyChangedEventArgs<'_>) {
2578        let parent = child.visual_parent();
2579        if let Some(grid) = parent.as_ref().and_then(|p| p.downcast_ref::<Grid>()) {
2580            if grid.ext_data.get().is_some() && grid.listen_to_notifications() {
2581                grid.set_cells_structure_dirty(true);
2582            }
2583        }
2584    }
2585
2586    /// The array of column definitions used during the calculations.
2587    fn definitions_u(&self) -> Definitions {
2588        self.ext()
2589            .definitions_u
2590            .borrow()
2591            .as_ref()
2592            .expect("the column definitions structure has not been validated")
2593            .definitions()
2594    }
2595
2596    /// The array of row definitions used during the calculations.
2597    fn definitions_v(&self) -> Definitions {
2598        self.ext()
2599            .definitions_v
2600            .borrow()
2601            .as_ref()
2602            .expect("the row definitions structure has not been validated")
2603            .definitions()
2604    }
2605
2606    /// Takes the layout time array of definition indices out of its thread
2607    /// slot, with at least `required_length` entries reset to -1.
2608    fn take_temp_definitions(required_length: usize) -> Vec<i32> {
2609        let mut temp_definitions = TEMP_DEFINITIONS.take();
2610        temp_definitions.clear();
2611        temp_definitions.resize(required_length, -1);
2612        temp_definitions
2613    }
2614
2615    fn return_temp_definitions(temp_definitions: Vec<i32>) {
2616        TEMP_DEFINITIONS.set(temp_definitions);
2617    }
2618
2619    /// Takes the array of definition indices out of the grid; it is put back
2620    /// when the final sizes have been set.
2621    fn take_definition_indices(&self) -> Vec<i32> {
2622        let required_length = self.definitions_u().count().max(self.definitions_v().count()).max(1) * 2;
2623
2624        match self.definition_indices.borrow_mut().take() {
2625            Some(definition_indices) if definition_indices.len() >= required_length => definition_indices,
2626            _ => vec![0; required_length],
2627        }
2628    }
2629
2630    /// Takes the array of rounding errors out of the grid; it is put back
2631    /// when the layout rounding has been applied.
2632    fn take_rounding_errors(&self) -> Vec<f64> {
2633        let required_length = self.definitions_u().count().max(self.definitions_v().count());
2634
2635        match self.rounding_errors.borrow_mut().take() {
2636            Some(rounding_errors) if rounding_errors.len() >= required_length => rounding_errors,
2637            None if required_length == 0 => vec![0.0; 1],
2638            _ => vec![0.0; required_length],
2639        }
2640    }
2641
2642    /// The number of cells.
2643    #[inline]
2644    fn private_cells_len(&self) -> usize {
2645        self.ext().cell_caches_collection.borrow().len()
2646    }
2647
2648    /// The cached values of a cell.
2649    #[inline]
2650    fn private_cell(&self, index: usize) -> CellCache {
2651        self.ext().cell_caches_collection.borrow()[index]
2652    }
2653
2654    #[inline]
2655    fn cells_structure_dirty(&self) -> bool {
2656        !self.check_flags(FLAG_VALID_CELLS_STRUCTURE)
2657    }
2658
2659    #[inline]
2660    fn set_cells_structure_dirty(&self, value: bool) {
2661        self.set_flags(!value, FLAG_VALID_CELLS_STRUCTURE)
2662    }
2663
2664    #[inline]
2665    fn listen_to_notifications(&self) -> bool {
2666        self.check_flags(FLAG_LISTEN_TO_NOTIFICATIONS)
2667    }
2668
2669    #[inline]
2670    fn set_listen_to_notifications(&self, value: bool) {
2671        self.set_flags(value, FLAG_LISTEN_TO_NOTIFICATIONS)
2672    }
2673
2674    #[inline]
2675    fn size_to_content_u(&self) -> bool {
2676        self.check_flags(FLAG_SIZE_TO_CONTENT_U)
2677    }
2678
2679    #[inline]
2680    fn size_to_content_v(&self) -> bool {
2681        self.check_flags(FLAG_SIZE_TO_CONTENT_V)
2682    }
2683
2684    #[inline]
2685    fn has_star_cells_u(&self) -> bool {
2686        self.check_flags(FLAG_HAS_STAR_CELLS_U)
2687    }
2688
2689    #[inline]
2690    fn has_star_cells_v(&self) -> bool {
2691        self.check_flags(FLAG_HAS_STAR_CELLS_V)
2692    }
2693
2694    #[inline]
2695    fn has_group3_cells_in_auto_rows(&self) -> bool {
2696        self.check_flags(FLAG_HAS_GROUP3_CELLS_IN_AUTO_ROWS)
2697    }
2698
2699    /// Returns the *-weight, adjusted for the scale computed during Phase 1.
2700    fn star_weight(def: &DefinitionBase, scale: f64) -> f64 {
2701        if scale < 0.0 {
2702            // if one of the *-weights is Infinity, adjust the weights by mapping
2703            // Infinity to 1.0 and everything else to 0.0:  the infinite items share the
2704            // available space equally, everyone else gets nothing.
2705            if def.user_size().value() == f64::INFINITY {
2706                1.0
2707            } else {
2708                0.0
2709            }
2710        } else {
2711            def.user_size().value() * scale
2712        }
2713    }
2714
2715    /// Orders definitions for the distribution of a span that fits into the
2716    /// preferred size of its range.
2717    fn span_preferred_distribution_order(definition_x: &DefinitionBase, definition_y: &DefinitionBase) -> Ordering {
2718        if definition_x.user_size().is_auto() {
2719            if definition_y.user_size().is_auto() {
2720                compare_to(definition_x.min_size(), definition_y.min_size())
2721            } else {
2722                Ordering::Less
2723            }
2724        } else if definition_y.user_size().is_auto() {
2725            Ordering::Greater
2726        } else {
2727            compare_to(definition_x.preferred_size(), definition_y.preferred_size())
2728        }
2729    }
2730
2731    /// Orders definitions for the distribution of a span that fits into the
2732    /// maximum size of its range.
2733    fn span_max_distribution_order(definition_x: &DefinitionBase, definition_y: &DefinitionBase) -> Ordering {
2734        if definition_x.user_size().is_auto() {
2735            if definition_y.user_size().is_auto() {
2736                compare_to(definition_x.size_cache(), definition_y.size_cache())
2737            } else {
2738                Ordering::Greater
2739            }
2740        } else if definition_y.user_size().is_auto() {
2741            Ordering::Less
2742        } else {
2743            compare_to(definition_x.size_cache(), definition_y.size_cache())
2744        }
2745    }
2746}
2747
2748const GRID_LINES_DASH_LENGTH: f64 = 4.0;
2749const GRID_LINES_PEN_WIDTH: f64 = 1.0;
2750
2751ferroui_base::ferro_thread_local! {
2752    /// The size of the last arranged grid that shows its lines; shared by
2753    /// all the renderers, as in the reference.
2754    static GRID_LINES_LAST_ARRANGE_SIZE: Cell<Size> = const { Cell::new(Size::new(0.0, 0.0)) };
2755    /// The first pen to draw a dash.
2756    static GRID_LINES_ODD_DASH_PEN: Rc<dyn ferroui_base::media::IPen> = grid_lines_pen(ferroui_base::media::Brushes::blue(), 0.0);
2757    /// The second pen to draw a dash.
2758    static GRID_LINES_EVEN_DASH_PEN: Rc<dyn ferroui_base::media::IPen> =
2759        grid_lines_pen(ferroui_base::media::Brushes::yellow(), GRID_LINES_DASH_LENGTH);
2760}
2761
2762fn grid_lines_pen(brush: Rc<dyn ferroui_base::media::IBrush>, dash_offset: f64) -> Rc<dyn ferroui_base::media::IPen> {
2763    use ferroui_base::media::{DashStyle, Pen, PenLineCap, PenLineJoin};
2764
2765    let dash_style = DashStyle::with_dashes(Some(&[GRID_LINES_DASH_LENGTH, GRID_LINES_DASH_LENGTH]), dash_offset);
2766    Pen::with_all(Some(brush), GRID_LINES_PEN_WIDTH, Some(dash_style.into()), PenLineCap::Flat, PenLineJoin::Miter, 10.0)
2767        .into()
2768}
2769
2770/// Helper to render grid lines.
2771#[repr(C)]
2772pub(crate) struct GridLinesRenderer {
2773    base: Control,
2774}
2775
2776ferro_class!(GridLinesRenderer: Control);
2777ferro_impl_classes!(
2778    GridLinesRenderer: FerroObjectImpl,
2779    StyledElementImpl,
2780    LayoutableImpl,
2781    InteractiveImpl,
2782    InputElementImpl,
2783    ControlImpl
2784);
2785
2786impl VisualImpl for GridLinesRenderer {
2787    fn render(this: &Self, drawing_context: &mut ferroui_base::media::DrawingContext) {
2788        let Some(grid) = this.get_visual_parent_of_type::<Grid>() else { return };
2789
2790        if !grid.show_grid_lines() {
2791            return;
2792        }
2793
2794        let last_arrange_size = GRID_LINES_LAST_ARRANGE_SIZE.get();
2795
2796        let column_definitions = grid.column_definitions();
2797        let column_spacing = grid.column_spacing();
2798        for i in 1..column_definitions.count() {
2799            let final_offset = column_definitions.get(i).final_offset();
2800            Self::draw_grid_line(drawing_context, final_offset, 0.0, final_offset, last_arrange_size.height);
2801
2802            if column_spacing != 0.0 {
2803                Self::draw_grid_line(
2804                    drawing_context,
2805                    final_offset - column_spacing,
2806                    0.0,
2807                    final_offset - column_spacing,
2808                    last_arrange_size.height,
2809                );
2810            }
2811        }
2812
2813        let row_definitions = grid.row_definitions();
2814        let row_spacing = grid.row_spacing();
2815        for i in 1..row_definitions.count() {
2816            let final_offset = row_definitions.get(i).final_offset();
2817            Self::draw_grid_line(drawing_context, 0.0, final_offset, last_arrange_size.width, final_offset);
2818
2819            if row_spacing != 0.0 {
2820                Self::draw_grid_line(
2821                    drawing_context,
2822                    0.0,
2823                    final_offset - row_spacing,
2824                    last_arrange_size.width,
2825                    final_offset - row_spacing,
2826                );
2827            }
2828        }
2829    }
2830}
2831
2832impl GridLinesRenderer {
2833    pub(crate) fn new() -> Ref<Self> {
2834        instantiate(Self { base: Control::construct() })
2835    }
2836
2837    /// Draws a single grid line.
2838    fn draw_grid_line(
2839        drawing_context: &mut ferroui_base::media::DrawingContext,
2840        start_x: f64,
2841        start_y: f64,
2842        end_x: f64,
2843        end_y: f64,
2844    ) {
2845        let start = ferroui_base::Point::new(start_x, start_y);
2846        let end = ferroui_base::Point::new(end_x, end_y);
2847        GRID_LINES_ODD_DASH_PEN.with(|pen| drawing_context.draw_line(pen, start, end));
2848        GRID_LINES_EVEN_DASH_PEN.with(|pen| drawing_context.draw_line(pen, start, end));
2849    }
2850
2851    pub(crate) fn update_render_bounds(&self, arrange_size: Size) {
2852        GRID_LINES_LAST_ARRANGE_SIZE.set(arrange_size);
2853        self.invalidate_visual();
2854    }
2855}