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> =
1941 Option<&'a mut dyn FnMut(usize, Coord, Coord, bool, bool) -> (Coord, Coord)>;
1942
1943fn identity_measure(_: usize, w: Coord, h: Coord, _: bool, _: bool) -> (Coord, Coord) {
1947 (w, h)
1948}
1949
1950fn zero_measure(_: usize, _: Option<Coord>, _: Option<Coord>) -> (Coord, Coord) {
1956 (0 as Coord, 0 as Coord)
1957}
1958
1959fn resolve_measure_defaults<'a>(
1963 cells_h: &'a [LayoutItemInfo],
1964 cells_v: &'a [LayoutItemInfo],
1965 measure: &'a mut dyn FnMut(usize, Coord, Coord, bool, bool) -> (Coord, Coord),
1966) -> impl FnMut(usize, Option<Coord>, Option<Coord>) -> (Coord, Coord) + 'a {
1967 move |index, known_w, known_h| {
1968 let w = known_w.unwrap_or_else(|| {
1969 cells_h.get(index).map_or(0 as Coord, |c| c.constraint.preferred_bounded())
1970 });
1971 let h = known_h.unwrap_or_else(|| {
1972 cells_v.get(index).map_or(0 as Coord, |c| c.constraint.preferred_bounded())
1973 });
1974 measure(index, w, h, known_w.is_some(), known_h.is_some())
1975 }
1976}
1977
1978pub fn solve_flexbox_layout(
1979 data: &FlexboxLayoutData,
1980 repeater_indices: Slice<u32>,
1981) -> SharedVector<Coord> {
1982 solve_flexbox_layout_with_measure(data, repeater_indices, None)
1983}
1984
1985pub fn solve_flexbox_layout_with_measure(
1988 data: &FlexboxLayoutData,
1989 repeater_indices: Slice<u32>,
1990 measure: FlexboxMeasureFn<'_>,
1991) -> SharedVector<Coord> {
1992 let mut result = SharedVector::<Coord>::default();
1994 result.resize(data.cells_h.len() * 4 + repeater_indices.len() * 2, 0 as _);
1995
1996 if data.cells_h.is_empty() {
1997 return result;
1998 }
1999
2000 let taffy_direction = match data.direction {
2001 FlexboxLayoutDirection::Row => flexbox_taffy::TaffyFlexDirection::Row,
2002 FlexboxLayoutDirection::RowReverse => flexbox_taffy::TaffyFlexDirection::RowReverse,
2003 FlexboxLayoutDirection::Column => flexbox_taffy::TaffyFlexDirection::Column,
2004 FlexboxLayoutDirection::ColumnReverse => flexbox_taffy::TaffyFlexDirection::ColumnReverse,
2005 };
2006
2007 let (container_width, container_height) = (
2008 if data.width > 0 as Coord { Some(data.width) } else { None },
2009 if data.height > 0 as Coord { Some(data.height) } else { None },
2010 );
2011
2012 let use_measure = measure.is_some();
2013 let build = |flex_wrap, flex_shrink| {
2014 flexbox_taffy::FlexboxTaffyBuilder::new(flexbox_taffy::FlexboxLayoutParams {
2015 cells_h: &data.cells_h,
2016 cells_v: &data.cells_v,
2017 flex_props: &data.flex_props,
2018 spacing_h: data.spacing_h,
2019 spacing_v: data.spacing_v,
2020 padding_h: &data.padding_h,
2021 padding_v: &data.padding_v,
2022 alignment: data.alignment,
2023 cross_axis_line_alignment: data.cross_axis_line_alignment,
2024 cross_axis_alignment: data.cross_axis_alignment,
2025 flex_wrap,
2026 flex_shrink,
2027 flex_direction: taffy_direction,
2028 container_width,
2029 container_height,
2030 cross_axis_sizing: if use_measure {
2031 flexbox_taffy::CrossAxisSizing::FromMeasure
2032 } else {
2033 flexbox_taffy::CrossAxisSizing::Preferred
2034 },
2035 })
2036 };
2037 let mut builder = build(data.flex_wrap, 1.);
2038
2039 let (available_width, available_height) = match data.direction {
2040 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
2041 (data.width, Coord::MAX)
2042 }
2043 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
2044 (Coord::MAX, data.height)
2045 }
2046 };
2047
2048 let mut identity = identity_measure;
2052 let measure = measure.unwrap_or(&mut identity);
2053 let mut measure = resolve_measure_defaults(&data.cells_h, &data.cells_v, measure);
2054 builder.compute_layout(available_width, available_height, &mut measure);
2055
2056 let is_column = matches!(
2068 data.direction,
2069 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse
2070 );
2071 if is_column && data.flex_wrap == FlexboxLayoutWrap::Wrap && data.width > 0 as Coord {
2072 #[cfg(not(slint_int_coord))]
2075 const OVERFLOW_TOLERANCE: Coord = 0.5;
2076 #[cfg(slint_int_coord)]
2077 const OVERFLOW_TOLERANCE: Coord = 0;
2078 let (left, right) = (data.padding_h.begin, data.width - data.padding_h.end);
2079 let overflows = (0..data.cells_h.len()).any(|idx| {
2082 let (x, _, w, _) = builder.child_geometry(idx);
2083 x < left - OVERFLOW_TOLERANCE || x + w > right + OVERFLOW_TOLERANCE
2084 });
2085 if overflows {
2086 let one_column = data.cells_h.len() < 2
2108 || flexbox_layout_unwrapped_main(
2109 Slice::from_slice(data.cells_v.as_slice()),
2110 data.spacing_v,
2111 &data.padding_v,
2112 ) <= data.height;
2113 builder = build(FlexboxLayoutWrap::NoWrap, if one_column { 1. } else { 0. });
2114 builder.compute_layout(available_width, available_height, &mut measure);
2115 }
2116 }
2117
2118 if builder.order_map.is_empty() {
2122 let mut generator = FlexboxLayoutCacheGenerator::new(&repeater_indices, &mut result);
2123 for idx in 0..data.cells_h.len() {
2124 let (x, y, w, h) = builder.child_geometry(idx);
2125 generator.add(x, y, w, h);
2126 }
2127 } else {
2128 let count = data.cells_h.len();
2129 let mut geom = alloc::vec![(0 as Coord, 0 as Coord, 0 as Coord, 0 as Coord); count];
2130 for taffy_idx in 0..count {
2131 let orig_idx = builder.original_index(taffy_idx);
2132 geom[orig_idx] = builder.child_geometry(taffy_idx);
2133 }
2134 let mut generator = FlexboxLayoutCacheGenerator::new(&repeater_indices, &mut result);
2135 for (x, y, w, h) in geom {
2136 generator.add(x, y, w, h);
2137 }
2138 }
2139
2140 result
2141}
2142
2143pub fn flexbox_layout_unwrapped_main(
2150 cells: Slice<LayoutItemInfo>,
2151 spacing: Coord,
2152 padding: &Padding,
2153) -> Coord {
2154 let extra_pad = padding.begin + padding.end;
2155 if cells.is_empty() {
2156 return extra_pad;
2157 }
2158 let num_spacings = cells.len().saturating_sub(1) as Coord;
2159 cells.iter().map(|c| c.constraint.preferred_bounded()).sum::<Coord>()
2160 + spacing * num_spacings
2161 + extra_pad
2162}
2163
2164pub fn flexbox_layout_info_main_axis(
2169 cells: Slice<LayoutItemInfo>,
2170 spacing: Coord,
2171 padding: &Padding,
2172 flex_wrap: FlexboxLayoutWrap,
2173) -> LayoutInfo {
2174 let extra_pad = padding.begin + padding.end;
2175 if cells.is_empty() {
2176 return LayoutInfo {
2177 min: extra_pad,
2178 preferred: extra_pad,
2179 max: extra_pad,
2180 ..Default::default()
2181 };
2182 }
2183 let num_spacings = cells.len().saturating_sub(1) as Coord;
2184 let min = if matches!(flex_wrap, FlexboxLayoutWrap::NoWrap) {
2185 cells.iter().map(|c| c.constraint.min).sum::<Coord>() + spacing * num_spacings + extra_pad
2186 } else {
2187 cells.iter().map(|c| c.constraint.min).fold(0.0 as Coord, |a, b| a.max(b)) + extra_pad
2189 };
2190 let preferred = if matches!(flex_wrap, FlexboxLayoutWrap::NoWrap) {
2191 flexbox_layout_unwrapped_main(cells, spacing, padding)
2193 } else {
2194 let total_area: f64 = cells
2206 .iter()
2207 .map(|c| c.constraint.preferred_bounded() as f64 + spacing as f64)
2208 .map(|w| w * w)
2209 .sum();
2210 let target = Float::sqrt(total_area as f32) as Coord;
2211 let mut acc = 0 as Coord;
2212 let mut started = false;
2213 for c in cells.iter() {
2214 let size = c.constraint.preferred_bounded();
2218 acc += if started { spacing + size } else { size };
2219 started = true;
2220 if acc + spacing >= target {
2224 break;
2225 }
2226 }
2227 acc + extra_pad
2228 };
2229 let stretch = cells.iter().map(|c| c.constraint.stretch).sum::<f32>();
2230 LayoutInfo {
2231 min,
2232 max: Coord::MAX,
2233 min_percent: 0 as _,
2234 max_percent: 100 as _,
2235 preferred,
2236 stretch,
2237 }
2238}
2239
2240#[allow(clippy::too_many_arguments)]
2253pub fn flexbox_layout_info_cross_axis(
2254 cells_h: Slice<LayoutItemInfo>,
2255 cells_v: Slice<LayoutItemInfo>,
2256 flex_props: Slice<FlexItemProps>,
2257 spacing_h: Coord,
2258 spacing_v: Coord,
2259 padding_h: &Padding,
2260 padding_v: &Padding,
2261 direction: FlexboxLayoutDirection,
2262 alignment: LayoutAlignment,
2263 flex_wrap: FlexboxLayoutWrap,
2264 constraint_size: Coord,
2265) -> LayoutInfo {
2266 flexbox_layout_info_cross_axis_with_measure(
2267 cells_h,
2268 cells_v,
2269 flex_props,
2270 spacing_h,
2271 spacing_v,
2272 padding_h,
2273 padding_v,
2274 direction,
2275 alignment,
2276 flex_wrap,
2277 constraint_size,
2278 None,
2279 )
2280}
2281
2282#[allow(clippy::too_many_arguments)]
2291pub fn flexbox_layout_info_cross_axis_with_measure(
2292 cells_h: Slice<LayoutItemInfo>,
2293 cells_v: Slice<LayoutItemInfo>,
2294 flex_props: Slice<FlexItemProps>,
2295 spacing_h: Coord,
2296 spacing_v: Coord,
2297 padding_h: &Padding,
2298 padding_v: &Padding,
2299 direction: FlexboxLayoutDirection,
2300 alignment: LayoutAlignment,
2301 flex_wrap: FlexboxLayoutWrap,
2302 constraint_size: Coord,
2303 measure: FlexboxMeasureFn<'_>,
2304) -> LayoutInfo {
2305 debug_assert_eq!(cells_h.len(), cells_v.len());
2306 debug_assert_eq!(cells_h.len(), flex_props.len());
2307 if cells_h.is_empty() {
2308 assert!(cells_v.is_empty());
2309 let orientation = match direction {
2310 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
2311 Orientation::Vertical
2312 }
2313 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
2314 Orientation::Horizontal
2315 }
2316 };
2317 let padding = match orientation {
2318 Orientation::Horizontal => padding_h,
2319 Orientation::Vertical => padding_v,
2320 };
2321 let pad = padding.begin + padding.end;
2322 return LayoutInfo { min: pad, preferred: pad, max: pad, ..Default::default() };
2323 }
2324
2325 let cross_cells = match direction {
2327 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => &cells_v,
2328 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => &cells_h,
2329 };
2330
2331 let (main_cells, main_spacing, main_padding) = match direction {
2334 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
2335 (&cells_h, spacing_h, padding_h)
2336 }
2337 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
2338 (&cells_v, spacing_v, padding_v)
2339 }
2340 };
2341 let main_extra_pad = main_padding.begin + main_padding.end;
2342 let main_axis_constraint = if constraint_size > 0 as Coord && constraint_size < Coord::MAX {
2343 constraint_size
2345 } else if matches!(flex_wrap, FlexboxLayoutWrap::NoWrap) || constraint_size >= Coord::MAX {
2346 Coord::MAX
2353 } else {
2354 let total_area: f64 = main_cells
2359 .iter()
2360 .zip(cross_cells.iter())
2361 .map(|(m, c)| {
2362 m.constraint.preferred_bounded() as f64 * c.constraint.preferred_bounded() as f64
2363 })
2364 .sum();
2365 let count = main_cells.len();
2366 Float::sqrt(total_area as f32) as Coord
2367 + main_spacing * (count - 1) as Coord
2368 + main_extra_pad
2369 };
2370
2371 let taffy_direction = match direction {
2372 FlexboxLayoutDirection::Row => flexbox_taffy::TaffyFlexDirection::Row,
2373 FlexboxLayoutDirection::RowReverse => flexbox_taffy::TaffyFlexDirection::RowReverse,
2374 FlexboxLayoutDirection::Column => flexbox_taffy::TaffyFlexDirection::Column,
2375 FlexboxLayoutDirection::ColumnReverse => flexbox_taffy::TaffyFlexDirection::ColumnReverse,
2376 };
2377
2378 let (container_width, container_height) = match direction {
2379 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
2380 (Some(main_axis_constraint), None)
2381 }
2382 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
2383 (None, Some(main_axis_constraint))
2384 }
2385 };
2386
2387 let params = |cross_axis_sizing| flexbox_taffy::FlexboxLayoutParams {
2388 cells_h: &cells_h,
2389 cells_v: &cells_v,
2390 flex_props: &flex_props,
2391 spacing_h,
2392 spacing_v,
2393 padding_h,
2394 padding_v,
2395 alignment,
2396 cross_axis_line_alignment: LayoutAlignment::Stretch,
2397 cross_axis_alignment: CrossAxisAlignment::Stretch,
2398 flex_wrap,
2399 flex_shrink: 1.,
2400 flex_direction: taffy_direction,
2401 container_width,
2402 container_height,
2403 cross_axis_sizing,
2404 };
2405
2406 let (available_width, available_height) = match direction {
2407 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
2408 (main_axis_constraint, Coord::MAX)
2409 }
2410 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
2411 (Coord::MAX, main_axis_constraint)
2412 }
2413 };
2414
2415 let cross_of = |(width, height): (Coord, Coord)| match direction {
2416 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => height,
2417 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => width,
2418 };
2419
2420 let mut builder =
2421 flexbox_taffy::FlexboxTaffyBuilder::new(params(flexbox_taffy::CrossAxisSizing::Minimum));
2422 let mut zero = zero_measure;
2424 builder.compute_layout(available_width, available_height, &mut zero);
2425 let cross_size = cross_of(builder.container_size());
2426 let mut resolved = measure.map(|m| resolve_measure_defaults(&cells_h, &cells_v, m));
2427
2428 let preferred = {
2433 let mut builder = flexbox_taffy::FlexboxTaffyBuilder::new(params(
2434 flexbox_taffy::CrossAxisSizing::FromMeasure,
2435 ));
2436 let mut identity = identity_measure;
2437 let mut fallback = resolve_measure_defaults(&cells_h, &cells_v, &mut identity);
2438 builder.compute_layout(
2439 available_width,
2440 available_height,
2441 match resolved.as_mut() {
2442 Some(m) => m,
2443 None => &mut fallback,
2444 },
2445 );
2446 cross_of(builder.container_size())
2447 };
2448
2449 LayoutInfo {
2450 min: cross_size,
2451 max: Coord::MAX,
2452 min_percent: 0 as _,
2453 max_percent: 100 as _,
2454 preferred,
2455 stretch: 0.0,
2456 }
2457}
2458
2459#[cfg(feature = "ffi")]
2460pub(crate) mod ffi {
2461 #![allow(unsafe_code)]
2462
2463 use super::*;
2464
2465 #[unsafe(no_mangle)]
2466 pub extern "C" fn slint_organize_grid_layout(
2467 input_data: Slice<GridLayoutInputData>,
2468 repeater_indices: Slice<u32>,
2469 repeater_steps: Slice<u32>,
2470 result: &mut GridLayoutOrganizedData,
2471 ) {
2472 *result = super::organize_grid_layout(input_data, repeater_indices, repeater_steps);
2473 }
2474
2475 #[unsafe(no_mangle)]
2476 pub extern "C" fn slint_organize_dialog_button_layout(
2477 input_data: Slice<GridLayoutInputData>,
2478 dialog_button_roles: Slice<DialogButtonRole>,
2479 result: &mut GridLayoutOrganizedData,
2480 ) {
2481 *result = super::organize_dialog_button_layout(input_data, dialog_button_roles);
2482 }
2483
2484 #[unsafe(no_mangle)]
2485 pub extern "C" fn slint_solve_grid_layout(
2486 data: &GridLayoutData,
2487 constraints: Slice<LayoutItemInfo>,
2488 orientation: Orientation,
2489 repeater_indices: Slice<u32>,
2490 repeater_steps: Slice<u32>,
2491 result: &mut SharedVector<Coord>,
2492 ) {
2493 *result = super::solve_grid_layout(
2494 data,
2495 constraints,
2496 orientation,
2497 repeater_indices,
2498 repeater_steps,
2499 )
2500 }
2501
2502 #[unsafe(no_mangle)]
2503 pub extern "C" fn slint_grid_layout_info(
2504 organized_data: &GridLayoutOrganizedData,
2505 constraints: Slice<LayoutItemInfo>,
2506 repeater_indices: Slice<u32>,
2507 repeater_steps: Slice<u32>,
2508 spacing: Coord,
2509 padding: &Padding,
2510 orientation: Orientation,
2511 ) -> LayoutInfo {
2512 super::grid_layout_info(
2513 organized_data.clone(),
2514 constraints,
2515 repeater_indices,
2516 repeater_steps,
2517 spacing,
2518 padding,
2519 orientation,
2520 )
2521 }
2522
2523 #[unsafe(no_mangle)]
2524 pub extern "C" fn slint_solve_box_layout(
2525 data: &BoxLayoutData,
2526 repeater_indices: Slice<u32>,
2527 result: &mut SharedVector<Coord>,
2528 ) {
2529 *result = super::solve_box_layout(data, repeater_indices)
2530 }
2531
2532 #[unsafe(no_mangle)]
2533 pub extern "C" fn slint_solve_box_layout_ortho(
2534 data: &BoxLayoutOrthoData,
2535 repeater_indices: Slice<u32>,
2536 result: &mut SharedVector<Coord>,
2537 ) {
2538 *result = super::solve_box_layout_ortho(data, repeater_indices)
2539 }
2540
2541 #[unsafe(no_mangle)]
2542 pub extern "C" fn slint_box_layout_info(
2544 cells: Slice<LayoutItemInfo>,
2545 spacing: Coord,
2546 padding: &Padding,
2547 alignment: LayoutAlignment,
2548 ) -> LayoutInfo {
2549 super::box_layout_info(cells, spacing, padding, alignment)
2550 }
2551
2552 #[unsafe(no_mangle)]
2553 pub extern "C" fn slint_box_layout_info_ortho(
2555 cells: Slice<LayoutItemInfo>,
2556 padding: &Padding,
2557 ) -> LayoutInfo {
2558 super::box_layout_info_ortho(cells, padding)
2559 }
2560
2561 pub type FlexboxMeasureFnC = unsafe extern "C" fn(
2566 user_data: *mut core::ffi::c_void,
2567 child_index: usize,
2568 width: Coord,
2569 height: Coord,
2570 known_width: bool,
2571 known_height: bool,
2572 out_width: *mut Coord,
2573 out_height: *mut Coord,
2574 );
2575
2576 unsafe fn measure_closure_from_c(
2584 measure_fn: *const core::ffi::c_void,
2585 measure_user_data: *mut core::ffi::c_void,
2586 ) -> Option<impl FnMut(usize, Coord, Coord, bool, bool) -> (Coord, Coord)> {
2587 const {
2588 assert!(
2589 core::mem::size_of::<*const core::ffi::c_void>()
2590 == core::mem::size_of::<FlexboxMeasureFnC>()
2591 );
2592 }
2593 if measure_fn.is_null() {
2594 return None;
2595 }
2596 let c_measure = unsafe {
2597 core::mem::transmute::<*const core::ffi::c_void, FlexboxMeasureFnC>(measure_fn)
2598 };
2599 Some(move |child_index: usize, w: Coord, h: Coord, known_w: bool, known_h: bool| {
2600 let mut out_w: Coord = 0 as _;
2601 let mut out_h: Coord = 0 as _;
2602 unsafe {
2605 c_measure(
2606 measure_user_data,
2607 child_index,
2608 w,
2609 h,
2610 known_w,
2611 known_h,
2612 &mut out_w,
2613 &mut out_h,
2614 );
2615 }
2616 (out_w, out_h)
2617 })
2618 }
2619
2620 #[unsafe(no_mangle)]
2621 pub extern "C" fn slint_solve_flexbox_layout(
2622 data: &FlexboxLayoutData,
2623 repeater_indices: Slice<u32>,
2624 result: &mut SharedVector<Coord>,
2625 measure_fn: *const core::ffi::c_void,
2626 measure_user_data: *mut core::ffi::c_void,
2627 ) {
2628 let measure = unsafe { measure_closure_from_c(measure_fn, measure_user_data) };
2631 if let Some(mut measure) = measure {
2632 *result = super::solve_flexbox_layout_with_measure(
2633 data,
2634 repeater_indices,
2635 Some(&mut measure),
2636 );
2637 } else {
2638 *result = super::solve_flexbox_layout(data, repeater_indices);
2639 }
2640 }
2641
2642 #[unsafe(no_mangle)]
2643 pub extern "C" fn slint_flexbox_layout_info_main_axis(
2645 cells: Slice<LayoutItemInfo>,
2646 spacing: Coord,
2647 padding: &Padding,
2648 flex_wrap: FlexboxLayoutWrap,
2649 ) -> LayoutInfo {
2650 super::flexbox_layout_info_main_axis(cells, spacing, padding, flex_wrap)
2651 }
2652
2653 #[unsafe(no_mangle)]
2654 pub extern "C" fn slint_flexbox_layout_unwrapped_main(
2656 cells: Slice<LayoutItemInfo>,
2657 spacing: Coord,
2658 padding: &Padding,
2659 ) -> Coord {
2660 super::flexbox_layout_unwrapped_main(cells, spacing, padding)
2661 }
2662
2663 #[unsafe(no_mangle)]
2664 pub extern "C" fn slint_flexbox_layout_info_cross_axis(
2666 cells_h: Slice<LayoutItemInfo>,
2667 cells_v: Slice<LayoutItemInfo>,
2668 flex_props: Slice<FlexItemProps>,
2669 spacing_h: Coord,
2670 spacing_v: Coord,
2671 padding_h: &Padding,
2672 padding_v: &Padding,
2673 direction: FlexboxLayoutDirection,
2674 alignment: LayoutAlignment,
2675 flex_wrap: FlexboxLayoutWrap,
2676 constraint_size: Coord,
2677 ) -> LayoutInfo {
2678 super::flexbox_layout_info_cross_axis(
2679 cells_h,
2680 cells_v,
2681 flex_props,
2682 spacing_h,
2683 spacing_v,
2684 padding_h,
2685 padding_v,
2686 direction,
2687 alignment,
2688 flex_wrap,
2689 constraint_size,
2690 )
2691 }
2692
2693 #[unsafe(no_mangle)]
2694 pub extern "C" fn slint_flexbox_layout_info_cross_axis_with_measure(
2697 cells_h: Slice<LayoutItemInfo>,
2698 cells_v: Slice<LayoutItemInfo>,
2699 flex_props: Slice<FlexItemProps>,
2700 spacing_h: Coord,
2701 spacing_v: Coord,
2702 padding_h: &Padding,
2703 padding_v: &Padding,
2704 direction: FlexboxLayoutDirection,
2705 alignment: LayoutAlignment,
2706 flex_wrap: FlexboxLayoutWrap,
2707 constraint_size: Coord,
2708 measure_fn: *const core::ffi::c_void,
2709 measure_user_data: *mut core::ffi::c_void,
2710 ) -> LayoutInfo {
2711 let mut measure = unsafe { measure_closure_from_c(measure_fn, measure_user_data) };
2714 super::flexbox_layout_info_cross_axis_with_measure(
2715 cells_h,
2716 cells_v,
2717 flex_props,
2718 spacing_h,
2719 spacing_v,
2720 padding_h,
2721 padding_v,
2722 direction,
2723 alignment,
2724 flex_wrap,
2725 constraint_size,
2726 measure.as_mut().map(|m| m as _),
2727 )
2728 }
2729}
2730
2731#[cfg(test)]
2732mod tests {
2733 use super::*;
2734
2735 fn collect_from_organized_data(
2736 organized_data: &GridLayoutOrganizedData,
2737 num_cells: usize,
2738 repeater_indices: Slice<u32>,
2739 repeater_steps: Slice<u32>,
2740 ) -> Vec<(u16, u16, u16, u16)> {
2741 let mut result = Vec::new();
2742 for i in 0..num_cells {
2743 let col_and_span = organized_data.col_or_row_and_span(
2744 i,
2745 Orientation::Horizontal,
2746 &repeater_indices,
2747 &repeater_steps,
2748 );
2749 let row_and_span = organized_data.col_or_row_and_span(
2750 i,
2751 Orientation::Vertical,
2752 &repeater_indices,
2753 &repeater_steps,
2754 );
2755 result.push((col_and_span.0, col_and_span.1, row_and_span.0, row_and_span.1));
2756 }
2757 result
2758 }
2759
2760 #[test]
2761 fn test_organized_data_generator_2_fixed_cells() {
2762 let mut result = GridLayoutOrganizedData::default();
2764 let num_cells = 2;
2765 let mut generator = OrganizedDataGenerator::new(&[], &[], num_cells, 0, 0, &mut result);
2766 generator.add(0, 1, 0, 1);
2767 generator.add(1, 2, 0, 3);
2768 assert_eq!(result.as_slice(), &[0, 1, 0, 1, 1, 2, 0, 3]);
2769
2770 let repeater_indices = Slice::from_slice(&[]);
2771 let empty_steps = Slice::from_slice(&[]);
2772 let collected_data =
2773 collect_from_organized_data(&result, num_cells, repeater_indices, empty_steps);
2774 assert_eq!(collected_data.as_slice(), &[(0, 1, 0, 1), (1, 2, 0, 3)]);
2775
2776 assert_eq!(
2777 result.max_value(num_cells, Orientation::Horizontal, &repeater_indices, &empty_steps),
2778 3
2779 );
2780 assert_eq!(
2781 result.max_value(num_cells, Orientation::Vertical, &repeater_indices, &empty_steps),
2782 3
2783 );
2784 }
2785
2786 #[test]
2787 fn test_organized_data_generator_1_fixed_cell_1_repeater() {
2788 let mut result = GridLayoutOrganizedData::default();
2790 let num_cells = 4;
2791 let repeater_indices = &[1u32, 3u32];
2792 let mut generator =
2793 OrganizedDataGenerator::new(repeater_indices, &[], 1, 1, 3, &mut result);
2794 generator.add(0, 1, 0, 2); generator.add(1, 2, 1, 3); generator.add(1, 1, 2, 4);
2797 generator.add(2, 2, 3, 5);
2798 assert_eq!(
2799 result.as_slice(),
2800 &[
2801 0, 1, 0, 2, 8, 4, 0, 0, 1, 2, 1, 3, 1, 1, 2, 4, 2, 2, 3, 5, ]
2807 );
2808 let repeater_indices = Slice::from_slice(repeater_indices);
2809 let empty_steps = Slice::from_slice(&[]);
2810 let collected_data =
2811 collect_from_organized_data(&result, num_cells, repeater_indices, empty_steps);
2812 assert_eq!(
2813 collected_data.as_slice(),
2814 &[(0, 1, 0, 2), (1, 2, 1, 3), (1, 1, 2, 4), (2, 2, 3, 5)]
2815 );
2816
2817 assert_eq!(
2818 result.max_value(num_cells, Orientation::Horizontal, &repeater_indices, &empty_steps),
2819 4
2820 );
2821 assert_eq!(
2822 result.max_value(num_cells, Orientation::Vertical, &repeater_indices, &empty_steps),
2823 8
2824 );
2825 }
2826
2827 #[test]
2828
2829 fn test_organize_data_with_auto_and_spans() {
2830 let auto = i_slint_common::ROW_COL_AUTO;
2831 let input = std::vec![
2832 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 2., rowspan: -1. },
2833 GridLayoutInputData { new_row: false, col: auto, row: auto, colspan: 1., rowspan: 2. },
2834 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 2., rowspan: 1. },
2835 GridLayoutInputData { new_row: true, col: -2., row: 80000., colspan: 2., rowspan: 1. },
2836 ];
2837 let repeater_indices = Slice::from_slice(&[]);
2838 let (organized_data, errors) = organize_grid_layout_impl(
2839 Slice::from_slice(&input),
2840 repeater_indices,
2841 Slice::from_slice(&[]),
2842 );
2843 assert_eq!(
2844 organized_data.as_slice(),
2845 &[
2846 0, 2, 0, 0, 2, 1, 0, 2, 0, 2, 1, 1, 0, 2, 65535, 1, ]
2851 );
2852 assert_eq!(errors.len(), 3);
2853 assert_eq!(errors[0], "cell rowspan -1 is negative, clamping to 0");
2855 assert_eq!(errors[1], "cell row 80000 is too large, clamping to 65535");
2856 assert_eq!(errors[2], "cell col -2 is negative, clamping to 0");
2857 let empty_steps = Slice::from_slice(&[]);
2858 let collected_data = collect_from_organized_data(
2859 &organized_data,
2860 input.len(),
2861 repeater_indices,
2862 empty_steps,
2863 );
2864 assert_eq!(
2865 collected_data.as_slice(),
2866 &[(0, 2, 0, 0), (2, 1, 0, 2), (0, 2, 1, 1), (0, 2, 65535, 1)]
2867 );
2868 assert_eq!(
2869 organized_data.max_value(3, Orientation::Horizontal, &repeater_indices, &empty_steps),
2870 3
2871 );
2872 assert_eq!(
2873 organized_data.max_value(3, Orientation::Vertical, &repeater_indices, &empty_steps),
2874 2
2875 );
2876 }
2877
2878 #[test]
2879 fn test_organize_data_1_empty_repeater() {
2880 let auto = i_slint_common::ROW_COL_AUTO;
2882 let cell =
2883 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. };
2884 let input = std::vec![cell];
2885 let repeater_indices = Slice::from_slice(&[1u32, 0u32]);
2886 let (organized_data, errors) = organize_grid_layout_impl(
2887 Slice::from_slice(&input),
2888 repeater_indices,
2889 Slice::from_slice(&[]),
2890 );
2891 assert_eq!(
2892 organized_data.as_slice(),
2893 &[
2894 0, 1, 0, 1, 0, 0, 0, 0
2896 ] );
2898 assert_eq!(errors.len(), 0);
2899 let empty_steps = Slice::from_slice(&[]);
2900 let collected_data = collect_from_organized_data(
2901 &organized_data,
2902 input.len(),
2903 repeater_indices,
2904 empty_steps,
2905 );
2906 assert_eq!(collected_data.as_slice(), &[(0, 1, 0, 1)]);
2907 assert_eq!(
2908 organized_data.max_value(1, Orientation::Horizontal, &repeater_indices, &empty_steps),
2909 1
2910 );
2911 }
2912
2913 #[test]
2914 fn test_organize_data_4_repeaters() {
2915 let auto = i_slint_common::ROW_COL_AUTO;
2916 let mut cell =
2917 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. };
2918 let mut input = std::vec![cell.clone()];
2919 for _ in 0..8 {
2920 cell.new_row = false;
2921 input.push(cell.clone());
2922 }
2923 let repeater_indices = Slice::from_slice(&[0u32, 0u32, 1u32, 4u32, 6u32, 2u32, 8u32, 0u32]);
2924 let (organized_data, errors) = organize_grid_layout_impl(
2925 Slice::from_slice(&input),
2926 repeater_indices,
2927 Slice::from_slice(&[]),
2928 );
2929 assert_eq!(
2930 organized_data.as_slice(),
2931 &[
2932 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, ]
2946 );
2947 assert_eq!(errors.len(), 0);
2948 let empty_steps = Slice::from_slice(&[]);
2949 let collected_data = collect_from_organized_data(
2950 &organized_data,
2951 input.len(),
2952 repeater_indices,
2953 empty_steps,
2954 );
2955 assert_eq!(
2956 collected_data.as_slice(),
2957 &[
2958 (0, 1, 0, 1),
2959 (1, 1, 0, 1),
2960 (2, 1, 0, 1),
2961 (3, 1, 0, 1),
2962 (4, 1, 0, 1),
2963 (5, 1, 0, 1),
2964 (6, 1, 0, 1),
2965 (7, 1, 0, 1),
2966 (8, 1, 0, 1),
2967 ]
2968 );
2969 let empty_steps = Slice::from_slice(&[]);
2970 assert_eq!(
2971 organized_data.max_value(
2972 input.len(),
2973 Orientation::Horizontal,
2974 &repeater_indices,
2975 &empty_steps
2976 ),
2977 9
2978 );
2979 }
2980
2981 #[test]
2982 fn test_organize_data_repeated_rows() {
2983 let auto = i_slint_common::ROW_COL_AUTO;
2984 let mut input = Vec::new();
2985 let num_rows: u32 = 3;
2986 let num_columns: u32 = 2;
2987 for _ in 0..num_rows {
2989 let mut cell = GridLayoutInputData {
2990 new_row: true,
2991 col: auto,
2992 row: auto,
2993 colspan: 1.,
2994 rowspan: 1.,
2995 };
2996 input.push(cell.clone());
2997 cell.new_row = false;
2998 input.push(cell.clone());
2999 }
3000 let repeater_indices_arr = [0_u32, num_rows];
3002 let repeater_steps_arr = [num_columns];
3003 let repeater_steps = Slice::from_slice(&repeater_steps_arr);
3004 let repeater_indices = Slice::from_slice(&repeater_indices_arr);
3005 let (organized_data, errors) =
3006 organize_grid_layout_impl(Slice::from_slice(&input), repeater_indices, repeater_steps);
3007 assert_eq!(
3008 organized_data.as_slice(),
3009 &[
3010 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, ]
3015 );
3016 assert_eq!(errors.len(), 0);
3017 let collected_data = collect_from_organized_data(
3018 &organized_data,
3019 input.len(),
3020 repeater_indices,
3021 repeater_steps,
3022 );
3023 assert_eq!(
3024 collected_data.as_slice(),
3025 &[(0, 1, 0, 1), (1, 1, 0, 1), (0, 1, 1, 1), (1, 1, 1, 1), (0, 1, 2, 1), (1, 1, 2, 1),]
3027 );
3028 assert_eq!(
3029 organized_data.max_value(
3030 input.len(),
3031 Orientation::Horizontal,
3032 &repeater_indices,
3033 &repeater_steps
3034 ),
3035 2
3036 );
3037 assert_eq!(
3038 organized_data.max_value(
3039 input.len(),
3040 Orientation::Vertical,
3041 &repeater_indices,
3042 &repeater_steps
3043 ),
3044 3
3045 );
3046
3047 let mut layout_cache_v = SharedVector::<Coord>::default();
3049 let mut generator = GridLayoutCacheGenerator::new(
3050 repeater_indices.as_slice(),
3051 repeater_steps.as_slice(),
3052 0, 1, 6, &mut layout_cache_v,
3056 );
3057 generator.add(0., 50.);
3059 generator.add(0., 50.);
3060 generator.add(50., 50.);
3062 generator.add(50., 50.);
3063 generator.add(100., 50.);
3065 generator.add(100., 50.);
3066 assert_eq!(
3067 layout_cache_v.as_slice(),
3068 &[
3069 2., 4., 0., 50., 0., 50., 50., 50., 50., 50., 100., 50., 100., 50., ]
3074 );
3075
3076 let layout_cache_v_access = |jump_index: usize,
3078 repeater_index: usize,
3079 stride: usize,
3080 child_offset: usize|
3081 -> Coord {
3082 let base = layout_cache_v[jump_index] as usize;
3083 let data_idx = base + repeater_index * stride + child_offset;
3084 layout_cache_v[data_idx]
3085 };
3086 assert_eq!(layout_cache_v_access(0, 0, 4, 0), 0.);
3089 assert_eq!(layout_cache_v_access(0, 1, 4, 0), 50.);
3090 assert_eq!(layout_cache_v_access(0, 2, 4, 0), 100.);
3091 assert_eq!(layout_cache_v_access(0, 0, 4, 2), 0.);
3093 assert_eq!(layout_cache_v_access(0, 1, 4, 2), 50.);
3094 assert_eq!(layout_cache_v_access(0, 2, 4, 2), 100.);
3095 }
3096
3097 #[test]
3098 fn test_organize_data_repeated_rows_multiple_repeaters() {
3099 let auto = i_slint_common::ROW_COL_AUTO;
3100 let mut input = Vec::new();
3101 let num_rows: u32 = 5;
3102 let mut cell =
3103 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. };
3104 for _ in 0..3 {
3106 cell.new_row = true;
3107 input.push(cell.clone());
3108 cell.new_row = false;
3109 input.push(cell.clone());
3110 }
3111 for _ in 0..2 {
3113 cell.new_row = true;
3114 input.push(cell.clone());
3115 cell.new_row = false;
3116 input.push(cell.clone());
3117 cell.new_row = false;
3118 input.push(cell.clone());
3119 }
3120 let repeater_indices_arr = [0_u32, 3, 6, 2];
3123 let repeater_steps_arr = [2, 3];
3124 let repeater_steps = Slice::from_slice(&repeater_steps_arr);
3125 let repeater_indices = Slice::from_slice(&repeater_indices_arr);
3126 let (organized_data, errors) =
3127 organize_grid_layout_impl(Slice::from_slice(&input), repeater_indices, repeater_steps);
3128 assert_eq!(
3129 organized_data.as_slice(),
3130 &[
3131 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, ]
3141 );
3142 assert_eq!(errors.len(), 0);
3143 let collected_data = collect_from_organized_data(
3144 &organized_data,
3145 input.len(),
3146 repeater_indices,
3147 repeater_steps,
3148 );
3149 assert_eq!(
3150 collected_data.as_slice(),
3151 &[
3153 (0, 1, 0, 1),
3154 (1, 1, 0, 1),
3155 (0, 1, 1, 1),
3156 (1, 1, 1, 1),
3157 (0, 1, 2, 1),
3158 (1, 1, 2, 1),
3159 (0, 1, 3, 1),
3160 (1, 1, 3, 1),
3161 (2, 1, 3, 1),
3162 (0, 1, 4, 1),
3163 (1, 1, 4, 1),
3164 (2, 1, 4, 1)
3165 ]
3166 );
3167 assert_eq!(
3168 organized_data.max_value(
3169 input.len(),
3170 Orientation::Horizontal,
3171 &repeater_indices,
3172 &repeater_steps
3173 ),
3174 3 );
3176 assert_eq!(
3177 organized_data.max_value(
3178 input.len(),
3179 Orientation::Vertical,
3180 &repeater_indices,
3181 &repeater_steps
3182 ),
3183 num_rows as usize );
3185
3186 let mut layout_cache_v = SharedVector::<Coord>::default();
3188 let mut generator = GridLayoutCacheGenerator::new(
3189 repeater_indices.as_slice(),
3190 repeater_steps.as_slice(),
3191 0, 2, 12, &mut layout_cache_v,
3195 );
3196 generator.add(0., 50.);
3198 generator.add(0., 50.);
3199 generator.add(50., 50.);
3201 generator.add(50., 50.);
3202 generator.add(100., 50.);
3204 generator.add(100., 50.);
3205 generator.add(150., 50.);
3207 generator.add(150., 50.);
3208 generator.add(150., 50.);
3209 generator.add(200., 50.);
3211 generator.add(200., 50.);
3212 generator.add(200., 50.);
3213 assert_eq!(
3214 layout_cache_v.as_slice(),
3215 &[
3216 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., ]
3224 );
3225
3226 let layout_cache_v_access = |jump_index: usize,
3228 repeater_index: usize,
3229 stride: usize,
3230 child_offset: usize|
3231 -> Coord {
3232 let base = layout_cache_v[jump_index] as usize;
3233 let data_idx = base + repeater_index * stride + child_offset;
3234 layout_cache_v[data_idx]
3235 };
3236 assert_eq!(layout_cache_v_access(0, 0, 4, 0), 0.);
3238 assert_eq!(layout_cache_v_access(0, 1, 4, 0), 50.);
3239 assert_eq!(layout_cache_v_access(0, 2, 4, 0), 100.);
3240 assert_eq!(layout_cache_v_access(0, 0, 4, 2), 0.);
3242 assert_eq!(layout_cache_v_access(0, 1, 4, 2), 50.);
3243 assert_eq!(layout_cache_v_access(0, 2, 4, 2), 100.);
3244 assert_eq!(layout_cache_v_access(2, 0, 6, 0), 150.);
3246 assert_eq!(layout_cache_v_access(2, 1, 6, 0), 200.);
3247 assert_eq!(layout_cache_v_access(2, 0, 6, 4), 150.);
3249 assert_eq!(layout_cache_v_access(2, 1, 6, 4), 200.);
3250 }
3251
3252 #[test]
3253 fn test_layout_cache_generator_2_fixed_cells() {
3254 let mut result = SharedVector::<Coord>::default();
3256 result.resize(2 * 2, 0 as _);
3257 let mut generator = LayoutCacheGenerator::new(&[], &mut result);
3258 generator.add(0., 50.); generator.add(80., 50.); assert_eq!(result.as_slice(), &[0., 50., 80., 50.]);
3261 }
3262
3263 #[test]
3264 fn test_layout_cache_generator_1_fixed_cell_1_repeater() {
3265 let mut result = SharedVector::<Coord>::default();
3267 let repeater_indices = &[1, 3];
3268 result.resize(4 * 2 + repeater_indices.len(), 0 as _);
3269 let mut generator = LayoutCacheGenerator::new(repeater_indices, &mut result);
3270 generator.add(0., 50.); generator.add(80., 50.); generator.add(160., 50.);
3273 generator.add(240., 50.);
3274 assert_eq!(
3275 result.as_slice(),
3276 &[
3277 0., 50., 4., 5., 80., 50., 160., 50., 240., 50. ]
3281 );
3282 }
3283
3284 #[test]
3285 fn test_layout_cache_generator_4_repeaters() {
3286 let mut result = SharedVector::<Coord>::default();
3288 let repeater_indices = &[1, 0, 1, 4, 6, 2, 8, 0];
3289 result.resize(8 * 2 + repeater_indices.len(), 0 as _);
3290 let mut generator = LayoutCacheGenerator::new(repeater_indices, &mut result);
3291 generator.add(0., 50.); generator.add(80., 10.); generator.add(160., 10.);
3294 generator.add(240., 10.);
3295 generator.add(320., 10.); generator.add(400., 80.); generator.add(500., 20.); generator.add(600., 20.); assert_eq!(
3300 result.as_slice(),
3301 &[
3302 0., 50., 12., 13., 12., 13., 400., 80., 20., 21., 0., 0., 80., 10., 160., 10., 240., 10., 320., 10., 500., 20., 600., 20. ]
3311 );
3312 }
3313
3314 mod max_content_matches_taffy {
3324 use super::*;
3325
3326 fn cell(preferred: Coord, stretch: f32) -> FlexboxLayoutItemInfo {
3327 FlexboxLayoutItemInfo {
3328 constraint: LayoutInfo { preferred, stretch, ..Default::default() },
3329 ..Default::default()
3330 }
3331 }
3332
3333 fn constraints(cells: &[FlexboxLayoutItemInfo]) -> Vec<LayoutItemInfo> {
3336 cells
3337 .iter()
3338 .map(|c| LayoutItemInfo { constraint: c.constraint.clone(), ..Default::default() })
3339 .collect()
3340 }
3341
3342 fn split(cells: &[FlexboxLayoutItemInfo]) -> (Vec<LayoutItemInfo>, Vec<FlexItemProps>) {
3345 (constraints(cells), cells.iter().map(|c| c.props).collect())
3346 }
3347
3348 fn taffy_max_content_main(cells: &[FlexboxLayoutItemInfo]) -> Coord {
3350 let (main, flex) = split(cells);
3351 let cross: Vec<LayoutItemInfo> = cells
3354 .iter()
3355 .map(|_| LayoutItemInfo {
3356 constraint: LayoutInfo { max: Coord::MAX, ..Default::default() },
3357 ..Default::default()
3358 })
3359 .collect();
3360 let cells_h = Slice::from_slice(&main);
3361 let cells_v = Slice::from_slice(&cross);
3362 let flex_props = Slice::from_slice(&flex);
3363 let pad = Padding::default();
3364 let mut builder =
3365 flexbox_taffy::FlexboxTaffyBuilder::new(flexbox_taffy::FlexboxLayoutParams {
3366 cells_h: &cells_h,
3367 cells_v: &cells_v,
3368 flex_props: &flex_props,
3369 spacing_h: 0 as Coord,
3370 spacing_v: 0 as Coord,
3371 padding_h: &pad,
3372 padding_v: &pad,
3373 alignment: LayoutAlignment::Stretch,
3376 cross_axis_line_alignment: LayoutAlignment::Stretch,
3377 cross_axis_alignment: CrossAxisAlignment::Stretch,
3378 flex_wrap: FlexboxLayoutWrap::NoWrap,
3379 flex_shrink: 1.,
3380 flex_direction: flexbox_taffy::TaffyFlexDirection::Row,
3381 container_width: None,
3382 container_height: None,
3383 cross_axis_sizing: flexbox_taffy::CrossAxisSizing::Preferred,
3384 });
3385 let mut measure = |idx: usize, known_w: Option<Coord>, known_h: Option<Coord>| {
3389 (
3390 known_w.unwrap_or_else(|| cells[idx].constraint.preferred_bounded()),
3391 known_h.unwrap_or(0 as Coord),
3392 )
3393 };
3394 builder.compute_layout(Coord::MAX, Coord::MAX, &mut measure);
3395 builder.container_size().0
3396 }
3397
3398 fn ours(cells: &[FlexboxLayoutItemInfo]) -> Coord {
3399 let main = constraints(cells);
3400 flexbox_layout_unwrapped_main(Slice::from_slice(&main), 0 as Coord, &Padding::default())
3401 }
3402
3403 #[track_caller]
3404 fn assert_agrees(name: &str, cells: &[FlexboxLayoutItemInfo]) {
3405 let (ours, theirs) = (ours(cells), taffy_max_content_main(cells));
3406 assert!((ours - theirs).abs() <= 1 as Coord, "{name}: ours={ours} taffy={theirs}");
3407 }
3408
3409 #[test]
3413 fn agrees() {
3414 assert_agrees("plain", &[cell(50., 0.), cell(250., 0.)]);
3415 assert_agrees("equal stretch", &[cell(50., 1.), cell(250., 1.)]);
3416 assert_agrees("uneven stretch", &[cell(60., 1.), cell(60., 3.)]);
3417 assert_agrees("fractional stretch", &[cell(60., 0.5), cell(40., 1.5)]);
3418 assert_agrees("mixed stretch and not", &[cell(50., 1.), cell(100., 0.)]);
3419 assert_agrees("zero preferred, stretchy", &[cell(0., 1.), cell(0., 1.)]);
3422 }
3423
3424 fn taffy_column_main(cells: &[FlexboxLayoutItemInfo]) -> Coord {
3427 let (main, flex) = split(cells);
3428 let h: Vec<LayoutItemInfo> = cells
3431 .iter()
3432 .map(|_| LayoutItemInfo {
3433 constraint: LayoutInfo { max: Coord::MAX, ..Default::default() },
3434 ..Default::default()
3435 })
3436 .collect();
3437 let cells_h = Slice::from_slice(&h);
3438 let cells_v = Slice::from_slice(&main);
3439 let flex_props = Slice::from_slice(&flex);
3440 let pad = Padding::default();
3441 let mut builder =
3442 flexbox_taffy::FlexboxTaffyBuilder::new(flexbox_taffy::FlexboxLayoutParams {
3443 cells_h: &cells_h,
3444 cells_v: &cells_v,
3445 flex_props: &flex_props,
3446 spacing_h: 0 as Coord,
3447 spacing_v: 0 as Coord,
3448 padding_h: &pad,
3449 padding_v: &pad,
3450 alignment: LayoutAlignment::Stretch,
3451 cross_axis_line_alignment: LayoutAlignment::Stretch,
3452 cross_axis_alignment: CrossAxisAlignment::Stretch,
3453 flex_wrap: FlexboxLayoutWrap::NoWrap,
3454 flex_shrink: 1.,
3455 flex_direction: flexbox_taffy::TaffyFlexDirection::Column,
3456 container_width: None,
3457 container_height: None,
3458 cross_axis_sizing: flexbox_taffy::CrossAxisSizing::Preferred,
3459 });
3460 let mut measure = |idx: usize, known_w: Option<Coord>, known_h: Option<Coord>| {
3461 (
3462 known_w.unwrap_or(0 as Coord),
3463 known_h.unwrap_or_else(|| cells[idx].constraint.preferred_bounded()),
3464 )
3465 };
3466 builder.compute_layout(Coord::MAX, Coord::MAX, &mut measure);
3467 builder.container_size().1
3468 }
3469
3470 #[track_caller]
3471 fn assert_agrees_column(name: &str, cells: &[FlexboxLayoutItemInfo]) {
3472 let (o, t) = (ours(cells), taffy_column_main(cells));
3473 assert!((o - t).abs() <= 1 as Coord, "{name}: ours={o} taffy={t}");
3474 }
3475
3476 #[test]
3480 fn agrees_for_a_column() {
3481 assert_agrees_column("plain", &[cell(50., 0.), cell(250., 0.)]);
3482 assert_agrees_column("equal stretch", &[cell(50., 1.), cell(250., 1.)]);
3483 assert_agrees_column("zero preferred, stretchy", &[cell(0., 1.), cell(0., 1.)]);
3484 }
3485 }
3486
3487 mod stretch_driven_grow {
3492 use super::*;
3493
3494 fn info(preferred: Coord, stretch: f32) -> LayoutInfo {
3495 LayoutInfo { preferred, stretch, ..Default::default() }
3496 }
3497
3498 fn solve_row(alignment: LayoutAlignment, constraints: &[LayoutInfo]) -> Vec<Coord> {
3501 let cells_h: Vec<LayoutItemInfo> = constraints
3502 .iter()
3503 .map(|c| LayoutItemInfo { constraint: c.clone(), ..Default::default() })
3504 .collect();
3505 let cells_v: Vec<LayoutItemInfo> =
3506 constraints.iter().map(|_| LayoutItemInfo::default()).collect();
3507 let flex_props: Vec<FlexItemProps> =
3508 constraints.iter().map(|_| FlexItemProps::default()).collect();
3509 let pad = Padding::default();
3510 let mut builder =
3511 flexbox_taffy::FlexboxTaffyBuilder::new(flexbox_taffy::FlexboxLayoutParams {
3512 cells_h: &Slice::from_slice(&cells_h),
3513 cells_v: &Slice::from_slice(&cells_v),
3514 flex_props: &Slice::from_slice(&flex_props),
3515 spacing_h: 0 as Coord,
3516 spacing_v: 0 as Coord,
3517 padding_h: &pad,
3518 padding_v: &pad,
3519 alignment,
3520 cross_axis_line_alignment: LayoutAlignment::Stretch,
3521 cross_axis_alignment: CrossAxisAlignment::Stretch,
3522 flex_wrap: FlexboxLayoutWrap::Wrap,
3523 flex_shrink: 1.,
3524 flex_direction: flexbox_taffy::TaffyFlexDirection::Row,
3525 container_width: Some(400 as Coord),
3526 container_height: None,
3527 cross_axis_sizing: flexbox_taffy::CrossAxisSizing::Preferred,
3528 });
3529 builder.compute_layout(400 as Coord, Coord::MAX, &mut zero_measure);
3530 (0..constraints.len()).map(|i| builder.child_geometry(i).2).collect()
3531 }
3532
3533 #[test]
3534 fn grows_by_stretch_only_under_alignment_stretch() {
3535 let cells = [info(100., 3.), info(100., 1.)];
3537 assert_eq!(solve_row(LayoutAlignment::Stretch, &cells), [250., 150.]);
3538 let cells = [info(100., 1.), info(100., 0.)];
3540 assert_eq!(solve_row(LayoutAlignment::Stretch, &cells), [300., 100.]);
3541 let cells = [info(100., 0.), info(100., 0.)];
3543 assert_eq!(solve_row(LayoutAlignment::Stretch, &cells), [200., 200.]);
3544 let cells = [info(100., 3.), info(100., 1.)];
3546 assert_eq!(solve_row(LayoutAlignment::Start, &cells), [100., 100.]);
3547 }
3548
3549 #[test]
3553 fn lone_oversized_item_shrinks_regardless_of_stretch() {
3554 let squeezable =
3555 LayoutInfo { preferred: 500., min: 200., stretch: 0., ..Default::default() };
3556 assert_eq!(solve_row(LayoutAlignment::Start, &[squeezable]), [400.]);
3557 let rigid =
3558 LayoutInfo { preferred: 500., min: 450., stretch: 0., ..Default::default() };
3559 assert_eq!(solve_row(LayoutAlignment::Start, &[rigid]), [450.]);
3560 }
3561 }
3562
3563 #[test]
3574 fn percentage_against_unbounded_container_leaves_item_finite() {
3575 let cells_h = [LayoutItemInfo {
3577 constraint: LayoutInfo {
3578 min_percent: 50 as Coord,
3579 max_percent: 50 as Coord,
3580 max: Coord::MAX,
3581 ..Default::default()
3582 },
3583 ..Default::default()
3584 }];
3585 let cells_v = [LayoutItemInfo {
3586 constraint: LayoutInfo {
3587 preferred: 30 as Coord,
3588 max: Coord::MAX,
3589 ..Default::default()
3590 },
3591 ..Default::default()
3592 }];
3593 let flex_props = [FlexItemProps::default()];
3594 let pad = Padding::default();
3595 let mut builder =
3596 flexbox_taffy::FlexboxTaffyBuilder::new(flexbox_taffy::FlexboxLayoutParams {
3597 cells_h: &Slice::from_slice(&cells_h),
3598 cells_v: &Slice::from_slice(&cells_v),
3599 flex_props: &Slice::from_slice(&flex_props),
3600 spacing_h: 0 as Coord,
3601 spacing_v: 0 as Coord,
3602 padding_h: &pad,
3603 padding_v: &pad,
3604 alignment: LayoutAlignment::Start,
3605 cross_axis_line_alignment: LayoutAlignment::Stretch,
3606 cross_axis_alignment: CrossAxisAlignment::Stretch,
3607 flex_wrap: FlexboxLayoutWrap::NoWrap,
3608 flex_shrink: 1.,
3609 flex_direction: flexbox_taffy::TaffyFlexDirection::Row,
3610 container_width: Some(Coord::MAX),
3612 container_height: None,
3613 cross_axis_sizing: flexbox_taffy::CrossAxisSizing::Preferred,
3614 });
3615 builder.compute_layout(Coord::MAX, Coord::MAX, &mut zero_measure);
3616 let (_x, _y, w, _h) = builder.child_geometry(0);
3617 assert!(w.is_finite() && w < Coord::MAX, "item main-axis size was {w}");
3620 }
3621
3622 fn row_layout_data(constraints: &[LayoutInfo]) -> grid_internal::LayoutData {
3627 let mut organized_data = GridLayoutOrganizedData::default();
3628 let mut generator =
3629 OrganizedDataGenerator::new(&[], &[], constraints.len(), 0, 0, &mut organized_data);
3630 for col in 0..constraints.len() {
3631 generator.add(col as u16, 1, 0, 1);
3632 }
3633 let items: Vec<LayoutItemInfo> = constraints
3634 .iter()
3635 .map(|constraint| LayoutItemInfo { constraint: *constraint, ..Default::default() })
3636 .collect();
3637 let mut layout_data = grid_internal::to_layout_data(
3638 &organized_data,
3639 Slice::from_slice(&items),
3640 Orientation::Vertical,
3641 Slice::from_slice(&[]),
3642 Slice::from_slice(&[]),
3643 0 as _,
3644 None,
3645 );
3646 assert_eq!(layout_data.len(), 1);
3647 layout_data.remove(0)
3648 }
3649
3650 #[test]
3651 fn test_grid_row_collapsed_cell_raises_max_and_pulls_pref_down_with_it() {
3652 let row = row_layout_data(&[
3656 LayoutInfo { min: 0 as _, max: 0 as _, preferred: 0 as _, ..Default::default() },
3657 LayoutInfo { min: 5 as _, preferred: 50 as _, ..Default::default() },
3658 ]);
3659 assert_eq!(row.min, 5 as Coord);
3665 assert_eq!(row.max, 5 as Coord);
3666 assert_eq!(row.pref, 5 as Coord);
3667 }
3668
3669 #[test]
3670 fn test_grid_row_without_conflicting_constraints_keeps_its_own_pref() {
3671 let row = row_layout_data(&[
3678 LayoutInfo { min: 0 as _, max: 10 as _, preferred: 5 as _, ..Default::default() },
3679 LayoutInfo { min: 0 as _, preferred: 50 as _, ..Default::default() },
3680 ]);
3681 assert_eq!(row.min, 0 as Coord);
3682 assert_eq!(row.max, 10 as Coord);
3683 assert_eq!(row.pref, 50 as Coord);
3684 }
3685}