1use crate::items::{
9 CrossAxisAlignment, DialogButtonRole, FlexboxLayoutDirection, FlexboxLayoutWrap,
10 LayoutAlignment,
11};
12use crate::{Coord, SharedVector, slice::Slice};
13use alloc::format;
14use alloc::string::String;
15use alloc::vec::Vec;
16use num_traits::Float;
17
18pub use crate::items::Orientation;
19
20#[repr(C)]
23#[derive(Clone, Copy, Debug, PartialEq)]
24pub struct LayoutInfo {
25 pub max: Coord,
27 pub max_percent: Coord,
29 pub min: Coord,
31 pub min_percent: Coord,
33 pub preferred: Coord,
35 pub stretch: f32,
37}
38
39impl Default for LayoutInfo {
40 fn default() -> Self {
41 LayoutInfo {
42 min: 0 as _,
43 max: Coord::MAX,
44 min_percent: 0 as _,
45 max_percent: 100 as _,
46 preferred: 0 as _,
47 stretch: 0 as _,
48 }
49 }
50}
51
52impl LayoutInfo {
53 #[must_use]
57 pub fn merge(&self, other: &LayoutInfo) -> Self {
58 Self {
59 min: self.min.max(other.min),
60 max: self.max.min(other.max),
61 min_percent: self.min_percent.max(other.min_percent),
62 max_percent: self.max_percent.min(other.max_percent),
63 preferred: self.preferred.max(other.preferred),
64 stretch: self.stretch.min(other.stretch),
65 }
66 }
67
68 #[must_use]
70 pub fn preferred_bounded(&self) -> Coord {
71 self.preferred.min(self.max).max(self.min)
72 }
73}
74
75impl core::ops::Add for LayoutInfo {
76 type Output = Self;
77
78 fn add(self, rhs: Self) -> Self::Output {
79 self.merge(&rhs)
80 }
81}
82
83pub fn min_max_size_for_layout_constraints(
85 constraints_horizontal: LayoutInfo,
86 constraints_vertical: LayoutInfo,
87) -> (Option<crate::api::LogicalSize>, Option<crate::api::LogicalSize>) {
88 let min_width = constraints_horizontal.min.min(constraints_horizontal.max) as f32;
89 let min_height = constraints_vertical.min.min(constraints_vertical.max) as f32;
90 let max_width = constraints_horizontal.max.max(constraints_horizontal.min) as f32;
91 let max_height = constraints_vertical.max.max(constraints_vertical.min) as f32;
92
93 let min_size = if min_width > 0. || min_height > 0. || cfg!(target_arch = "wasm32") {
97 Some(crate::api::LogicalSize::new(min_width, min_height))
98 } else {
99 None
100 };
101
102 let max_size = if (max_width > 0.
103 && max_height > 0.
104 && (max_width < i32::MAX as f32 || max_height < i32::MAX as f32))
105 || cfg!(target_arch = "wasm32")
106 {
107 let window_size_max = 16_777_215.;
109 Some(crate::api::LogicalSize::new(
110 max_width.min(window_size_max),
111 max_height.min(window_size_max),
112 ))
113 } else {
114 None
115 };
116
117 (min_size, max_size)
118}
119
120trait Saturating {
123 fn add(_: Self, _: Self) -> Self;
124}
125impl Saturating for i32 {
126 #[inline]
127 fn add(a: Self, b: Self) -> Self {
128 a.saturating_add(b)
129 }
130}
131impl Saturating for f32 {
132 #[inline]
133 fn add(a: Self, b: Self) -> Self {
134 a + b
135 }
136}
137
138mod grid_internal {
139 use super::*;
140
141 fn order_coord<T: PartialOrd>(a: &T, b: &T) -> core::cmp::Ordering {
142 a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)
143 }
144
145 #[derive(Debug, Clone)]
146 pub struct LayoutData {
147 pub min: Coord,
149 pub max: Coord,
150 pub pref: Coord,
151 pub stretch: f32,
152
153 pub pos: Coord,
155 pub size: Coord,
156 }
157
158 impl Default for LayoutData {
159 fn default() -> Self {
160 LayoutData {
161 min: 0 as _,
162 max: Coord::MAX,
163 pref: 0 as _,
164 stretch: f32::MAX,
165 pos: 0 as _,
166 size: 0 as _,
167 }
168 }
169 }
170
171 trait Adjust {
172 fn can_grow(_: &LayoutData) -> Coord;
173 fn to_distribute(expected_size: Coord, current_size: Coord) -> Coord;
174 fn distribute(_: &mut LayoutData, val: Coord);
175 }
176
177 struct Grow;
178 impl Adjust for Grow {
179 fn can_grow(it: &LayoutData) -> Coord {
180 it.max - it.size
181 }
182
183 fn to_distribute(expected_size: Coord, current_size: Coord) -> Coord {
184 expected_size - current_size
185 }
186
187 fn distribute(it: &mut LayoutData, val: Coord) {
188 it.size += val;
189 }
190 }
191
192 struct Shrink;
193 impl Adjust for Shrink {
194 fn can_grow(it: &LayoutData) -> Coord {
195 it.size - it.min
196 }
197
198 fn to_distribute(expected_size: Coord, current_size: Coord) -> Coord {
199 current_size - expected_size
200 }
201
202 fn distribute(it: &mut LayoutData, val: Coord) {
203 it.size -= val;
204 }
205 }
206
207 #[allow(clippy::unnecessary_cast)] fn adjust_items<A: Adjust>(data: &mut [LayoutData], size_without_spacing: Coord) -> Option<()> {
209 loop {
210 let size_cannot_grow: Coord = data
211 .iter()
212 .filter(|it| A::can_grow(it) <= 0 as _)
213 .map(|it| it.size)
214 .fold(0 as Coord, Saturating::add);
215
216 let total_stretch: f32 =
217 data.iter().filter(|it| A::can_grow(it) > 0 as _).map(|it| it.stretch).sum();
218
219 let actual_stretch = |s: f32| if total_stretch <= 0. { 1. } else { s };
220
221 let max_grow = data
222 .iter()
223 .filter(|it| A::can_grow(it) > 0 as _)
224 .map(|it| A::can_grow(it) as f32 / actual_stretch(it.stretch))
225 .min_by(order_coord)?;
226
227 let current_size: Coord = data
228 .iter()
229 .filter(|it| A::can_grow(it) > 0 as _)
230 .map(|it| it.size)
231 .fold(0 as _, Saturating::add);
232
233 let to_distribute =
235 A::to_distribute(size_without_spacing, size_cannot_grow + current_size) as f32;
236 if to_distribute <= 0. || max_grow <= 0. {
237 return Some(());
238 }
239
240 let grow = if total_stretch <= 0. {
241 to_distribute
242 / (data.iter().filter(|it| A::can_grow(it) > 0 as _).count() as Coord) as f32
243 } else {
244 to_distribute / total_stretch
245 }
246 .min(max_grow);
247
248 let mut distributed = 0 as Coord;
249 for it in data.iter_mut().filter(|it| A::can_grow(it) > 0 as Coord) {
250 let val = (grow * actual_stretch(it.stretch)) as Coord;
251 A::distribute(it, val);
252 distributed += val;
253 }
254
255 if distributed <= 0 as Coord {
256 if let Some(it) = data
259 .iter_mut()
260 .filter(|it| A::can_grow(it) > 0 as _)
261 .max_by(|a, b| actual_stretch(a.stretch).total_cmp(&b.stretch))
262 {
263 A::distribute(it, to_distribute as Coord);
264 }
265 return Some(());
266 }
267 }
268 }
269
270 pub fn layout_items(data: &mut [LayoutData], start_pos: Coord, size: Coord, spacing: Coord) {
271 let size_without_spacing = size - spacing * (data.len() - 1) as Coord;
272
273 let mut pref = 0 as Coord;
274 for it in data.iter_mut() {
275 it.size = it.pref;
276 pref += it.pref;
277 }
278 if size_without_spacing >= pref {
279 adjust_items::<Grow>(data, size_without_spacing);
280 } else if size_without_spacing < pref {
281 adjust_items::<Shrink>(data, size_without_spacing);
282 }
283
284 let mut pos = start_pos;
285 for it in data.iter_mut() {
286 it.pos = pos;
287 pos = Saturating::add(pos, Saturating::add(it.size, spacing));
288 }
289 }
290
291 #[test]
292 #[allow(clippy::float_cmp)] fn test_layout_items() {
294 let my_items = &mut [
295 LayoutData { min: 100., max: 200., pref: 100., stretch: 1., ..Default::default() },
296 LayoutData { min: 50., max: 300., pref: 100., stretch: 1., ..Default::default() },
297 LayoutData { min: 50., max: 150., pref: 100., stretch: 1., ..Default::default() },
298 ];
299
300 layout_items(my_items, 100., 650., 0.);
301 assert_eq!(my_items[0].size, 200.);
302 assert_eq!(my_items[1].size, 300.);
303 assert_eq!(my_items[2].size, 150.);
304
305 layout_items(my_items, 100., 200., 0.);
306 assert_eq!(my_items[0].size, 100.);
307 assert_eq!(my_items[1].size, 50.);
308 assert_eq!(my_items[2].size, 50.);
309
310 layout_items(my_items, 100., 300., 0.);
311 assert_eq!(my_items[0].size, 100.);
312 assert_eq!(my_items[1].size, 100.);
313 assert_eq!(my_items[2].size, 100.);
314 }
315
316 pub fn to_layout_data(
319 organized_data: &GridLayoutOrganizedData,
320 constraints: Slice<LayoutItemInfo>,
321 orientation: Orientation,
322 repeater_indices: Slice<u32>,
323 repeater_steps: Slice<u32>,
324 spacing: Coord,
325 size: Option<Coord>,
326 ) -> Vec<LayoutData> {
327 assert!(organized_data.len().is_multiple_of(4));
328 let num = organized_data.max_value(
329 constraints.len(),
330 orientation,
331 &repeater_indices,
332 &repeater_steps,
333 );
334 if num < 1 {
335 return Default::default();
336 }
337 let marker_for_empty = -1.;
338 let mut layout_data = alloc::vec![grid_internal::LayoutData { max: 0 as Coord, stretch: marker_for_empty, ..Default::default() }; num];
339 let mut has_spans = false;
340 for (idx, cell_data) in constraints.iter().enumerate() {
341 let constraint = &cell_data.constraint;
342 let mut max = constraint.max;
343 if let Some(size) = size {
344 max = max.min(size * constraint.max_percent / 100 as Coord);
345 }
346 let (col_or_row, span) = organized_data.col_or_row_and_span(
347 idx,
348 orientation,
349 &repeater_indices,
350 &repeater_steps,
351 );
352 for c in 0..(span as usize) {
353 let cdata = &mut layout_data[col_or_row as usize + c];
354 if cdata.stretch == marker_for_empty {
357 cdata.max = Coord::MAX;
358 cdata.stretch = 1.;
359 }
360 cdata.max = cdata.max.min(max);
361 }
362 if span == 1 {
363 let mut min = constraint.min;
364 if let Some(size) = size {
365 min = min.max(size * constraint.min_percent / 100 as Coord);
366 }
367 let pref = constraint.preferred.min(max).max(min);
368 let cdata = &mut layout_data[col_or_row as usize];
369 cdata.min = cdata.min.max(min);
370 cdata.pref = cdata.pref.max(pref);
371 cdata.stretch = cdata.stretch.min(constraint.stretch);
372 } else {
373 has_spans = true;
374 }
375 }
376 if has_spans {
377 for (idx, cell_data) in constraints.iter().enumerate() {
378 let constraint = &cell_data.constraint;
379 let (col_or_row, span) = organized_data.col_or_row_and_span(
380 idx,
381 orientation,
382 &repeater_indices,
383 &repeater_steps,
384 );
385 if span > 1 {
386 let span_data = &mut layout_data
387 [(col_or_row as usize)..(col_or_row as usize + span as usize)];
388
389 let mut min = constraint.min;
391 if let Some(size) = size {
392 min = min.max(size * constraint.min_percent / 100 as Coord);
393 }
394 grid_internal::layout_items(span_data, 0 as _, min, spacing);
395 for cdata in span_data.iter_mut() {
396 if cdata.min < cdata.size {
397 cdata.min = cdata.size;
398 }
399 }
400
401 let mut max = constraint.max;
403 if let Some(size) = size {
404 max = max.min(size * constraint.max_percent / 100 as Coord);
405 }
406 grid_internal::layout_items(span_data, 0 as _, max, spacing);
407 for cdata in span_data.iter_mut() {
408 if cdata.max > cdata.size {
409 cdata.max = cdata.size;
410 }
411 }
412
413 grid_internal::layout_items(span_data, 0 as _, constraint.preferred, spacing);
415 for cdata in span_data.iter_mut() {
416 cdata.pref = cdata.pref.max(cdata.size).min(cdata.max).max(cdata.min);
417 }
418
419 let total_stretch: f32 = span_data.iter().map(|c| c.stretch).sum();
421 if total_stretch > constraint.stretch {
422 for cdata in span_data.iter_mut() {
423 cdata.stretch *= constraint.stretch / total_stretch;
424 }
425 }
426 }
427 }
428 }
429 for cdata in layout_data.iter_mut() {
430 if cdata.stretch == marker_for_empty {
431 cdata.stretch = 0.;
432 }
433 if cdata.max < cdata.min {
440 cdata.max = cdata.min;
441 cdata.pref = cdata.min;
442 }
443 }
444 layout_data
445 }
446}
447
448#[repr(C)]
449pub struct Constraint {
450 pub min: Coord,
451 pub max: Coord,
452}
453
454impl Default for Constraint {
455 fn default() -> Self {
456 Constraint { min: 0 as Coord, max: Coord::MAX }
457 }
458}
459
460#[repr(C)]
461#[derive(Copy, Clone, Debug, Default)]
462pub struct Padding {
463 pub begin: Coord,
464 pub end: Coord,
465}
466
467#[repr(C)]
468#[derive(Debug)]
469pub struct GridLayoutData {
471 pub size: Coord,
472 pub spacing: Coord,
473 pub padding: Padding,
474 pub organized_data: GridLayoutOrganizedData,
475}
476
477#[repr(C)]
480#[derive(Debug, Clone)]
481pub struct GridLayoutInputData {
482 pub new_row: bool,
484 pub col: f32,
488 pub row: f32,
489 pub colspan: f32,
492 pub rowspan: f32,
493}
494
495impl Default for GridLayoutInputData {
496 fn default() -> Self {
497 Self {
498 new_row: false,
499 col: i_slint_common::ROW_COL_AUTO,
500 row: i_slint_common::ROW_COL_AUTO,
501 colspan: 1.0,
502 rowspan: 1.0,
503 }
504 }
505}
506
507pub type GridLayoutOrganizedData = SharedVector<u16>;
510
511impl GridLayoutOrganizedData {
512 fn push_cell(&mut self, col: u16, colspan: u16, row: u16, rowspan: u16) {
513 self.push(col);
514 self.push(colspan);
515 self.push(row);
516 self.push(rowspan);
517 }
518
519 fn col_or_row_and_span(
520 &self,
521 cell_number: usize,
522 orientation: Orientation,
523 repeater_indices: &Slice<u32>,
524 repeater_steps: &Slice<u32>,
525 ) -> (u16, u16) {
526 let mut final_idx = 0;
535 let mut cell_nr_adj = 0i32; let cell_number = cell_number as i32;
537 for rep_idx in 0..(repeater_indices.len() / 2) {
539 let ri_start_cell = repeater_indices[rep_idx * 2] as i32;
540 if cell_number < ri_start_cell {
541 break;
542 }
543 let ri_cell_count = repeater_indices[rep_idx * 2 + 1] as i32;
544 let step = repeater_steps.get(rep_idx).copied().unwrap_or(1) as i32;
545 let cells_in_repeater = ri_cell_count * step;
546 if cells_in_repeater > 0
547 && cell_number >= ri_start_cell
548 && cell_number < ri_start_cell + cells_in_repeater
549 {
550 let cell_in_rep = cell_number - ri_start_cell;
551 let row_in_rep = cell_in_rep / step;
552 let col_in_rep = cell_in_rep % step;
553 let jump_pos = (ri_start_cell - cell_nr_adj) as usize * 4;
554 let data_base = self[jump_pos] as usize;
555 let stride = self[jump_pos + 1] as usize;
556 final_idx = data_base + row_in_rep as usize * stride + col_in_rep as usize * 4;
557 break;
558 }
559 cell_nr_adj += cells_in_repeater - 1;
562 }
563 if final_idx == 0 {
564 final_idx = ((cell_number - cell_nr_adj) * 4) as usize;
565 }
566 let offset = if orientation == Orientation::Horizontal { 0 } else { 2 };
567 (self[final_idx + offset], self[final_idx + offset + 1])
568 }
569
570 fn max_value(
571 &self,
572 num_cells: usize,
573 orientation: Orientation,
574 repeater_indices: &Slice<u32>,
575 repeater_steps: &Slice<u32>,
576 ) -> usize {
577 let mut max = 0;
578 for idx in 0..num_cells {
581 let (col_or_row, span) =
582 self.col_or_row_and_span(idx, orientation, repeater_indices, repeater_steps);
583 max = max.max(col_or_row as usize + span.max(1) as usize);
586 }
587 max
588 }
589}
590
591struct OrganizedDataGenerator<'a> {
598 repeater_indices: &'a [u32],
600 repeater_steps: &'a [u32],
601 counter: usize,
603 repeat_u16_offset: usize,
605 next_rep: usize,
607 current_offset: usize,
609 result: &'a mut GridLayoutOrganizedData,
611}
612
613impl<'a> OrganizedDataGenerator<'a> {
614 fn new(
615 repeater_indices: &'a [u32],
616 repeater_steps: &'a [u32],
617 static_cells: usize,
618 num_repeaters: usize,
619 total_repeated_cells_count: usize,
620 result: &'a mut GridLayoutOrganizedData,
621 ) -> Self {
622 result.resize((static_cells + num_repeaters + total_repeated_cells_count) * 4, 0 as _);
623 let repeat_u16_offset = (static_cells + num_repeaters) * 4;
624 Self {
625 repeater_indices,
626 repeater_steps,
627 counter: 0,
628 repeat_u16_offset,
629 next_rep: 0,
630 current_offset: 0,
631 result,
632 }
633 }
634 fn add(&mut self, col: u16, colspan: u16, row: u16, rowspan: u16) {
635 let res = self.result.make_mut_slice();
636 loop {
637 if let Some(nr) = self.repeater_indices.get(self.next_rep * 2) {
638 let nr = *nr as usize;
639 let step = self.repeater_steps.get(self.next_rep).copied().unwrap_or(1) as usize;
640 let rep_count = self.repeater_indices[self.next_rep * 2 + 1] as usize;
641
642 if nr == self.counter {
643 let data_u16_start = self.repeat_u16_offset;
645 let stride = step * 4;
646
647 res[self.current_offset * 4] = data_u16_start as _;
649 res[self.current_offset * 4 + 1] = stride as _;
650 self.current_offset += 1;
651 }
652 if self.counter >= nr {
653 let cells_in_repeater = rep_count * step;
654 if self.counter - nr == cells_in_repeater {
655 self.repeat_u16_offset += cells_in_repeater * 4;
657 self.next_rep += 1;
658 continue;
659 }
660 let cell_in_rep = self.counter - nr;
662 let row_in_rep = cell_in_rep / step;
663 let col_in_rep = cell_in_rep % step;
664 let data_u16_start = self.repeat_u16_offset;
665 let u16_pos = data_u16_start + row_in_rep * step * 4 + col_in_rep * 4;
666 res[u16_pos] = col;
667 res[u16_pos + 1] = colspan;
668 res[u16_pos + 2] = row;
669 res[u16_pos + 3] = rowspan;
670 self.counter += 1;
671 return;
672 }
673 }
674 res[self.current_offset * 4] = col;
676 res[self.current_offset * 4 + 1] = colspan;
677 res[self.current_offset * 4 + 2] = row;
678 res[self.current_offset * 4 + 3] = rowspan;
679 self.current_offset += 1;
680 self.counter += 1;
681 return;
682 }
683 }
684}
685
686pub fn organize_dialog_button_layout(
689 input_data: Slice<GridLayoutInputData>,
690 dialog_button_roles: Slice<DialogButtonRole>,
691) -> GridLayoutOrganizedData {
692 let mut organized_data = GridLayoutOrganizedData::default();
693 organized_data.reserve(input_data.len() * 4);
694
695 #[cfg(feature = "std")]
696 fn is_kde() -> bool {
697 std::env::var("XDG_CURRENT_DESKTOP")
699 .ok()
700 .and_then(|v| v.as_bytes().first().copied())
701 .is_some_and(|x| x.eq_ignore_ascii_case(&b'K'))
702 }
703 #[cfg(not(feature = "std"))]
704 let is_kde = || true;
705
706 let expected_order: &[DialogButtonRole] = match crate::detect_operating_system() {
707 crate::items::OperatingSystemType::Windows => {
708 &[
709 DialogButtonRole::Reset,
710 DialogButtonRole::None, DialogButtonRole::Accept,
712 DialogButtonRole::Action,
713 DialogButtonRole::Reject,
714 DialogButtonRole::Apply,
715 DialogButtonRole::Help,
716 ]
717 }
718 crate::items::OperatingSystemType::Macos | crate::items::OperatingSystemType::Ios => {
719 &[
720 DialogButtonRole::Help,
721 DialogButtonRole::Reset,
722 DialogButtonRole::Apply,
723 DialogButtonRole::Action,
724 DialogButtonRole::None, DialogButtonRole::Reject,
726 DialogButtonRole::Accept,
727 ]
728 }
729 _ if is_kde() => {
730 &[
732 DialogButtonRole::Help,
733 DialogButtonRole::Reset,
734 DialogButtonRole::None, DialogButtonRole::Action,
736 DialogButtonRole::Accept,
737 DialogButtonRole::Apply,
738 DialogButtonRole::Reject,
739 ]
740 }
741 _ => {
742 &[
744 DialogButtonRole::Help,
745 DialogButtonRole::Reset,
746 DialogButtonRole::None, DialogButtonRole::Action,
748 DialogButtonRole::Accept,
749 DialogButtonRole::Apply,
750 DialogButtonRole::Reject,
751 ]
752 }
753 };
754
755 let mut column_for_input: Vec<usize> = Vec::with_capacity(dialog_button_roles.len());
757 for role in expected_order.iter() {
758 if role == &DialogButtonRole::None {
759 column_for_input.push(usize::MAX); continue;
761 }
762 for (idx, r) in dialog_button_roles.as_slice().iter().enumerate() {
763 if *r == *role {
764 column_for_input.push(idx);
765 }
766 }
767 }
768
769 for (input_index, cell) in input_data.as_slice().iter().enumerate() {
770 let col = column_for_input.iter().position(|&x| x == input_index);
771 if let Some(col) = col {
772 organized_data.push_cell(col as _, cell.colspan as _, cell.row as _, cell.rowspan as _);
773 } else {
774 organized_data.push_cell(
777 cell.col as _,
778 cell.colspan as _,
779 cell.row as _,
780 cell.rowspan as _,
781 );
782 }
783 }
784 organized_data
785}
786
787fn total_repeated_cells<'a>(repeater_indices: &'a [u32], repeater_steps: &'a [u32]) -> usize {
789 repeater_indices
790 .chunks(2)
791 .enumerate()
792 .map(|(i, chunk)| {
793 let count = chunk.get(1).copied().unwrap_or(0) as usize;
794 let step = repeater_steps.get(i).copied().unwrap_or(1) as usize;
795 count * step
796 })
797 .sum()
798}
799
800type Errors = Vec<String>;
801
802pub fn organize_grid_layout(
803 input_data: Slice<GridLayoutInputData>,
804 repeater_indices: Slice<u32>,
805 repeater_steps: Slice<u32>,
806) -> GridLayoutOrganizedData {
807 let (organized_data, errors) =
808 organize_grid_layout_impl(input_data, repeater_indices, repeater_steps);
809 for error in errors {
810 crate::debug_log!("Slint layout error: {}", error);
811 }
812 organized_data
813}
814
815fn organize_grid_layout_impl(
817 input_data: Slice<GridLayoutInputData>,
818 repeater_indices: Slice<u32>,
819 repeater_steps: Slice<u32>,
820) -> (GridLayoutOrganizedData, Errors) {
821 let mut organized_data = GridLayoutOrganizedData::default();
822 let num_repeaters = repeater_indices.len() / 2;
825 let total_repeated_cells =
826 total_repeated_cells(repeater_indices.as_slice(), repeater_steps.as_slice());
827 let static_cells = input_data.len() - total_repeated_cells;
828 let mut generator = OrganizedDataGenerator::new(
829 repeater_indices.as_slice(),
830 repeater_steps.as_slice(),
831 static_cells,
832 num_repeaters,
833 total_repeated_cells,
834 &mut organized_data,
835 );
836 let mut errors = Vec::new();
837
838 fn clamp_to_u16(value: f32, field_name: &str, errors: &mut Vec<String>) -> u16 {
839 if value < 0.0 {
840 errors.push(format!("cell {field_name} {value} is negative, clamping to 0"));
841 0
842 } else if value > u16::MAX as f32 {
843 errors
844 .push(format!("cell {field_name} {value} is too large, clamping to {}", u16::MAX));
845 u16::MAX
846 } else {
847 value as u16
848 }
849 }
850
851 let mut row = 0;
852 let mut col = 0;
853 let mut first = true;
854 for cell in input_data.as_slice().iter() {
855 if cell.new_row && !first {
856 row += 1;
857 col = 0;
858 }
859 first = false;
860
861 if cell.row != i_slint_common::ROW_COL_AUTO {
862 let cell_row = clamp_to_u16(cell.row, "row", &mut errors);
863 if row != cell_row {
864 row = cell_row;
865 col = 0;
866 }
867 }
868 if cell.col != i_slint_common::ROW_COL_AUTO {
869 col = clamp_to_u16(cell.col, "col", &mut errors);
870 }
871
872 let colspan = clamp_to_u16(cell.colspan, "colspan", &mut errors);
873 let rowspan = clamp_to_u16(cell.rowspan, "rowspan", &mut errors);
874 col = col.min(u16::MAX - colspan); generator.add(col, colspan, row, rowspan);
876 col += colspan;
877 }
878 (organized_data, errors)
879}
880
881struct LayoutCacheGenerator<'a> {
889 repeater_indices: &'a [u32],
891 counter: usize,
893 repeat_offset: usize,
895 next_rep: usize,
897 current_offset: usize,
899 result: &'a mut SharedVector<Coord>,
901}
902
903impl<'a> LayoutCacheGenerator<'a> {
904 fn new(repeater_indices: &'a [u32], result: &'a mut SharedVector<Coord>) -> Self {
905 let total_repeated_cells: usize = repeater_indices
906 .chunks(2)
907 .map(|chunk| chunk.get(1).copied().unwrap_or(0) as usize)
908 .sum();
909 assert!(result.len() >= total_repeated_cells * 2);
910 let repeat_offset = result.len() / 2 - total_repeated_cells;
911 Self { repeater_indices, counter: 0, repeat_offset, next_rep: 0, current_offset: 0, result }
912 }
913 fn add(&mut self, pos: Coord, size: Coord) {
914 let res = self.result.make_mut_slice();
915 let o = loop {
916 if let Some(nr) = self.repeater_indices.get(self.next_rep * 2) {
917 let nr = *nr as usize;
918 if nr == self.counter {
919 for o in 0..2 {
921 res[self.current_offset * 2 + o] = (self.repeat_offset * 2 + o) as _;
922 }
923 self.current_offset += 1;
924 }
925 if self.counter >= nr {
926 let rep_count = self.repeater_indices[self.next_rep * 2 + 1] as usize;
927 if self.counter - nr == rep_count {
928 self.repeat_offset += rep_count;
929 self.next_rep += 1;
930 continue;
931 }
932 let offset = self.repeat_offset + (self.counter - nr);
933 break offset;
934 }
935 }
936 self.current_offset += 1;
937 break self.current_offset - 1;
938 };
939 res[o * 2] = pos;
940 res[o * 2 + 1] = size;
941 self.counter += 1;
942 }
943}
944
945struct GridLayoutCacheGenerator<'a> {
949 repeater_indices: &'a [u32],
951 repeater_steps: &'a [u32],
952 counter: usize,
954 repeat_f32_offset: usize,
956 next_rep: usize,
958 current_offset: usize,
960 result: &'a mut SharedVector<Coord>,
962}
963
964impl<'a> GridLayoutCacheGenerator<'a> {
965 fn new(
966 repeater_indices: &'a [u32],
967 repeater_steps: &'a [u32],
968 static_cells: usize,
969 num_repeaters: usize,
970 total_repeated_cells_count: usize,
971 result: &'a mut SharedVector<Coord>,
972 ) -> Self {
973 result.resize((static_cells + num_repeaters + total_repeated_cells_count) * 2, 0 as _);
974 let repeat_f32_offset = (static_cells + num_repeaters) * 2;
975 Self {
976 repeater_indices,
977 repeater_steps,
978 counter: 0,
979 repeat_f32_offset,
980 next_rep: 0,
981 current_offset: 0,
982 result,
983 }
984 }
985 fn add(&mut self, pos: Coord, size: Coord) {
986 let res = self.result.make_mut_slice();
987 loop {
988 if let Some(nr) = self.repeater_indices.get(self.next_rep * 2) {
989 let nr = *nr as usize;
990 let step = self.repeater_steps.get(self.next_rep).copied().unwrap_or(1) as usize;
991 let rep_count = self.repeater_indices[self.next_rep * 2 + 1] as usize;
992
993 if nr == self.counter {
994 let data_f32_start = self.repeat_f32_offset;
996 let stride = step * 2;
997
998 res[self.current_offset * 2] = data_f32_start as _;
1000 res[self.current_offset * 2 + 1] = stride as _;
1001 self.current_offset += 1;
1002 }
1003 if self.counter >= nr {
1004 let cells_in_repeater = rep_count * step;
1005 if self.counter - nr == cells_in_repeater {
1006 self.repeat_f32_offset += cells_in_repeater * 2;
1008 self.next_rep += 1;
1009 continue;
1010 }
1011 let cell_in_rep = self.counter - nr;
1013 let row_in_rep = cell_in_rep / step;
1014 let col_in_rep = cell_in_rep % step;
1015 let data_f32_start = self.repeat_f32_offset;
1016 let f32_pos = data_f32_start + row_in_rep * step * 2 + col_in_rep * 2;
1017 res[f32_pos] = pos;
1018 res[f32_pos + 1] = size;
1019 self.counter += 1;
1020 return;
1021 }
1022 }
1023 res[self.current_offset * 2] = pos;
1025 res[self.current_offset * 2 + 1] = size;
1026 self.current_offset += 1;
1027 self.counter += 1;
1028 return;
1029 }
1030 }
1031}
1032
1033pub fn solve_grid_layout(
1036 data: &GridLayoutData,
1037 constraints: Slice<LayoutItemInfo>,
1038 orientation: Orientation,
1039 repeater_indices: Slice<u32>,
1040 repeater_steps: Slice<u32>,
1041) -> SharedVector<Coord> {
1042 let mut layout_data = grid_internal::to_layout_data(
1043 &data.organized_data,
1044 constraints,
1045 orientation,
1046 repeater_indices,
1047 repeater_steps,
1048 data.spacing,
1049 Some(data.size),
1050 );
1051
1052 if layout_data.is_empty() {
1053 return Default::default();
1054 }
1055
1056 grid_internal::layout_items(
1057 &mut layout_data,
1058 data.padding.begin,
1059 data.size - (data.padding.begin + data.padding.end),
1060 data.spacing,
1061 );
1062
1063 let mut result = SharedVector::<Coord>::default();
1064 let num_repeaters = repeater_indices.len() / 2;
1065 let total_repeated_cells =
1066 total_repeated_cells(repeater_indices.as_slice(), repeater_steps.as_slice());
1067 let static_cells = constraints.len() - total_repeated_cells;
1068 let mut generator = GridLayoutCacheGenerator::new(
1069 repeater_indices.as_slice(),
1070 repeater_steps.as_slice(),
1071 static_cells,
1072 num_repeaters,
1073 total_repeated_cells,
1074 &mut result,
1075 );
1076
1077 for idx in 0..constraints.len() {
1078 let (col_or_row, span) = data.organized_data.col_or_row_and_span(
1079 idx,
1080 orientation,
1081 &repeater_indices,
1082 &repeater_steps,
1083 );
1084 let cdata = &layout_data[col_or_row as usize];
1085 let size = if span > 0 {
1086 let last_cell = &layout_data[col_or_row as usize + span as usize - 1];
1087 last_cell.pos + last_cell.size - cdata.pos
1088 } else {
1089 0 as Coord
1090 };
1091 generator.add(cdata.pos, size);
1092 }
1093 result
1094}
1095
1096pub fn grid_layout_info(
1097 organized_data: GridLayoutOrganizedData, constraints: Slice<LayoutItemInfo>,
1099 repeater_indices: Slice<u32>,
1100 repeater_steps: Slice<u32>,
1101 spacing: Coord,
1102 padding: &Padding,
1103 orientation: Orientation,
1104) -> LayoutInfo {
1105 let layout_data = grid_internal::to_layout_data(
1106 &organized_data,
1107 constraints,
1108 orientation,
1109 repeater_indices,
1110 repeater_steps,
1111 spacing,
1112 None,
1113 );
1114 if layout_data.is_empty() {
1115 let mut info = LayoutInfo::default();
1116 info.min = padding.begin + padding.end;
1117 info.preferred = info.min;
1118 info.max = info.min;
1119 return info;
1120 }
1121 let spacing_w = spacing * (layout_data.len() - 1) as Coord + padding.begin + padding.end;
1122 let min = layout_data.iter().map(|data| data.min).sum::<Coord>() + spacing_w;
1123 let max = layout_data.iter().map(|data| data.max).fold(spacing_w, Saturating::add);
1124 let preferred = layout_data.iter().map(|data| data.pref).sum::<Coord>() + spacing_w;
1125 let stretch = layout_data.iter().map(|data| data.stretch).sum::<f32>();
1126 LayoutInfo { min, max, min_percent: 0 as _, max_percent: 100 as _, preferred, stretch }
1127}
1128
1129#[repr(C)]
1130#[derive(Debug)]
1131pub struct BoxLayoutData<'a> {
1135 pub size: Coord,
1136 pub spacing: Coord,
1137 pub padding: Padding,
1138 pub alignment: LayoutAlignment,
1139 pub cells: Slice<'a, LayoutItemInfo>,
1140}
1141
1142#[repr(C)]
1144#[derive(Debug)]
1145pub struct BoxLayoutOrthoData<'a> {
1146 pub size: Coord,
1147 pub padding: Padding,
1148 pub cross_axis_alignment: CrossAxisAlignment,
1149 pub cells: Slice<'a, LayoutItemInfo>,
1150}
1151
1152#[repr(C)]
1153#[derive(Debug)]
1154pub struct FlexboxLayoutData<'a> {
1156 pub width: Coord,
1157 pub height: Coord,
1158 pub spacing_h: Coord,
1159 pub spacing_v: Coord,
1160 pub padding_h: Padding,
1161 pub padding_v: Padding,
1162 pub alignment: LayoutAlignment,
1163 pub direction: FlexboxLayoutDirection,
1164 pub cross_axis_line_alignment: LayoutAlignment,
1165 pub cross_axis_alignment: CrossAxisAlignment,
1166 pub flex_wrap: FlexboxLayoutWrap,
1167 pub cells_h: Slice<'a, LayoutItemInfo>,
1169 pub cells_v: Slice<'a, LayoutItemInfo>,
1171 pub flex_props: Slice<'a, FlexItemProps>,
1173}
1174
1175#[repr(C)]
1176#[derive(Debug, Clone, Default)]
1177pub struct LayoutItemInfo {
1179 pub constraint: LayoutInfo,
1180 pub cross_axis_self_alignment: CrossAxisAlignment,
1183 pub layout_order: i32,
1189}
1190
1191#[repr(C)]
1197#[derive(Debug, Clone, Copy, Default)]
1198pub struct FlexItemProps {
1199 pub cross_axis_self_alignment: CrossAxisAlignment,
1201 pub layout_order: i32,
1203}
1204
1205#[repr(C)]
1206#[derive(Debug, Clone, Default)]
1207pub struct FlexboxLayoutItemInfo {
1212 pub constraint: LayoutInfo,
1213 pub props: FlexItemProps,
1214}
1215
1216impl From<LayoutItemInfo> for FlexboxLayoutItemInfo {
1217 fn from(info: LayoutItemInfo) -> Self {
1218 Self {
1219 constraint: info.constraint,
1220 props: FlexItemProps {
1221 cross_axis_self_alignment: info.cross_axis_self_alignment,
1222 layout_order: info.layout_order,
1223 },
1224 }
1225 }
1226}
1227
1228pub fn solve_box_layout(data: &BoxLayoutData, repeater_indices: Slice<u32>) -> SharedVector<Coord> {
1230 let mut result = SharedVector::<Coord>::default();
1231 result.resize(data.cells.len() * 2 + repeater_indices.len(), 0 as _);
1233
1234 if data.cells.is_empty() {
1235 return result;
1236 }
1237
1238 let size_without_padding = data.size - data.padding.begin - data.padding.end;
1239 let num_spacings = (data.cells.len() - 1) as Coord;
1240 let spacings = data.spacing * num_spacings;
1241 let content_size = size_without_padding - spacings; let mut layout_data: Vec<_> = data
1243 .cells
1244 .iter()
1245 .map(|c| {
1246 let min = c.constraint.min.max(c.constraint.min_percent * content_size / 100 as Coord);
1247 let max = c.constraint.max.min(c.constraint.max_percent * content_size / 100 as Coord);
1248 grid_internal::LayoutData {
1249 min,
1250 max,
1251 pref: c.constraint.preferred.min(max).max(min),
1252 stretch: c.constraint.stretch,
1253 ..Default::default()
1254 }
1255 })
1256 .collect();
1257
1258 let order_map: Vec<usize> = if data.cells.iter().any(|c| c.layout_order != 0) {
1262 let mut indices: Vec<usize> = (0..layout_data.len()).collect();
1263 indices.sort_by_key(|&i| data.cells[i].layout_order);
1265 layout_data = indices.iter().map(|&i| layout_data[i].clone()).collect();
1266 indices
1267 } else {
1268 Vec::new()
1269 };
1270
1271 let pref_size: Coord = layout_data.iter().map(|it| it.pref).sum();
1272
1273 let align = match data.alignment {
1274 LayoutAlignment::Stretch => {
1275 grid_internal::layout_items(
1276 &mut layout_data,
1277 data.padding.begin,
1278 size_without_padding,
1279 data.spacing,
1280 );
1281 None
1282 }
1283 _ if size_without_padding <= pref_size + spacings => {
1284 grid_internal::layout_items(
1285 &mut layout_data,
1286 data.padding.begin,
1287 size_without_padding,
1288 data.spacing,
1289 );
1290 None
1291 }
1292 LayoutAlignment::Center => Some((
1293 data.padding.begin + (size_without_padding - pref_size - spacings) / 2 as Coord,
1294 data.spacing,
1295 )),
1296 LayoutAlignment::Start => Some((data.padding.begin, data.spacing)),
1297 LayoutAlignment::End => {
1298 Some((data.padding.begin + (size_without_padding - pref_size - spacings), data.spacing))
1299 }
1300 LayoutAlignment::SpaceBetween => {
1301 Some((data.padding.begin, (size_without_padding - pref_size) / num_spacings))
1302 }
1303 LayoutAlignment::SpaceAround => {
1304 let spacing = (size_without_padding - pref_size) / (num_spacings + 1 as Coord);
1305 Some((data.padding.begin + spacing / 2 as Coord, spacing))
1306 }
1307 LayoutAlignment::SpaceEvenly => {
1308 let spacing = (size_without_padding - pref_size) / (num_spacings + 2 as Coord);
1309 Some((data.padding.begin + spacing, spacing))
1310 }
1311 };
1312 if let Some((mut pos, spacing)) = align {
1313 for it in &mut layout_data {
1314 it.pos = pos;
1315 it.size = it.pref;
1316 pos += spacing + it.size;
1317 }
1318 }
1319
1320 let mut generator = LayoutCacheGenerator::new(&repeater_indices, &mut result);
1321 if order_map.is_empty() {
1322 for layout in layout_data.iter() {
1323 generator.add(layout.pos, layout.size);
1324 }
1325 } else {
1326 let mut geom = alloc::vec![(0 as Coord, 0 as Coord); layout_data.len()];
1327 for (sorted_idx, &declared_idx) in order_map.iter().enumerate() {
1328 let layout = &layout_data[sorted_idx];
1329 geom[declared_idx] = (layout.pos, layout.size);
1330 }
1331 for (pos, size) in geom {
1332 generator.add(pos, size);
1333 }
1334 }
1335 result
1336}
1337
1338fn resolve_cross_axis_alignment(
1341 self_alignment: CrossAxisAlignment,
1342 container_alignment: CrossAxisAlignment,
1343) -> CrossAxisAlignment {
1344 let alignment = match self_alignment {
1345 CrossAxisAlignment::Auto => container_alignment,
1346 other => other,
1347 };
1348 match alignment {
1349 CrossAxisAlignment::Auto => CrossAxisAlignment::Stretch,
1350 other => other,
1351 }
1352}
1353
1354pub fn solve_box_layout_ortho(
1356 data: &BoxLayoutOrthoData,
1357 repeater_indices: Slice<u32>,
1358) -> SharedVector<Coord> {
1359 let mut result = SharedVector::<Coord>::default();
1360 result.resize(data.cells.len() * 2 + repeater_indices.len(), 0 as _);
1361 if data.cells.is_empty() {
1362 return result;
1363 }
1364 let size_without_padding = data.size - data.padding.begin - data.padding.end;
1365 let mut generator = LayoutCacheGenerator::new(&repeater_indices, &mut result);
1366 for c in data.cells.iter() {
1367 let alignment =
1368 resolve_cross_axis_alignment(c.cross_axis_self_alignment, data.cross_axis_alignment);
1369 let min =
1370 c.constraint.min.max(c.constraint.min_percent * size_without_padding / 100 as Coord);
1371 let max =
1372 c.constraint.max.min(c.constraint.max_percent * size_without_padding / 100 as Coord);
1373 let size = match alignment {
1374 CrossAxisAlignment::Stretch => size_without_padding,
1375 _ => c.constraint.preferred,
1376 }
1377 .min(max)
1378 .max(min);
1379 let pos = match alignment {
1380 CrossAxisAlignment::Auto | CrossAxisAlignment::Stretch | CrossAxisAlignment::Start => {
1381 data.padding.begin
1382 }
1383 CrossAxisAlignment::End => data.padding.begin + size_without_padding - size,
1384 CrossAxisAlignment::Center => {
1385 data.padding.begin + (size_without_padding - size) / 2 as Coord
1386 }
1387 };
1388 generator.add(pos, size);
1389 }
1390 result
1391}
1392
1393pub fn box_layout_info(
1395 cells: Slice<LayoutItemInfo>,
1396 spacing: Coord,
1397 padding: &Padding,
1398 alignment: LayoutAlignment,
1399) -> LayoutInfo {
1400 let count = cells.len();
1401 let is_stretch = alignment == LayoutAlignment::Stretch;
1402 if count < 1 {
1403 let mut info = LayoutInfo::default();
1404 info.min = padding.begin + padding.end;
1405 info.preferred = info.min;
1406 if is_stretch {
1407 info.max = info.min;
1408 }
1409 return info;
1410 };
1411 let extra_w = padding.begin + padding.end + spacing * (count - 1) as Coord;
1412 let min = cells.iter().map(|c| c.constraint.min).sum::<Coord>() + extra_w; let max = if is_stretch {
1414 (cells.iter().map(|c| c.constraint.max).fold(extra_w, Saturating::add)).max(min)
1415 } else {
1416 Coord::MAX
1417 }; let preferred = cells.iter().map(|c| c.constraint.preferred_bounded()).sum::<Coord>() + extra_w;
1419 let stretch = cells.iter().map(|c| c.constraint.stretch).sum::<f32>();
1420 LayoutInfo { min, max, min_percent: 0 as _, max_percent: 100 as _, preferred, stretch }
1421}
1422
1423pub fn box_layout_info_ortho(cells: Slice<LayoutItemInfo>, padding: &Padding) -> LayoutInfo {
1424 let extra_w = padding.begin + padding.end;
1425 let mut fold =
1426 cells.iter().fold(LayoutInfo { stretch: f32::MAX, ..Default::default() }, |a, b| {
1427 a.merge(&b.constraint)
1428 });
1429 fold.max = fold.max.max(fold.min);
1430 fold.preferred = fold.preferred.clamp(fold.min, fold.max);
1431 fold.min += extra_w;
1432 fold.max = Saturating::add(fold.max, extra_w);
1433 fold.preferred += extra_w;
1434 fold.min_percent = 0 as _;
1437 fold.max_percent = 100 as _;
1438 fold
1439}
1440
1441mod flexbox_taffy {
1443 use super::{
1444 Coord, CrossAxisAlignment, FlexItemProps, FlexboxLayoutWrap as SlintFlexboxLayoutWrap,
1445 LayoutAlignment, LayoutInfo, LayoutItemInfo, Padding, Slice, resolve_cross_axis_alignment,
1446 };
1447 use alloc::vec::Vec;
1448 pub use taffy::prelude::FlexDirection as TaffyFlexDirection;
1449 use taffy::prelude::*;
1450
1451 fn to_align_content(alignment: LayoutAlignment) -> AlignContent {
1454 match alignment {
1455 LayoutAlignment::Stretch => AlignContent::Stretch,
1456 LayoutAlignment::Start => AlignContent::FlexStart,
1457 LayoutAlignment::End => AlignContent::FlexEnd,
1458 LayoutAlignment::Center => AlignContent::Center,
1459 LayoutAlignment::SpaceBetween => AlignContent::SpaceBetween,
1460 LayoutAlignment::SpaceAround => AlignContent::SpaceAround,
1461 LayoutAlignment::SpaceEvenly => AlignContent::SpaceEvenly,
1462 }
1463 }
1464
1465 #[derive(Copy, Clone, PartialEq, Eq)]
1467 pub enum CrossAxisSizing {
1468 Preferred,
1471 FromMeasure,
1475 Minimum,
1479 }
1480
1481 pub struct FlexboxLayoutParams<'a> {
1483 pub cells_h: &'a Slice<'a, LayoutItemInfo>,
1484 pub cells_v: &'a Slice<'a, LayoutItemInfo>,
1485 pub flex_props: &'a Slice<'a, FlexItemProps>,
1486 pub spacing_h: Coord,
1487 pub spacing_v: Coord,
1488 pub padding_h: &'a Padding,
1489 pub padding_v: &'a Padding,
1490 pub alignment: LayoutAlignment,
1491 pub cross_axis_line_alignment: LayoutAlignment,
1492 pub cross_axis_alignment: CrossAxisAlignment,
1493 pub flex_wrap: SlintFlexboxLayoutWrap,
1494 pub flex_shrink: f32,
1498 pub flex_direction: TaffyFlexDirection,
1499 pub container_width: Option<Coord>,
1500 pub container_height: Option<Coord>,
1501 pub cross_axis_sizing: CrossAxisSizing,
1502 }
1503
1504 pub struct FlexboxTaffyBuilder {
1507 pub taffy: TaffyTree<usize>,
1508 pub children: Vec<NodeId>,
1509 pub container: NodeId,
1510 pub order_map: Vec<usize>,
1512 }
1513
1514 impl FlexboxTaffyBuilder {
1515 pub fn new(params: FlexboxLayoutParams) -> Self {
1517 let mut taffy = TaffyTree::<usize>::new();
1518
1519 let content_box = |size: Option<Coord>, pad: &Padding| -> Option<Coord> {
1528 size.filter(|s| *s < Coord::MAX).map(|s| (s - pad.begin - pad.end).max(0 as Coord))
1529 };
1530 let content_w = content_box(params.container_width, params.padding_h);
1531 let content_h = content_box(params.container_height, params.padding_v);
1532
1533 let (column_cross_cap, row_cross_cap) = match params.flex_direction {
1539 TaffyFlexDirection::Column | TaffyFlexDirection::ColumnReverse => (content_w, None),
1540 TaffyFlexDirection::Row | TaffyFlexDirection::RowReverse => (None, content_h),
1541 };
1542
1543 let eff_min = |c: &LayoutInfo, content: Option<Coord>| -> Coord {
1549 match content {
1550 Some(s) if c.min_percent > 0 as Coord => {
1551 c.min.max(c.min_percent * s / 100 as Coord)
1552 }
1553 _ => c.min,
1554 }
1555 };
1556 let eff_max = |c: &LayoutInfo, content: Option<Coord>| -> Coord {
1557 match content {
1558 Some(s) if c.max_percent < 100 as Coord => {
1559 c.max.min(c.max_percent * s / 100 as Coord)
1560 }
1561 _ => c.max,
1562 }
1563 };
1564
1565 let main_cells = match params.flex_direction {
1577 TaffyFlexDirection::Row | TaffyFlexDirection::RowReverse => params.cells_h,
1578 TaffyFlexDirection::Column | TaffyFlexDirection::ColumnReverse => params.cells_v,
1579 };
1580 let any_stretch = main_cells.iter().any(|c| c.constraint.stretch > 0.);
1581
1582 let mut children: Vec<NodeId> = params
1584 .cells_h
1585 .iter()
1586 .enumerate()
1587 .map(|(idx, cell_h)| {
1588 let cell_v = params.cells_v.get(idx);
1589 let flex = params.flex_props.get(idx).cloned().unwrap_or_default();
1590 let h_constraint = &cell_h.constraint;
1591 let v_constraint = cell_v.map(|c| &c.constraint);
1592
1593 let preferred_width = h_constraint.preferred_bounded();
1595 let preferred_height =
1596 v_constraint.map(|vc| vc.preferred_bounded()).unwrap_or(0 as Coord);
1597
1598 let flex_basis = match params.flex_direction {
1601 TaffyFlexDirection::Row | TaffyFlexDirection::RowReverse => {
1602 Dimension::length(preferred_width as _)
1603 }
1604 TaffyFlexDirection::Column | TaffyFlexDirection::ColumnReverse
1611 if params.cross_axis_sizing == CrossAxisSizing::FromMeasure =>
1612 {
1613 Dimension::auto()
1614 }
1615 TaffyFlexDirection::Column | TaffyFlexDirection::ColumnReverse => {
1616 Dimension::length(preferred_height as _)
1617 }
1618 };
1619
1620 let max_width = eff_max(h_constraint, content_w)
1621 .min(column_cross_cap.unwrap_or(Coord::MAX));
1622 let max_height = v_constraint
1623 .map_or(Coord::MAX, |vc| eff_max(vc, content_h))
1624 .min(row_cross_cap.unwrap_or(Coord::MAX));
1625 let max_width_dim = if max_width < Coord::MAX {
1626 Dimension::length(max_width as _)
1627 } else {
1628 Dimension::auto()
1629 };
1630
1631 let stretches = resolve_cross_axis_alignment(
1639 flex.cross_axis_self_alignment,
1640 params.cross_axis_alignment,
1641 ) == CrossAxisAlignment::Stretch;
1642 let cross_auto =
1643 stretches || params.cross_axis_sizing != CrossAxisSizing::Preferred;
1644 let definite_cross = |preferred: Coord| {
1645 if cross_auto || preferred <= 0 as Coord {
1646 Dimension::auto()
1647 } else {
1648 Dimension::length(preferred as _)
1649 }
1650 };
1651
1652 taffy
1653 .new_leaf_with_context(
1654 Style {
1655 flex_basis,
1656 size: Size {
1657 width: match params.flex_direction {
1658 TaffyFlexDirection::Column
1659 | TaffyFlexDirection::ColumnReverse => {
1660 definite_cross(preferred_width)
1661 }
1662 _ => Dimension::auto(),
1663 },
1664 height: match params.flex_direction {
1665 TaffyFlexDirection::Row
1666 | TaffyFlexDirection::RowReverse => {
1667 definite_cross(preferred_height)
1668 }
1669 _ => Dimension::auto(),
1670 },
1671 },
1672 min_size: Size {
1673 width: Dimension::length(eff_min(h_constraint, content_w) as _),
1674 height: Dimension::length(
1675 v_constraint
1676 .map(|vc| eff_min(vc, content_h) as f32)
1677 .unwrap_or(0.0),
1678 ),
1679 },
1680 max_size: Size {
1681 width: max_width_dim,
1682 height: if max_height < Coord::MAX {
1683 Dimension::length(max_height as _)
1684 } else {
1685 Dimension::auto()
1686 },
1687 },
1688 flex_grow: if params.alignment == LayoutAlignment::Stretch {
1689 if any_stretch {
1690 main_cells.get(idx).map_or(0., |c| c.constraint.stretch)
1691 } else {
1692 1.
1693 }
1694 } else {
1695 0.
1696 },
1697 flex_shrink: params.flex_shrink,
1706 align_self: match flex.cross_axis_self_alignment {
1707 CrossAxisAlignment::Auto => None,
1708 CrossAxisAlignment::Stretch => Some(AlignSelf::Stretch),
1709 CrossAxisAlignment::Start => Some(AlignSelf::FlexStart),
1710 CrossAxisAlignment::End => Some(AlignSelf::FlexEnd),
1711 CrossAxisAlignment::Center => Some(AlignSelf::Center),
1712 },
1713 ..Default::default()
1714 },
1715 idx,
1716 )
1717 .unwrap() })
1719 .collect();
1720
1721 let has_order = params.flex_props.iter().any(|f| f.layout_order != 0);
1724 let order_map: Vec<usize> = if has_order {
1725 let mut indices: Vec<usize> = (0..children.len()).collect();
1726 indices.sort_by_key(|&i| params.flex_props.get(i).map_or(0, |f| f.layout_order));
1728 let sorted_children: Vec<NodeId> = indices.iter().map(|&i| children[i]).collect();
1729 children = sorted_children;
1730 indices
1731 } else {
1732 Vec::new()
1733 };
1734
1735 let container = taffy
1737 .new_with_children(
1738 Style {
1739 display: Display::Flex,
1740 flex_direction: params.flex_direction,
1741 flex_wrap: match params.flex_wrap {
1742 SlintFlexboxLayoutWrap::Wrap => FlexWrap::Wrap,
1743 SlintFlexboxLayoutWrap::NoWrap => FlexWrap::NoWrap,
1744 SlintFlexboxLayoutWrap::WrapReverse => FlexWrap::WrapReverse,
1745 },
1746 justify_content: Some(to_align_content(params.alignment)),
1747 align_items: Some(match params.cross_axis_alignment {
1748 CrossAxisAlignment::Auto | CrossAxisAlignment::Stretch => {
1749 AlignItems::Stretch
1750 }
1751 CrossAxisAlignment::Start => AlignItems::FlexStart,
1752 CrossAxisAlignment::End => AlignItems::FlexEnd,
1753 CrossAxisAlignment::Center => AlignItems::Center,
1754 }),
1755 align_content: Some(to_align_content(params.cross_axis_line_alignment)),
1756 gap: Size {
1757 width: LengthPercentage::length(params.spacing_h as _),
1758 height: LengthPercentage::length(params.spacing_v as _),
1759 },
1760 padding: Rect {
1761 left: LengthPercentage::length(params.padding_h.begin as _),
1762 right: LengthPercentage::length(params.padding_h.end as _),
1763 top: LengthPercentage::length(params.padding_v.begin as _),
1764 bottom: LengthPercentage::length(params.padding_v.end as _),
1765 },
1766 size: Size {
1767 width: params
1768 .container_width
1769 .map(|w| Dimension::length(w as _))
1770 .unwrap_or(Dimension::auto()),
1771 height: params
1772 .container_height
1773 .map(|h| Dimension::length(h as _))
1774 .unwrap_or(Dimension::auto()),
1775 },
1776 ..Default::default()
1777 },
1778 &children,
1779 )
1780 .unwrap(); Self { taffy, children, container, order_map }
1783 }
1784
1785 pub fn compute_layout(
1793 &mut self,
1794 available_width: Coord,
1795 available_height: Coord,
1796 measure: &mut dyn FnMut(usize, Option<Coord>, Option<Coord>) -> (Coord, Coord),
1797 ) {
1798 let available_space = taffy::prelude::Size {
1799 width: if available_width < Coord::MAX {
1800 AvailableSpace::Definite(available_width as _)
1801 } else {
1802 AvailableSpace::MaxContent
1803 },
1804 height: if available_height < Coord::MAX {
1805 AvailableSpace::Definite(available_height as _)
1806 } else {
1807 AvailableSpace::MaxContent
1808 },
1809 };
1810 self.taffy
1811 .compute_layout_with_measure(
1812 self.container,
1813 available_space,
1814 |known_dimensions, _available_space, _node_id, node_context, _style| {
1815 let Some(&mut child_index) = node_context else {
1817 return taffy::prelude::Size::ZERO;
1818 };
1819 let known_w = known_dimensions.width.map(|w| w as Coord);
1820 let known_h = known_dimensions.height.map(|h| h as Coord);
1821 let (w, h) = measure(child_index, known_w, known_h);
1822 taffy::prelude::Size { width: w as f32, height: h as f32 }
1823 },
1824 )
1825 .unwrap_or_else(|e| {
1826 crate::debug_log!("FlexboxLayout computation error: {}", e);
1827 });
1828 }
1829
1830 pub fn container_size(&self) -> (Coord, Coord) {
1832 let layout = self.taffy.layout(self.container).unwrap();
1833 (layout.size.width as Coord, layout.size.height as Coord)
1834 }
1835
1836 pub fn child_geometry(&self, idx: usize) -> (Coord, Coord, Coord, Coord) {
1838 let layout = self.taffy.layout(self.children[idx]).unwrap();
1839 (
1840 layout.location.x as Coord,
1841 layout.location.y as Coord,
1842 layout.size.width as Coord,
1843 layout.size.height as Coord,
1844 )
1845 }
1846
1847 pub fn original_index(&self, taffy_idx: usize) -> usize {
1849 if self.order_map.is_empty() { taffy_idx } else { self.order_map[taffy_idx] }
1850 }
1851 }
1852}
1853
1854struct FlexboxLayoutCacheGenerator<'a> {
1856 repeater_indices: &'a [u32],
1858 counter: usize,
1860 repeat_offset: usize,
1862 next_rep: usize,
1864 current_offset: usize,
1866 result: &'a mut SharedVector<Coord>,
1868}
1869
1870impl<'a> FlexboxLayoutCacheGenerator<'a> {
1871 fn new(repeater_indices: &'a [u32], result: &'a mut SharedVector<Coord>) -> Self {
1872 let total_repeated_cells: usize = repeater_indices
1874 .chunks(2)
1875 .map(|chunk| chunk.get(1).copied().unwrap_or(0) as usize)
1876 .sum();
1877 assert!(result.len() >= total_repeated_cells * 4);
1878 let repeat_offset = result.len() / 4 - total_repeated_cells;
1879 Self { repeater_indices, counter: 0, repeat_offset, next_rep: 0, current_offset: 0, result }
1880 }
1881
1882 fn add(&mut self, x: Coord, y: Coord, w: Coord, h: Coord) {
1883 let res = self.result.make_mut_slice();
1884 let o = loop {
1885 if let Some(nr) = self.repeater_indices.get(self.next_rep * 2) {
1886 let nr = *nr as usize;
1887 if nr == self.counter {
1888 res[self.current_offset * 4] = (self.repeat_offset * 4) as Coord;
1891 res[self.current_offset * 4 + 1] = (self.repeat_offset * 4 + 1) as Coord;
1892 res[self.current_offset * 4 + 2] = (self.repeat_offset * 4 + 2) as Coord;
1893 res[self.current_offset * 4 + 3] = (self.repeat_offset * 4 + 3) as Coord;
1894 self.current_offset += 1;
1895 }
1896 if self.counter >= nr {
1897 let rep_count = self.repeater_indices[self.next_rep * 2 + 1] as usize;
1898 if self.counter - nr == rep_count {
1899 self.repeat_offset += rep_count;
1901 self.next_rep += 1;
1902 continue;
1903 }
1904 let cell_in_rep = self.counter - nr;
1906 let offset = self.repeat_offset + cell_in_rep;
1907 break offset;
1908 }
1909 }
1910 self.current_offset += 1;
1911 break self.current_offset - 1;
1912 };
1913 res[o * 4] = x;
1914 res[o * 4 + 1] = y;
1915 res[o * 4 + 2] = w;
1916 res[o * 4 + 3] = h;
1917 self.counter += 1;
1918 }
1919}
1920
1921pub type FlexboxMeasureFn<'a> = Option<&'a mut dyn FnMut(usize, Coord, Coord) -> (Coord, Coord)>;
1946
1947fn zero_measure(_: usize, _: Option<Coord>, _: Option<Coord>) -> (Coord, Coord) {
1953 (0 as Coord, 0 as Coord)
1954}
1955
1956fn resolve_measure_defaults<'a, 'm: 'a>(
1965 cells_h: &'a [LayoutItemInfo],
1966 cells_v: &'a [LayoutItemInfo],
1967 mut measure: FlexboxMeasureFn<'m>,
1968) -> impl FnMut(usize, Option<Coord>, Option<Coord>) -> (Coord, Coord) + 'a {
1969 move |index, known_w, known_h| {
1970 let w = known_w.unwrap_or_else(|| {
1971 cells_h.get(index).map_or(0 as Coord, |c| c.constraint.preferred_bounded())
1972 });
1973 let h = known_h.unwrap_or_else(|| {
1974 cells_v.get(index).map_or(0 as Coord, |c| c.constraint.preferred_bounded())
1975 });
1976 match (known_h, measure.as_mut()) {
1983 (None, Some(measure)) => measure(index, w, h),
1984 _ => (w, h),
1985 }
1986 }
1987}
1988
1989pub fn solve_flexbox_layout(
1990 data: &FlexboxLayoutData,
1991 repeater_indices: Slice<u32>,
1992) -> SharedVector<Coord> {
1993 solve_flexbox_layout_with_measure(data, repeater_indices, None)
1994}
1995
1996pub fn solve_flexbox_layout_with_measure(
1999 data: &FlexboxLayoutData,
2000 repeater_indices: Slice<u32>,
2001 measure: FlexboxMeasureFn<'_>,
2002) -> SharedVector<Coord> {
2003 let mut result = SharedVector::<Coord>::default();
2005 result.resize(data.cells_h.len() * 4 + repeater_indices.len() * 2, 0 as _);
2006
2007 if data.cells_h.is_empty() {
2008 return result;
2009 }
2010
2011 let taffy_direction = match data.direction {
2012 FlexboxLayoutDirection::Row => flexbox_taffy::TaffyFlexDirection::Row,
2013 FlexboxLayoutDirection::RowReverse => flexbox_taffy::TaffyFlexDirection::RowReverse,
2014 FlexboxLayoutDirection::Column => flexbox_taffy::TaffyFlexDirection::Column,
2015 FlexboxLayoutDirection::ColumnReverse => flexbox_taffy::TaffyFlexDirection::ColumnReverse,
2016 };
2017
2018 let (container_width, container_height) = (
2019 if data.width > 0 as Coord { Some(data.width) } else { None },
2020 if data.height > 0 as Coord { Some(data.height) } else { None },
2021 );
2022
2023 let use_measure = measure.is_some();
2024 let build = |flex_wrap, flex_shrink| {
2025 flexbox_taffy::FlexboxTaffyBuilder::new(flexbox_taffy::FlexboxLayoutParams {
2026 cells_h: &data.cells_h,
2027 cells_v: &data.cells_v,
2028 flex_props: &data.flex_props,
2029 spacing_h: data.spacing_h,
2030 spacing_v: data.spacing_v,
2031 padding_h: &data.padding_h,
2032 padding_v: &data.padding_v,
2033 alignment: data.alignment,
2034 cross_axis_line_alignment: data.cross_axis_line_alignment,
2035 cross_axis_alignment: data.cross_axis_alignment,
2036 flex_wrap,
2037 flex_shrink,
2038 flex_direction: taffy_direction,
2039 container_width,
2040 container_height,
2041 cross_axis_sizing: if use_measure {
2042 flexbox_taffy::CrossAxisSizing::FromMeasure
2043 } else {
2044 flexbox_taffy::CrossAxisSizing::Preferred
2045 },
2046 })
2047 };
2048 let mut builder = build(data.flex_wrap, 1.);
2049
2050 let (available_width, available_height) = match data.direction {
2051 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
2052 (data.width, Coord::MAX)
2053 }
2054 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
2055 (Coord::MAX, data.height)
2056 }
2057 };
2058
2059 let mut measure = resolve_measure_defaults(&data.cells_h, &data.cells_v, measure);
2063 builder.compute_layout(available_width, available_height, &mut measure);
2064
2065 let is_column = matches!(
2077 data.direction,
2078 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse
2079 );
2080 if is_column && data.flex_wrap == FlexboxLayoutWrap::Wrap && data.width > 0 as Coord {
2081 #[cfg(not(slint_int_coord))]
2084 const OVERFLOW_TOLERANCE: Coord = 0.5;
2085 #[cfg(slint_int_coord)]
2086 const OVERFLOW_TOLERANCE: Coord = 0;
2087 let (left, right) = (data.padding_h.begin, data.width - data.padding_h.end);
2088 let overflows = (0..data.cells_h.len()).any(|idx| {
2091 let (x, _, w, _) = builder.child_geometry(idx);
2092 x < left - OVERFLOW_TOLERANCE || x + w > right + OVERFLOW_TOLERANCE
2093 });
2094 if overflows {
2095 let one_column = data.cells_h.len() < 2
2117 || flexbox_layout_unwrapped_main(
2118 Slice::from_slice(data.cells_v.as_slice()),
2119 data.spacing_v,
2120 &data.padding_v,
2121 ) <= data.height;
2122 builder = build(FlexboxLayoutWrap::NoWrap, if one_column { 1. } else { 0. });
2123 builder.compute_layout(available_width, available_height, &mut measure);
2124 }
2125 }
2126
2127 if builder.order_map.is_empty() {
2131 let mut generator = FlexboxLayoutCacheGenerator::new(&repeater_indices, &mut result);
2132 for idx in 0..data.cells_h.len() {
2133 let (x, y, w, h) = builder.child_geometry(idx);
2134 generator.add(x, y, w, h);
2135 }
2136 } else {
2137 let count = data.cells_h.len();
2138 let mut geom = alloc::vec![(0 as Coord, 0 as Coord, 0 as Coord, 0 as Coord); count];
2139 for taffy_idx in 0..count {
2140 let orig_idx = builder.original_index(taffy_idx);
2141 geom[orig_idx] = builder.child_geometry(taffy_idx);
2142 }
2143 let mut generator = FlexboxLayoutCacheGenerator::new(&repeater_indices, &mut result);
2144 for (x, y, w, h) in geom {
2145 generator.add(x, y, w, h);
2146 }
2147 }
2148
2149 result
2150}
2151
2152pub fn flexbox_layout_unwrapped_main(
2159 cells: Slice<LayoutItemInfo>,
2160 spacing: Coord,
2161 padding: &Padding,
2162) -> Coord {
2163 let extra_pad = padding.begin + padding.end;
2164 if cells.is_empty() {
2165 return extra_pad;
2166 }
2167 let num_spacings = cells.len().saturating_sub(1) as Coord;
2168 cells.iter().map(|c| c.constraint.preferred_bounded()).sum::<Coord>()
2169 + spacing * num_spacings
2170 + extra_pad
2171}
2172
2173pub fn flexbox_layout_info_main_axis(
2178 cells: Slice<LayoutItemInfo>,
2179 spacing: Coord,
2180 padding: &Padding,
2181 flex_wrap: FlexboxLayoutWrap,
2182) -> LayoutInfo {
2183 let extra_pad = padding.begin + padding.end;
2184 if cells.is_empty() {
2185 return LayoutInfo {
2186 min: extra_pad,
2187 preferred: extra_pad,
2188 max: extra_pad,
2189 ..Default::default()
2190 };
2191 }
2192 let num_spacings = cells.len().saturating_sub(1) as Coord;
2193 let min = if matches!(flex_wrap, FlexboxLayoutWrap::NoWrap) {
2194 cells.iter().map(|c| c.constraint.min).sum::<Coord>() + spacing * num_spacings + extra_pad
2195 } else {
2196 cells.iter().map(|c| c.constraint.min).fold(0.0 as Coord, |a, b| a.max(b)) + extra_pad
2198 };
2199 let preferred = if matches!(flex_wrap, FlexboxLayoutWrap::NoWrap) {
2200 flexbox_layout_unwrapped_main(cells, spacing, padding)
2202 } else {
2203 let total_area: f64 = cells
2215 .iter()
2216 .map(|c| c.constraint.preferred_bounded() as f64 + spacing as f64)
2217 .map(|w| w * w)
2218 .sum();
2219 let target = Float::sqrt(total_area as f32) as Coord;
2220 let mut acc = 0 as Coord;
2221 let mut started = false;
2222 for c in cells.iter() {
2223 let size = c.constraint.preferred_bounded();
2227 acc += if started { spacing + size } else { size };
2228 started = true;
2229 if acc + spacing >= target {
2233 break;
2234 }
2235 }
2236 acc + extra_pad
2237 };
2238 let stretch = cells.iter().map(|c| c.constraint.stretch).sum::<f32>();
2239 LayoutInfo {
2240 min,
2241 max: Coord::MAX,
2242 min_percent: 0 as _,
2243 max_percent: 100 as _,
2244 preferred,
2245 stretch,
2246 }
2247}
2248
2249#[allow(clippy::too_many_arguments)]
2262pub fn flexbox_layout_info_cross_axis(
2263 cells_h: Slice<LayoutItemInfo>,
2264 cells_v: Slice<LayoutItemInfo>,
2265 flex_props: Slice<FlexItemProps>,
2266 spacing_h: Coord,
2267 spacing_v: Coord,
2268 padding_h: &Padding,
2269 padding_v: &Padding,
2270 direction: FlexboxLayoutDirection,
2271 alignment: LayoutAlignment,
2272 flex_wrap: FlexboxLayoutWrap,
2273 constraint_size: Coord,
2274) -> LayoutInfo {
2275 flexbox_layout_info_cross_axis_with_measure(
2276 cells_h,
2277 cells_v,
2278 flex_props,
2279 spacing_h,
2280 spacing_v,
2281 padding_h,
2282 padding_v,
2283 direction,
2284 alignment,
2285 flex_wrap,
2286 constraint_size,
2287 None,
2288 )
2289}
2290
2291#[allow(clippy::too_many_arguments)]
2300pub fn flexbox_layout_info_cross_axis_with_measure(
2301 cells_h: Slice<LayoutItemInfo>,
2302 cells_v: Slice<LayoutItemInfo>,
2303 flex_props: Slice<FlexItemProps>,
2304 spacing_h: Coord,
2305 spacing_v: Coord,
2306 padding_h: &Padding,
2307 padding_v: &Padding,
2308 direction: FlexboxLayoutDirection,
2309 alignment: LayoutAlignment,
2310 flex_wrap: FlexboxLayoutWrap,
2311 constraint_size: Coord,
2312 measure: FlexboxMeasureFn<'_>,
2313) -> LayoutInfo {
2314 debug_assert_eq!(cells_h.len(), cells_v.len());
2315 debug_assert_eq!(cells_h.len(), flex_props.len());
2316 if cells_h.is_empty() {
2317 assert!(cells_v.is_empty());
2318 let orientation = match direction {
2319 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
2320 Orientation::Vertical
2321 }
2322 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
2323 Orientation::Horizontal
2324 }
2325 };
2326 let padding = match orientation {
2327 Orientation::Horizontal => padding_h,
2328 Orientation::Vertical => padding_v,
2329 };
2330 let pad = padding.begin + padding.end;
2331 return LayoutInfo { min: pad, preferred: pad, max: pad, ..Default::default() };
2332 }
2333
2334 let cross_cells = match direction {
2336 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => &cells_v,
2337 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => &cells_h,
2338 };
2339
2340 let (main_cells, main_spacing, main_padding) = match direction {
2343 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
2344 (&cells_h, spacing_h, padding_h)
2345 }
2346 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
2347 (&cells_v, spacing_v, padding_v)
2348 }
2349 };
2350 let main_extra_pad = main_padding.begin + main_padding.end;
2351 let main_axis_constraint = if constraint_size > 0 as Coord && constraint_size < Coord::MAX {
2352 constraint_size
2354 } else if matches!(flex_wrap, FlexboxLayoutWrap::NoWrap) || constraint_size >= Coord::MAX {
2355 Coord::MAX
2362 } else {
2363 let total_area: f64 = main_cells
2368 .iter()
2369 .zip(cross_cells.iter())
2370 .map(|(m, c)| {
2371 m.constraint.preferred_bounded() as f64 * c.constraint.preferred_bounded() as f64
2372 })
2373 .sum();
2374 let count = main_cells.len();
2375 Float::sqrt(total_area as f32) as Coord
2376 + main_spacing * (count - 1) as Coord
2377 + main_extra_pad
2378 };
2379
2380 let taffy_direction = match direction {
2381 FlexboxLayoutDirection::Row => flexbox_taffy::TaffyFlexDirection::Row,
2382 FlexboxLayoutDirection::RowReverse => flexbox_taffy::TaffyFlexDirection::RowReverse,
2383 FlexboxLayoutDirection::Column => flexbox_taffy::TaffyFlexDirection::Column,
2384 FlexboxLayoutDirection::ColumnReverse => flexbox_taffy::TaffyFlexDirection::ColumnReverse,
2385 };
2386
2387 let (container_width, container_height) = match direction {
2388 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
2389 (Some(main_axis_constraint), None)
2390 }
2391 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
2392 (None, Some(main_axis_constraint))
2393 }
2394 };
2395
2396 let params = |cross_axis_sizing| flexbox_taffy::FlexboxLayoutParams {
2397 cells_h: &cells_h,
2398 cells_v: &cells_v,
2399 flex_props: &flex_props,
2400 spacing_h,
2401 spacing_v,
2402 padding_h,
2403 padding_v,
2404 alignment,
2405 cross_axis_line_alignment: LayoutAlignment::Stretch,
2406 cross_axis_alignment: CrossAxisAlignment::Stretch,
2407 flex_wrap,
2408 flex_shrink: 1.,
2409 flex_direction: taffy_direction,
2410 container_width,
2411 container_height,
2412 cross_axis_sizing,
2413 };
2414
2415 let (available_width, available_height) = match direction {
2416 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
2417 (main_axis_constraint, Coord::MAX)
2418 }
2419 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
2420 (Coord::MAX, main_axis_constraint)
2421 }
2422 };
2423
2424 let cross_of = |(width, height): (Coord, Coord)| match direction {
2425 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => height,
2426 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => width,
2427 };
2428
2429 let mut builder =
2430 flexbox_taffy::FlexboxTaffyBuilder::new(params(flexbox_taffy::CrossAxisSizing::Minimum));
2431 let mut zero = zero_measure;
2433 builder.compute_layout(available_width, available_height, &mut zero);
2434 let cross_size = cross_of(builder.container_size());
2435 let preferred = {
2439 let mut builder = flexbox_taffy::FlexboxTaffyBuilder::new(params(
2440 flexbox_taffy::CrossAxisSizing::FromMeasure,
2441 ));
2442 let mut resolved = resolve_measure_defaults(&cells_h, &cells_v, measure);
2443 builder.compute_layout(available_width, available_height, &mut resolved);
2444 cross_of(builder.container_size())
2445 };
2446
2447 LayoutInfo {
2448 min: cross_size,
2449 max: Coord::MAX,
2450 min_percent: 0 as _,
2451 max_percent: 100 as _,
2452 preferred,
2453 stretch: 0.0,
2454 }
2455}
2456
2457#[cfg(feature = "ffi")]
2458pub(crate) mod ffi {
2459 #![allow(unsafe_code)]
2460
2461 use super::*;
2462
2463 #[unsafe(no_mangle)]
2464 pub extern "C" fn slint_organize_grid_layout(
2465 input_data: Slice<GridLayoutInputData>,
2466 repeater_indices: Slice<u32>,
2467 repeater_steps: Slice<u32>,
2468 result: &mut GridLayoutOrganizedData,
2469 ) {
2470 *result = super::organize_grid_layout(input_data, repeater_indices, repeater_steps);
2471 }
2472
2473 #[unsafe(no_mangle)]
2474 pub extern "C" fn slint_organize_dialog_button_layout(
2475 input_data: Slice<GridLayoutInputData>,
2476 dialog_button_roles: Slice<DialogButtonRole>,
2477 result: &mut GridLayoutOrganizedData,
2478 ) {
2479 *result = super::organize_dialog_button_layout(input_data, dialog_button_roles);
2480 }
2481
2482 #[unsafe(no_mangle)]
2483 pub extern "C" fn slint_solve_grid_layout(
2484 data: &GridLayoutData,
2485 constraints: Slice<LayoutItemInfo>,
2486 orientation: Orientation,
2487 repeater_indices: Slice<u32>,
2488 repeater_steps: Slice<u32>,
2489 result: &mut SharedVector<Coord>,
2490 ) {
2491 *result = super::solve_grid_layout(
2492 data,
2493 constraints,
2494 orientation,
2495 repeater_indices,
2496 repeater_steps,
2497 )
2498 }
2499
2500 #[unsafe(no_mangle)]
2501 pub extern "C" fn slint_grid_layout_info(
2502 organized_data: &GridLayoutOrganizedData,
2503 constraints: Slice<LayoutItemInfo>,
2504 repeater_indices: Slice<u32>,
2505 repeater_steps: Slice<u32>,
2506 spacing: Coord,
2507 padding: &Padding,
2508 orientation: Orientation,
2509 ) -> LayoutInfo {
2510 super::grid_layout_info(
2511 organized_data.clone(),
2512 constraints,
2513 repeater_indices,
2514 repeater_steps,
2515 spacing,
2516 padding,
2517 orientation,
2518 )
2519 }
2520
2521 #[unsafe(no_mangle)]
2522 pub extern "C" fn slint_solve_box_layout(
2523 data: &BoxLayoutData,
2524 repeater_indices: Slice<u32>,
2525 result: &mut SharedVector<Coord>,
2526 ) {
2527 *result = super::solve_box_layout(data, repeater_indices)
2528 }
2529
2530 #[unsafe(no_mangle)]
2531 pub extern "C" fn slint_solve_box_layout_ortho(
2532 data: &BoxLayoutOrthoData,
2533 repeater_indices: Slice<u32>,
2534 result: &mut SharedVector<Coord>,
2535 ) {
2536 *result = super::solve_box_layout_ortho(data, repeater_indices)
2537 }
2538
2539 #[unsafe(no_mangle)]
2540 pub extern "C" fn slint_box_layout_info(
2542 cells: Slice<LayoutItemInfo>,
2543 spacing: Coord,
2544 padding: &Padding,
2545 alignment: LayoutAlignment,
2546 ) -> LayoutInfo {
2547 super::box_layout_info(cells, spacing, padding, alignment)
2548 }
2549
2550 #[unsafe(no_mangle)]
2551 pub extern "C" fn slint_box_layout_info_ortho(
2553 cells: Slice<LayoutItemInfo>,
2554 padding: &Padding,
2555 ) -> LayoutInfo {
2556 super::box_layout_info_ortho(cells, padding)
2557 }
2558
2559 pub type FlexboxMeasureFnC = unsafe extern "C" fn(
2564 user_data: *mut core::ffi::c_void,
2565 child_index: usize,
2566 width: Coord,
2567 height: Coord,
2568 out_width: *mut Coord,
2569 out_height: *mut Coord,
2570 );
2571
2572 unsafe fn measure_closure_from_c(
2580 measure_fn: *const core::ffi::c_void,
2581 measure_user_data: *mut core::ffi::c_void,
2582 ) -> Option<impl FnMut(usize, Coord, Coord) -> (Coord, Coord)> {
2583 const {
2584 assert!(
2585 core::mem::size_of::<*const core::ffi::c_void>()
2586 == core::mem::size_of::<FlexboxMeasureFnC>()
2587 );
2588 }
2589 if measure_fn.is_null() {
2590 return None;
2591 }
2592 let c_measure = unsafe {
2593 core::mem::transmute::<*const core::ffi::c_void, FlexboxMeasureFnC>(measure_fn)
2594 };
2595 Some(move |child_index: usize, w: Coord, h: Coord| {
2596 let mut out_w: Coord = 0 as _;
2597 let mut out_h: Coord = 0 as _;
2598 unsafe {
2601 c_measure(measure_user_data, child_index, w, h, &mut out_w, &mut out_h);
2602 }
2603 (out_w, out_h)
2604 })
2605 }
2606
2607 #[unsafe(no_mangle)]
2608 pub extern "C" fn slint_solve_flexbox_layout(
2609 data: &FlexboxLayoutData,
2610 repeater_indices: Slice<u32>,
2611 result: &mut SharedVector<Coord>,
2612 measure_fn: *const core::ffi::c_void,
2613 measure_user_data: *mut core::ffi::c_void,
2614 ) {
2615 let measure = unsafe { measure_closure_from_c(measure_fn, measure_user_data) };
2618 if let Some(mut measure) = measure {
2619 *result = super::solve_flexbox_layout_with_measure(
2620 data,
2621 repeater_indices,
2622 Some(&mut measure),
2623 );
2624 } else {
2625 *result = super::solve_flexbox_layout(data, repeater_indices);
2626 }
2627 }
2628
2629 #[unsafe(no_mangle)]
2630 pub extern "C" fn slint_flexbox_layout_info_main_axis(
2632 cells: Slice<LayoutItemInfo>,
2633 spacing: Coord,
2634 padding: &Padding,
2635 flex_wrap: FlexboxLayoutWrap,
2636 ) -> LayoutInfo {
2637 super::flexbox_layout_info_main_axis(cells, spacing, padding, flex_wrap)
2638 }
2639
2640 #[unsafe(no_mangle)]
2641 pub extern "C" fn slint_flexbox_layout_unwrapped_main(
2643 cells: Slice<LayoutItemInfo>,
2644 spacing: Coord,
2645 padding: &Padding,
2646 ) -> Coord {
2647 super::flexbox_layout_unwrapped_main(cells, spacing, padding)
2648 }
2649
2650 #[unsafe(no_mangle)]
2651 pub extern "C" fn slint_flexbox_layout_info_cross_axis(
2653 cells_h: Slice<LayoutItemInfo>,
2654 cells_v: Slice<LayoutItemInfo>,
2655 flex_props: Slice<FlexItemProps>,
2656 spacing_h: Coord,
2657 spacing_v: Coord,
2658 padding_h: &Padding,
2659 padding_v: &Padding,
2660 direction: FlexboxLayoutDirection,
2661 alignment: LayoutAlignment,
2662 flex_wrap: FlexboxLayoutWrap,
2663 constraint_size: Coord,
2664 ) -> LayoutInfo {
2665 super::flexbox_layout_info_cross_axis(
2666 cells_h,
2667 cells_v,
2668 flex_props,
2669 spacing_h,
2670 spacing_v,
2671 padding_h,
2672 padding_v,
2673 direction,
2674 alignment,
2675 flex_wrap,
2676 constraint_size,
2677 )
2678 }
2679
2680 #[unsafe(no_mangle)]
2681 pub extern "C" fn slint_flexbox_layout_info_cross_axis_with_measure(
2684 cells_h: Slice<LayoutItemInfo>,
2685 cells_v: Slice<LayoutItemInfo>,
2686 flex_props: Slice<FlexItemProps>,
2687 spacing_h: Coord,
2688 spacing_v: Coord,
2689 padding_h: &Padding,
2690 padding_v: &Padding,
2691 direction: FlexboxLayoutDirection,
2692 alignment: LayoutAlignment,
2693 flex_wrap: FlexboxLayoutWrap,
2694 constraint_size: Coord,
2695 measure_fn: *const core::ffi::c_void,
2696 measure_user_data: *mut core::ffi::c_void,
2697 ) -> LayoutInfo {
2698 let mut measure = unsafe { measure_closure_from_c(measure_fn, measure_user_data) };
2701 super::flexbox_layout_info_cross_axis_with_measure(
2702 cells_h,
2703 cells_v,
2704 flex_props,
2705 spacing_h,
2706 spacing_v,
2707 padding_h,
2708 padding_v,
2709 direction,
2710 alignment,
2711 flex_wrap,
2712 constraint_size,
2713 measure.as_mut().map(|m| m as _),
2714 )
2715 }
2716}
2717
2718#[cfg(test)]
2719mod tests {
2720 use super::*;
2721
2722 fn collect_from_organized_data(
2723 organized_data: &GridLayoutOrganizedData,
2724 num_cells: usize,
2725 repeater_indices: Slice<u32>,
2726 repeater_steps: Slice<u32>,
2727 ) -> Vec<(u16, u16, u16, u16)> {
2728 let mut result = Vec::new();
2729 for i in 0..num_cells {
2730 let col_and_span = organized_data.col_or_row_and_span(
2731 i,
2732 Orientation::Horizontal,
2733 &repeater_indices,
2734 &repeater_steps,
2735 );
2736 let row_and_span = organized_data.col_or_row_and_span(
2737 i,
2738 Orientation::Vertical,
2739 &repeater_indices,
2740 &repeater_steps,
2741 );
2742 result.push((col_and_span.0, col_and_span.1, row_and_span.0, row_and_span.1));
2743 }
2744 result
2745 }
2746
2747 #[test]
2748 fn test_organized_data_generator_2_fixed_cells() {
2749 let mut result = GridLayoutOrganizedData::default();
2751 let num_cells = 2;
2752 let mut generator = OrganizedDataGenerator::new(&[], &[], num_cells, 0, 0, &mut result);
2753 generator.add(0, 1, 0, 1);
2754 generator.add(1, 2, 0, 3);
2755 assert_eq!(result.as_slice(), &[0, 1, 0, 1, 1, 2, 0, 3]);
2756
2757 let repeater_indices = Slice::from_slice(&[]);
2758 let empty_steps = Slice::from_slice(&[]);
2759 let collected_data =
2760 collect_from_organized_data(&result, num_cells, repeater_indices, empty_steps);
2761 assert_eq!(collected_data.as_slice(), &[(0, 1, 0, 1), (1, 2, 0, 3)]);
2762
2763 assert_eq!(
2764 result.max_value(num_cells, Orientation::Horizontal, &repeater_indices, &empty_steps),
2765 3
2766 );
2767 assert_eq!(
2768 result.max_value(num_cells, Orientation::Vertical, &repeater_indices, &empty_steps),
2769 3
2770 );
2771 }
2772
2773 #[test]
2774 fn test_organized_data_generator_1_fixed_cell_1_repeater() {
2775 let mut result = GridLayoutOrganizedData::default();
2777 let num_cells = 4;
2778 let repeater_indices = &[1u32, 3u32];
2779 let mut generator =
2780 OrganizedDataGenerator::new(repeater_indices, &[], 1, 1, 3, &mut result);
2781 generator.add(0, 1, 0, 2); generator.add(1, 2, 1, 3); generator.add(1, 1, 2, 4);
2784 generator.add(2, 2, 3, 5);
2785 assert_eq!(
2786 result.as_slice(),
2787 &[
2788 0, 1, 0, 2, 8, 4, 0, 0, 1, 2, 1, 3, 1, 1, 2, 4, 2, 2, 3, 5, ]
2794 );
2795 let repeater_indices = Slice::from_slice(repeater_indices);
2796 let empty_steps = Slice::from_slice(&[]);
2797 let collected_data =
2798 collect_from_organized_data(&result, num_cells, repeater_indices, empty_steps);
2799 assert_eq!(
2800 collected_data.as_slice(),
2801 &[(0, 1, 0, 2), (1, 2, 1, 3), (1, 1, 2, 4), (2, 2, 3, 5)]
2802 );
2803
2804 assert_eq!(
2805 result.max_value(num_cells, Orientation::Horizontal, &repeater_indices, &empty_steps),
2806 4
2807 );
2808 assert_eq!(
2809 result.max_value(num_cells, Orientation::Vertical, &repeater_indices, &empty_steps),
2810 8
2811 );
2812 }
2813
2814 #[test]
2815
2816 fn test_organize_data_with_auto_and_spans() {
2817 let auto = i_slint_common::ROW_COL_AUTO;
2818 let input = std::vec![
2819 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 2., rowspan: -1. },
2820 GridLayoutInputData { new_row: false, col: auto, row: auto, colspan: 1., rowspan: 2. },
2821 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 2., rowspan: 1. },
2822 GridLayoutInputData { new_row: true, col: -2., row: 80000., colspan: 2., rowspan: 1. },
2823 ];
2824 let repeater_indices = Slice::from_slice(&[]);
2825 let (organized_data, errors) = organize_grid_layout_impl(
2826 Slice::from_slice(&input),
2827 repeater_indices,
2828 Slice::from_slice(&[]),
2829 );
2830 assert_eq!(
2831 organized_data.as_slice(),
2832 &[
2833 0, 2, 0, 0, 2, 1, 0, 2, 0, 2, 1, 1, 0, 2, 65535, 1, ]
2838 );
2839 assert_eq!(errors.len(), 3);
2840 assert_eq!(errors[0], "cell rowspan -1 is negative, clamping to 0");
2842 assert_eq!(errors[1], "cell row 80000 is too large, clamping to 65535");
2843 assert_eq!(errors[2], "cell col -2 is negative, clamping to 0");
2844 let empty_steps = Slice::from_slice(&[]);
2845 let collected_data = collect_from_organized_data(
2846 &organized_data,
2847 input.len(),
2848 repeater_indices,
2849 empty_steps,
2850 );
2851 assert_eq!(
2852 collected_data.as_slice(),
2853 &[(0, 2, 0, 0), (2, 1, 0, 2), (0, 2, 1, 1), (0, 2, 65535, 1)]
2854 );
2855 assert_eq!(
2856 organized_data.max_value(3, Orientation::Horizontal, &repeater_indices, &empty_steps),
2857 3
2858 );
2859 assert_eq!(
2860 organized_data.max_value(3, Orientation::Vertical, &repeater_indices, &empty_steps),
2861 2
2862 );
2863 }
2864
2865 #[test]
2866 fn test_organize_data_1_empty_repeater() {
2867 let auto = i_slint_common::ROW_COL_AUTO;
2869 let cell =
2870 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. };
2871 let input = std::vec![cell];
2872 let repeater_indices = Slice::from_slice(&[1u32, 0u32]);
2873 let (organized_data, errors) = organize_grid_layout_impl(
2874 Slice::from_slice(&input),
2875 repeater_indices,
2876 Slice::from_slice(&[]),
2877 );
2878 assert_eq!(
2879 organized_data.as_slice(),
2880 &[
2881 0, 1, 0, 1, 0, 0, 0, 0
2883 ] );
2885 assert_eq!(errors.len(), 0);
2886 let empty_steps = Slice::from_slice(&[]);
2887 let collected_data = collect_from_organized_data(
2888 &organized_data,
2889 input.len(),
2890 repeater_indices,
2891 empty_steps,
2892 );
2893 assert_eq!(collected_data.as_slice(), &[(0, 1, 0, 1)]);
2894 assert_eq!(
2895 organized_data.max_value(1, Orientation::Horizontal, &repeater_indices, &empty_steps),
2896 1
2897 );
2898 }
2899
2900 #[test]
2901 fn test_organize_data_4_repeaters() {
2902 let auto = i_slint_common::ROW_COL_AUTO;
2903 let mut cell =
2904 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. };
2905 let mut input = std::vec![cell.clone()];
2906 for _ in 0..8 {
2907 cell.new_row = false;
2908 input.push(cell.clone());
2909 }
2910 let repeater_indices = Slice::from_slice(&[0u32, 0u32, 1u32, 4u32, 6u32, 2u32, 8u32, 0u32]);
2911 let (organized_data, errors) = organize_grid_layout_impl(
2912 Slice::from_slice(&input),
2913 repeater_indices,
2914 Slice::from_slice(&[]),
2915 );
2916 assert_eq!(
2917 organized_data.as_slice(),
2918 &[
2919 28, 4, 0, 0, 0, 1, 0, 1, 28, 4, 0, 0, 5, 1, 0, 1, 44, 4, 0, 0, 52, 4, 0, 0, 8, 1, 0, 1, 1, 1, 0, 1, 2, 1, 0, 1, 3, 1, 0, 1, 4, 1, 0, 1, 6, 1, 0, 1, 7, 1, 0, 1, ]
2933 );
2934 assert_eq!(errors.len(), 0);
2935 let empty_steps = Slice::from_slice(&[]);
2936 let collected_data = collect_from_organized_data(
2937 &organized_data,
2938 input.len(),
2939 repeater_indices,
2940 empty_steps,
2941 );
2942 assert_eq!(
2943 collected_data.as_slice(),
2944 &[
2945 (0, 1, 0, 1),
2946 (1, 1, 0, 1),
2947 (2, 1, 0, 1),
2948 (3, 1, 0, 1),
2949 (4, 1, 0, 1),
2950 (5, 1, 0, 1),
2951 (6, 1, 0, 1),
2952 (7, 1, 0, 1),
2953 (8, 1, 0, 1),
2954 ]
2955 );
2956 let empty_steps = Slice::from_slice(&[]);
2957 assert_eq!(
2958 organized_data.max_value(
2959 input.len(),
2960 Orientation::Horizontal,
2961 &repeater_indices,
2962 &empty_steps
2963 ),
2964 9
2965 );
2966 }
2967
2968 #[test]
2969 fn test_organize_data_repeated_rows() {
2970 let auto = i_slint_common::ROW_COL_AUTO;
2971 let mut input = Vec::new();
2972 let num_rows: u32 = 3;
2973 let num_columns: u32 = 2;
2974 for _ in 0..num_rows {
2976 let mut cell = GridLayoutInputData {
2977 new_row: true,
2978 col: auto,
2979 row: auto,
2980 colspan: 1.,
2981 rowspan: 1.,
2982 };
2983 input.push(cell.clone());
2984 cell.new_row = false;
2985 input.push(cell.clone());
2986 }
2987 let repeater_indices_arr = [0_u32, num_rows];
2989 let repeater_steps_arr = [num_columns];
2990 let repeater_steps = Slice::from_slice(&repeater_steps_arr);
2991 let repeater_indices = Slice::from_slice(&repeater_indices_arr);
2992 let (organized_data, errors) =
2993 organize_grid_layout_impl(Slice::from_slice(&input), repeater_indices, repeater_steps);
2994 assert_eq!(
2995 organized_data.as_slice(),
2996 &[
2997 4, 8, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 2, 1, 1, 1, 2, 1, ]
3002 );
3003 assert_eq!(errors.len(), 0);
3004 let collected_data = collect_from_organized_data(
3005 &organized_data,
3006 input.len(),
3007 repeater_indices,
3008 repeater_steps,
3009 );
3010 assert_eq!(
3011 collected_data.as_slice(),
3012 &[(0, 1, 0, 1), (1, 1, 0, 1), (0, 1, 1, 1), (1, 1, 1, 1), (0, 1, 2, 1), (1, 1, 2, 1),]
3014 );
3015 assert_eq!(
3016 organized_data.max_value(
3017 input.len(),
3018 Orientation::Horizontal,
3019 &repeater_indices,
3020 &repeater_steps
3021 ),
3022 2
3023 );
3024 assert_eq!(
3025 organized_data.max_value(
3026 input.len(),
3027 Orientation::Vertical,
3028 &repeater_indices,
3029 &repeater_steps
3030 ),
3031 3
3032 );
3033
3034 let mut layout_cache_v = SharedVector::<Coord>::default();
3036 let mut generator = GridLayoutCacheGenerator::new(
3037 repeater_indices.as_slice(),
3038 repeater_steps.as_slice(),
3039 0, 1, 6, &mut layout_cache_v,
3043 );
3044 generator.add(0., 50.);
3046 generator.add(0., 50.);
3047 generator.add(50., 50.);
3049 generator.add(50., 50.);
3050 generator.add(100., 50.);
3052 generator.add(100., 50.);
3053 assert_eq!(
3054 layout_cache_v.as_slice(),
3055 &[
3056 2., 4., 0., 50., 0., 50., 50., 50., 50., 50., 100., 50., 100., 50., ]
3061 );
3062
3063 let layout_cache_v_access = |jump_index: usize,
3065 repeater_index: usize,
3066 stride: usize,
3067 child_offset: usize|
3068 -> Coord {
3069 let base = layout_cache_v[jump_index] as usize;
3070 let data_idx = base + repeater_index * stride + child_offset;
3071 layout_cache_v[data_idx]
3072 };
3073 assert_eq!(layout_cache_v_access(0, 0, 4, 0), 0.);
3076 assert_eq!(layout_cache_v_access(0, 1, 4, 0), 50.);
3077 assert_eq!(layout_cache_v_access(0, 2, 4, 0), 100.);
3078 assert_eq!(layout_cache_v_access(0, 0, 4, 2), 0.);
3080 assert_eq!(layout_cache_v_access(0, 1, 4, 2), 50.);
3081 assert_eq!(layout_cache_v_access(0, 2, 4, 2), 100.);
3082 }
3083
3084 #[test]
3085 fn test_organize_data_repeated_rows_multiple_repeaters() {
3086 let auto = i_slint_common::ROW_COL_AUTO;
3087 let mut input = Vec::new();
3088 let num_rows: u32 = 5;
3089 let mut cell =
3090 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. };
3091 for _ in 0..3 {
3093 cell.new_row = true;
3094 input.push(cell.clone());
3095 cell.new_row = false;
3096 input.push(cell.clone());
3097 }
3098 for _ in 0..2 {
3100 cell.new_row = true;
3101 input.push(cell.clone());
3102 cell.new_row = false;
3103 input.push(cell.clone());
3104 cell.new_row = false;
3105 input.push(cell.clone());
3106 }
3107 let repeater_indices_arr = [0_u32, 3, 6, 2];
3110 let repeater_steps_arr = [2, 3];
3111 let repeater_steps = Slice::from_slice(&repeater_steps_arr);
3112 let repeater_indices = Slice::from_slice(&repeater_indices_arr);
3113 let (organized_data, errors) =
3114 organize_grid_layout_impl(Slice::from_slice(&input), repeater_indices, repeater_steps);
3115 assert_eq!(
3116 organized_data.as_slice(),
3117 &[
3118 8, 8, 0, 0, 32, 12, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 2, 1, 1, 1, 2, 1, 0, 1, 3, 1, 1, 1, 3, 1, 2, 1, 3, 1, 0, 1, 4, 1, 1, 1, 4, 1, 2, 1, 4, 1, ]
3128 );
3129 assert_eq!(errors.len(), 0);
3130 let collected_data = collect_from_organized_data(
3131 &organized_data,
3132 input.len(),
3133 repeater_indices,
3134 repeater_steps,
3135 );
3136 assert_eq!(
3137 collected_data.as_slice(),
3138 &[
3140 (0, 1, 0, 1),
3141 (1, 1, 0, 1),
3142 (0, 1, 1, 1),
3143 (1, 1, 1, 1),
3144 (0, 1, 2, 1),
3145 (1, 1, 2, 1),
3146 (0, 1, 3, 1),
3147 (1, 1, 3, 1),
3148 (2, 1, 3, 1),
3149 (0, 1, 4, 1),
3150 (1, 1, 4, 1),
3151 (2, 1, 4, 1)
3152 ]
3153 );
3154 assert_eq!(
3155 organized_data.max_value(
3156 input.len(),
3157 Orientation::Horizontal,
3158 &repeater_indices,
3159 &repeater_steps
3160 ),
3161 3 );
3163 assert_eq!(
3164 organized_data.max_value(
3165 input.len(),
3166 Orientation::Vertical,
3167 &repeater_indices,
3168 &repeater_steps
3169 ),
3170 num_rows as usize );
3172
3173 let mut layout_cache_v = SharedVector::<Coord>::default();
3175 let mut generator = GridLayoutCacheGenerator::new(
3176 repeater_indices.as_slice(),
3177 repeater_steps.as_slice(),
3178 0, 2, 12, &mut layout_cache_v,
3182 );
3183 generator.add(0., 50.);
3185 generator.add(0., 50.);
3186 generator.add(50., 50.);
3188 generator.add(50., 50.);
3189 generator.add(100., 50.);
3191 generator.add(100., 50.);
3192 generator.add(150., 50.);
3194 generator.add(150., 50.);
3195 generator.add(150., 50.);
3196 generator.add(200., 50.);
3198 generator.add(200., 50.);
3199 generator.add(200., 50.);
3200 assert_eq!(
3201 layout_cache_v.as_slice(),
3202 &[
3203 4., 4., 16., 6., 0., 50., 0., 50., 50., 50., 50., 50., 100., 50., 100., 50., 150., 50., 150., 50., 150., 50., 200., 50., 200., 50., 200., 50., ]
3211 );
3212
3213 let layout_cache_v_access = |jump_index: usize,
3215 repeater_index: usize,
3216 stride: usize,
3217 child_offset: usize|
3218 -> Coord {
3219 let base = layout_cache_v[jump_index] as usize;
3220 let data_idx = base + repeater_index * stride + child_offset;
3221 layout_cache_v[data_idx]
3222 };
3223 assert_eq!(layout_cache_v_access(0, 0, 4, 0), 0.);
3225 assert_eq!(layout_cache_v_access(0, 1, 4, 0), 50.);
3226 assert_eq!(layout_cache_v_access(0, 2, 4, 0), 100.);
3227 assert_eq!(layout_cache_v_access(0, 0, 4, 2), 0.);
3229 assert_eq!(layout_cache_v_access(0, 1, 4, 2), 50.);
3230 assert_eq!(layout_cache_v_access(0, 2, 4, 2), 100.);
3231 assert_eq!(layout_cache_v_access(2, 0, 6, 0), 150.);
3233 assert_eq!(layout_cache_v_access(2, 1, 6, 0), 200.);
3234 assert_eq!(layout_cache_v_access(2, 0, 6, 4), 150.);
3236 assert_eq!(layout_cache_v_access(2, 1, 6, 4), 200.);
3237 }
3238
3239 #[test]
3240 fn test_layout_cache_generator_2_fixed_cells() {
3241 let mut result = SharedVector::<Coord>::default();
3243 result.resize(2 * 2, 0 as _);
3244 let mut generator = LayoutCacheGenerator::new(&[], &mut result);
3245 generator.add(0., 50.); generator.add(80., 50.); assert_eq!(result.as_slice(), &[0., 50., 80., 50.]);
3248 }
3249
3250 #[test]
3251 fn test_layout_cache_generator_1_fixed_cell_1_repeater() {
3252 let mut result = SharedVector::<Coord>::default();
3254 let repeater_indices = &[1, 3];
3255 result.resize(4 * 2 + repeater_indices.len(), 0 as _);
3256 let mut generator = LayoutCacheGenerator::new(repeater_indices, &mut result);
3257 generator.add(0., 50.); generator.add(80., 50.); generator.add(160., 50.);
3260 generator.add(240., 50.);
3261 assert_eq!(
3262 result.as_slice(),
3263 &[
3264 0., 50., 4., 5., 80., 50., 160., 50., 240., 50. ]
3268 );
3269 }
3270
3271 #[test]
3272 fn test_layout_cache_generator_4_repeaters() {
3273 let mut result = SharedVector::<Coord>::default();
3275 let repeater_indices = &[1, 0, 1, 4, 6, 2, 8, 0];
3276 result.resize(8 * 2 + repeater_indices.len(), 0 as _);
3277 let mut generator = LayoutCacheGenerator::new(repeater_indices, &mut result);
3278 generator.add(0., 50.); generator.add(80., 10.); generator.add(160., 10.);
3281 generator.add(240., 10.);
3282 generator.add(320., 10.); generator.add(400., 80.); generator.add(500., 20.); generator.add(600., 20.); assert_eq!(
3287 result.as_slice(),
3288 &[
3289 0., 50., 12., 13., 12., 13., 400., 80., 20., 21., 0., 0., 80., 10., 160., 10., 240., 10., 320., 10., 500., 20., 600., 20. ]
3298 );
3299 }
3300
3301 mod max_content_matches_taffy {
3311 use super::*;
3312
3313 fn cell(preferred: Coord, stretch: f32) -> FlexboxLayoutItemInfo {
3314 FlexboxLayoutItemInfo {
3315 constraint: LayoutInfo { preferred, stretch, ..Default::default() },
3316 ..Default::default()
3317 }
3318 }
3319
3320 fn constraints(cells: &[FlexboxLayoutItemInfo]) -> Vec<LayoutItemInfo> {
3323 cells
3324 .iter()
3325 .map(|c| LayoutItemInfo { constraint: c.constraint.clone(), ..Default::default() })
3326 .collect()
3327 }
3328
3329 fn split(cells: &[FlexboxLayoutItemInfo]) -> (Vec<LayoutItemInfo>, Vec<FlexItemProps>) {
3332 (constraints(cells), cells.iter().map(|c| c.props).collect())
3333 }
3334
3335 fn taffy_max_content_main(cells: &[FlexboxLayoutItemInfo]) -> Coord {
3337 let (main, flex) = split(cells);
3338 let cross: Vec<LayoutItemInfo> = cells
3341 .iter()
3342 .map(|_| LayoutItemInfo {
3343 constraint: LayoutInfo { max: Coord::MAX, ..Default::default() },
3344 ..Default::default()
3345 })
3346 .collect();
3347 let cells_h = Slice::from_slice(&main);
3348 let cells_v = Slice::from_slice(&cross);
3349 let flex_props = Slice::from_slice(&flex);
3350 let pad = Padding::default();
3351 let mut builder =
3352 flexbox_taffy::FlexboxTaffyBuilder::new(flexbox_taffy::FlexboxLayoutParams {
3353 cells_h: &cells_h,
3354 cells_v: &cells_v,
3355 flex_props: &flex_props,
3356 spacing_h: 0 as Coord,
3357 spacing_v: 0 as Coord,
3358 padding_h: &pad,
3359 padding_v: &pad,
3360 alignment: LayoutAlignment::Stretch,
3363 cross_axis_line_alignment: LayoutAlignment::Stretch,
3364 cross_axis_alignment: CrossAxisAlignment::Stretch,
3365 flex_wrap: FlexboxLayoutWrap::NoWrap,
3366 flex_shrink: 1.,
3367 flex_direction: flexbox_taffy::TaffyFlexDirection::Row,
3368 container_width: None,
3369 container_height: None,
3370 cross_axis_sizing: flexbox_taffy::CrossAxisSizing::Preferred,
3371 });
3372 let mut measure = |idx: usize, known_w: Option<Coord>, known_h: Option<Coord>| {
3376 (
3377 known_w.unwrap_or_else(|| cells[idx].constraint.preferred_bounded()),
3378 known_h.unwrap_or(0 as Coord),
3379 )
3380 };
3381 builder.compute_layout(Coord::MAX, Coord::MAX, &mut measure);
3382 builder.container_size().0
3383 }
3384
3385 fn ours(cells: &[FlexboxLayoutItemInfo]) -> Coord {
3386 let main = constraints(cells);
3387 flexbox_layout_unwrapped_main(Slice::from_slice(&main), 0 as Coord, &Padding::default())
3388 }
3389
3390 #[track_caller]
3391 fn assert_agrees(name: &str, cells: &[FlexboxLayoutItemInfo]) {
3392 let (ours, theirs) = (ours(cells), taffy_max_content_main(cells));
3393 assert!((ours - theirs).abs() <= 1 as Coord, "{name}: ours={ours} taffy={theirs}");
3394 }
3395
3396 #[test]
3400 fn agrees() {
3401 assert_agrees("plain", &[cell(50., 0.), cell(250., 0.)]);
3402 assert_agrees("equal stretch", &[cell(50., 1.), cell(250., 1.)]);
3403 assert_agrees("uneven stretch", &[cell(60., 1.), cell(60., 3.)]);
3404 assert_agrees("fractional stretch", &[cell(60., 0.5), cell(40., 1.5)]);
3405 assert_agrees("mixed stretch and not", &[cell(50., 1.), cell(100., 0.)]);
3406 assert_agrees("zero preferred, stretchy", &[cell(0., 1.), cell(0., 1.)]);
3409 }
3410
3411 fn taffy_column_main(cells: &[FlexboxLayoutItemInfo]) -> Coord {
3414 let (main, flex) = split(cells);
3415 let h: Vec<LayoutItemInfo> = cells
3418 .iter()
3419 .map(|_| LayoutItemInfo {
3420 constraint: LayoutInfo { max: Coord::MAX, ..Default::default() },
3421 ..Default::default()
3422 })
3423 .collect();
3424 let cells_h = Slice::from_slice(&h);
3425 let cells_v = Slice::from_slice(&main);
3426 let flex_props = Slice::from_slice(&flex);
3427 let pad = Padding::default();
3428 let mut builder =
3429 flexbox_taffy::FlexboxTaffyBuilder::new(flexbox_taffy::FlexboxLayoutParams {
3430 cells_h: &cells_h,
3431 cells_v: &cells_v,
3432 flex_props: &flex_props,
3433 spacing_h: 0 as Coord,
3434 spacing_v: 0 as Coord,
3435 padding_h: &pad,
3436 padding_v: &pad,
3437 alignment: LayoutAlignment::Stretch,
3438 cross_axis_line_alignment: LayoutAlignment::Stretch,
3439 cross_axis_alignment: CrossAxisAlignment::Stretch,
3440 flex_wrap: FlexboxLayoutWrap::NoWrap,
3441 flex_shrink: 1.,
3442 flex_direction: flexbox_taffy::TaffyFlexDirection::Column,
3443 container_width: None,
3444 container_height: None,
3445 cross_axis_sizing: flexbox_taffy::CrossAxisSizing::Preferred,
3446 });
3447 let mut measure = |idx: usize, known_w: Option<Coord>, known_h: Option<Coord>| {
3448 (
3449 known_w.unwrap_or(0 as Coord),
3450 known_h.unwrap_or_else(|| cells[idx].constraint.preferred_bounded()),
3451 )
3452 };
3453 builder.compute_layout(Coord::MAX, Coord::MAX, &mut measure);
3454 builder.container_size().1
3455 }
3456
3457 #[track_caller]
3458 fn assert_agrees_column(name: &str, cells: &[FlexboxLayoutItemInfo]) {
3459 let (o, t) = (ours(cells), taffy_column_main(cells));
3460 assert!((o - t).abs() <= 1 as Coord, "{name}: ours={o} taffy={t}");
3461 }
3462
3463 #[test]
3467 fn agrees_for_a_column() {
3468 assert_agrees_column("plain", &[cell(50., 0.), cell(250., 0.)]);
3469 assert_agrees_column("equal stretch", &[cell(50., 1.), cell(250., 1.)]);
3470 assert_agrees_column("zero preferred, stretchy", &[cell(0., 1.), cell(0., 1.)]);
3471 }
3472 }
3473
3474 mod stretch_driven_grow {
3479 use super::*;
3480
3481 fn info(preferred: Coord, stretch: f32) -> LayoutInfo {
3482 LayoutInfo { preferred, stretch, ..Default::default() }
3483 }
3484
3485 fn solve_row(alignment: LayoutAlignment, constraints: &[LayoutInfo]) -> Vec<Coord> {
3488 let cells_h: Vec<LayoutItemInfo> = constraints
3489 .iter()
3490 .map(|c| LayoutItemInfo { constraint: c.clone(), ..Default::default() })
3491 .collect();
3492 let cells_v: Vec<LayoutItemInfo> =
3493 constraints.iter().map(|_| LayoutItemInfo::default()).collect();
3494 let flex_props: Vec<FlexItemProps> =
3495 constraints.iter().map(|_| FlexItemProps::default()).collect();
3496 let pad = Padding::default();
3497 let mut builder =
3498 flexbox_taffy::FlexboxTaffyBuilder::new(flexbox_taffy::FlexboxLayoutParams {
3499 cells_h: &Slice::from_slice(&cells_h),
3500 cells_v: &Slice::from_slice(&cells_v),
3501 flex_props: &Slice::from_slice(&flex_props),
3502 spacing_h: 0 as Coord,
3503 spacing_v: 0 as Coord,
3504 padding_h: &pad,
3505 padding_v: &pad,
3506 alignment,
3507 cross_axis_line_alignment: LayoutAlignment::Stretch,
3508 cross_axis_alignment: CrossAxisAlignment::Stretch,
3509 flex_wrap: FlexboxLayoutWrap::Wrap,
3510 flex_shrink: 1.,
3511 flex_direction: flexbox_taffy::TaffyFlexDirection::Row,
3512 container_width: Some(400 as Coord),
3513 container_height: None,
3514 cross_axis_sizing: flexbox_taffy::CrossAxisSizing::Preferred,
3515 });
3516 builder.compute_layout(400 as Coord, Coord::MAX, &mut zero_measure);
3517 (0..constraints.len()).map(|i| builder.child_geometry(i).2).collect()
3518 }
3519
3520 #[test]
3521 fn grows_by_stretch_only_under_alignment_stretch() {
3522 let cells = [info(100., 3.), info(100., 1.)];
3524 assert_eq!(solve_row(LayoutAlignment::Stretch, &cells), [250., 150.]);
3525 let cells = [info(100., 1.), info(100., 0.)];
3527 assert_eq!(solve_row(LayoutAlignment::Stretch, &cells), [300., 100.]);
3528 let cells = [info(100., 0.), info(100., 0.)];
3530 assert_eq!(solve_row(LayoutAlignment::Stretch, &cells), [200., 200.]);
3531 let cells = [info(100., 3.), info(100., 1.)];
3533 assert_eq!(solve_row(LayoutAlignment::Start, &cells), [100., 100.]);
3534 }
3535
3536 #[test]
3540 fn lone_oversized_item_shrinks_regardless_of_stretch() {
3541 let squeezable =
3542 LayoutInfo { preferred: 500., min: 200., stretch: 0., ..Default::default() };
3543 assert_eq!(solve_row(LayoutAlignment::Start, &[squeezable]), [400.]);
3544 let rigid =
3545 LayoutInfo { preferred: 500., min: 450., stretch: 0., ..Default::default() };
3546 assert_eq!(solve_row(LayoutAlignment::Start, &[rigid]), [450.]);
3547 }
3548 }
3549
3550 #[test]
3561 fn percentage_against_unbounded_container_leaves_item_finite() {
3562 let cells_h = [LayoutItemInfo {
3564 constraint: LayoutInfo {
3565 min_percent: 50 as Coord,
3566 max_percent: 50 as Coord,
3567 max: Coord::MAX,
3568 ..Default::default()
3569 },
3570 ..Default::default()
3571 }];
3572 let cells_v = [LayoutItemInfo {
3573 constraint: LayoutInfo {
3574 preferred: 30 as Coord,
3575 max: Coord::MAX,
3576 ..Default::default()
3577 },
3578 ..Default::default()
3579 }];
3580 let flex_props = [FlexItemProps::default()];
3581 let pad = Padding::default();
3582 let mut builder =
3583 flexbox_taffy::FlexboxTaffyBuilder::new(flexbox_taffy::FlexboxLayoutParams {
3584 cells_h: &Slice::from_slice(&cells_h),
3585 cells_v: &Slice::from_slice(&cells_v),
3586 flex_props: &Slice::from_slice(&flex_props),
3587 spacing_h: 0 as Coord,
3588 spacing_v: 0 as Coord,
3589 padding_h: &pad,
3590 padding_v: &pad,
3591 alignment: LayoutAlignment::Start,
3592 cross_axis_line_alignment: LayoutAlignment::Stretch,
3593 cross_axis_alignment: CrossAxisAlignment::Stretch,
3594 flex_wrap: FlexboxLayoutWrap::NoWrap,
3595 flex_shrink: 1.,
3596 flex_direction: flexbox_taffy::TaffyFlexDirection::Row,
3597 container_width: Some(Coord::MAX),
3599 container_height: None,
3600 cross_axis_sizing: flexbox_taffy::CrossAxisSizing::Preferred,
3601 });
3602 builder.compute_layout(Coord::MAX, Coord::MAX, &mut zero_measure);
3603 let (_x, _y, w, _h) = builder.child_geometry(0);
3604 assert!(w.is_finite() && w < Coord::MAX, "item main-axis size was {w}");
3607 }
3608
3609 fn row_layout_data(constraints: &[LayoutInfo]) -> grid_internal::LayoutData {
3614 let mut organized_data = GridLayoutOrganizedData::default();
3615 let mut generator =
3616 OrganizedDataGenerator::new(&[], &[], constraints.len(), 0, 0, &mut organized_data);
3617 for col in 0..constraints.len() {
3618 generator.add(col as u16, 1, 0, 1);
3619 }
3620 let items: Vec<LayoutItemInfo> = constraints
3621 .iter()
3622 .map(|constraint| LayoutItemInfo { constraint: *constraint, ..Default::default() })
3623 .collect();
3624 let mut layout_data = grid_internal::to_layout_data(
3625 &organized_data,
3626 Slice::from_slice(&items),
3627 Orientation::Vertical,
3628 Slice::from_slice(&[]),
3629 Slice::from_slice(&[]),
3630 0 as _,
3631 None,
3632 );
3633 assert_eq!(layout_data.len(), 1);
3634 layout_data.remove(0)
3635 }
3636
3637 #[test]
3638 fn test_grid_row_collapsed_cell_raises_max_and_pulls_pref_down_with_it() {
3639 let row = row_layout_data(&[
3643 LayoutInfo { min: 0 as _, max: 0 as _, preferred: 0 as _, ..Default::default() },
3644 LayoutInfo { min: 5 as _, preferred: 50 as _, ..Default::default() },
3645 ]);
3646 assert_eq!(row.min, 5 as Coord);
3652 assert_eq!(row.max, 5 as Coord);
3653 assert_eq!(row.pref, 5 as Coord);
3654 }
3655
3656 #[test]
3657 fn test_grid_row_without_conflicting_constraints_keeps_its_own_pref() {
3658 let row = row_layout_data(&[
3665 LayoutInfo { min: 0 as _, max: 10 as _, preferred: 5 as _, ..Default::default() },
3666 LayoutInfo { min: 0 as _, preferred: 50 as _, ..Default::default() },
3667 ]);
3668 assert_eq!(row.min, 0 as Coord);
3669 assert_eq!(row.max, 10 as Coord);
3670 assert_eq!(row.pref, 50 as Coord);
3671 }
3672}