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}