1use crate::items::{DialogButtonRole, LayoutAlignment};
9use crate::{Coord, SharedVector, slice::Slice};
10use alloc::format;
11use alloc::string::String;
12use alloc::vec::Vec;
13
14pub use crate::items::Orientation;
15
16#[repr(C)]
19#[derive(Clone, Copy, Debug, PartialEq)]
20pub struct LayoutInfo {
21 pub max: Coord,
23 pub max_percent: Coord,
25 pub min: Coord,
27 pub min_percent: Coord,
29 pub preferred: Coord,
31 pub stretch: f32,
33}
34
35impl Default for LayoutInfo {
36 fn default() -> Self {
37 LayoutInfo {
38 min: 0 as _,
39 max: Coord::MAX,
40 min_percent: 0 as _,
41 max_percent: 100 as _,
42 preferred: 0 as _,
43 stretch: 0 as _,
44 }
45 }
46}
47
48impl LayoutInfo {
49 #[must_use]
51 pub fn merge(&self, other: &LayoutInfo) -> Self {
52 Self {
53 min: self.min.max(other.min),
54 max: self.max.min(other.max),
55 min_percent: self.min_percent.max(other.min_percent),
56 max_percent: self.max_percent.min(other.max_percent),
57 preferred: self.preferred.max(other.preferred),
58 stretch: self.stretch.min(other.stretch),
59 }
60 }
61
62 #[must_use]
64 pub fn preferred_bounded(&self) -> Coord {
65 self.preferred.min(self.max).max(self.min)
66 }
67}
68
69impl core::ops::Add for LayoutInfo {
70 type Output = Self;
71
72 fn add(self, rhs: Self) -> Self::Output {
73 self.merge(&rhs)
74 }
75}
76
77pub fn min_max_size_for_layout_constraints(
79 constraints_horizontal: LayoutInfo,
80 constraints_vertical: LayoutInfo,
81) -> (Option<crate::api::LogicalSize>, Option<crate::api::LogicalSize>) {
82 let min_width = constraints_horizontal.min.min(constraints_horizontal.max) as f32;
83 let min_height = constraints_vertical.min.min(constraints_vertical.max) as f32;
84 let max_width = constraints_horizontal.max.max(constraints_horizontal.min) as f32;
85 let max_height = constraints_vertical.max.max(constraints_vertical.min) as f32;
86
87 let min_size = if min_width > 0. || min_height > 0. || cfg!(target_arch = "wasm32") {
91 Some(crate::api::LogicalSize::new(min_width, min_height))
92 } else {
93 None
94 };
95
96 let max_size = if (max_width > 0.
97 && max_height > 0.
98 && (max_width < i32::MAX as f32 || max_height < i32::MAX as f32))
99 || cfg!(target_arch = "wasm32")
100 {
101 let window_size_max = 16_777_215.;
103 Some(crate::api::LogicalSize::new(
104 max_width.min(window_size_max),
105 max_height.min(window_size_max),
106 ))
107 } else {
108 None
109 };
110
111 (min_size, max_size)
112}
113
114trait Saturating {
117 fn add(_: Self, _: Self) -> Self;
118}
119impl Saturating for i32 {
120 #[inline]
121 fn add(a: Self, b: Self) -> Self {
122 a.saturating_add(b)
123 }
124}
125impl Saturating for f32 {
126 #[inline]
127 fn add(a: Self, b: Self) -> Self {
128 a + b
129 }
130}
131
132mod grid_internal {
133 use super::*;
134
135 fn order_coord<T: PartialOrd>(a: &T, b: &T) -> core::cmp::Ordering {
136 a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)
137 }
138
139 #[derive(Debug, Clone)]
140 pub struct LayoutData {
141 pub min: Coord,
143 pub max: Coord,
144 pub pref: Coord,
145 pub stretch: f32,
146
147 pub pos: Coord,
149 pub size: Coord,
150 }
151
152 impl Default for LayoutData {
153 fn default() -> Self {
154 LayoutData {
155 min: 0 as _,
156 max: Coord::MAX,
157 pref: 0 as _,
158 stretch: f32::MAX,
159 pos: 0 as _,
160 size: 0 as _,
161 }
162 }
163 }
164
165 trait Adjust {
166 fn can_grow(_: &LayoutData) -> Coord;
167 fn to_distribute(expected_size: Coord, current_size: Coord) -> Coord;
168 fn distribute(_: &mut LayoutData, val: Coord);
169 }
170
171 struct Grow;
172 impl Adjust for Grow {
173 fn can_grow(it: &LayoutData) -> Coord {
174 it.max - it.size
175 }
176
177 fn to_distribute(expected_size: Coord, current_size: Coord) -> Coord {
178 expected_size - current_size
179 }
180
181 fn distribute(it: &mut LayoutData, val: Coord) {
182 it.size += val;
183 }
184 }
185
186 struct Shrink;
187 impl Adjust for Shrink {
188 fn can_grow(it: &LayoutData) -> Coord {
189 it.size - it.min
190 }
191
192 fn to_distribute(expected_size: Coord, current_size: Coord) -> Coord {
193 current_size - expected_size
194 }
195
196 fn distribute(it: &mut LayoutData, val: Coord) {
197 it.size -= val;
198 }
199 }
200
201 #[allow(clippy::unnecessary_cast)] fn adjust_items<A: Adjust>(data: &mut [LayoutData], size_without_spacing: Coord) -> Option<()> {
203 loop {
204 let size_cannot_grow: Coord = data
205 .iter()
206 .filter(|it| A::can_grow(it) <= 0 as _)
207 .map(|it| it.size)
208 .fold(0 as Coord, Saturating::add);
209
210 let total_stretch: f32 =
211 data.iter().filter(|it| A::can_grow(it) > 0 as _).map(|it| it.stretch).sum();
212
213 let actual_stretch = |s: f32| if total_stretch <= 0. { 1. } else { s };
214
215 let max_grow = data
216 .iter()
217 .filter(|it| A::can_grow(it) > 0 as _)
218 .map(|it| A::can_grow(it) as f32 / actual_stretch(it.stretch))
219 .min_by(order_coord)?;
220
221 let current_size: Coord = data
222 .iter()
223 .filter(|it| A::can_grow(it) > 0 as _)
224 .map(|it| it.size)
225 .fold(0 as _, Saturating::add);
226
227 let to_distribute =
229 A::to_distribute(size_without_spacing, size_cannot_grow + current_size) as f32;
230 if to_distribute <= 0. || max_grow <= 0. {
231 return Some(());
232 }
233
234 let grow = if total_stretch <= 0. {
235 to_distribute
236 / (data.iter().filter(|it| A::can_grow(it) > 0 as _).count() as Coord) as f32
237 } else {
238 to_distribute / total_stretch
239 }
240 .min(max_grow);
241
242 let mut distributed = 0 as Coord;
243 for it in data.iter_mut().filter(|it| A::can_grow(it) > 0 as Coord) {
244 let val = (grow * actual_stretch(it.stretch)) as Coord;
245 A::distribute(it, val);
246 distributed += val;
247 }
248
249 if distributed <= 0 as Coord {
250 if let Some(it) = data
253 .iter_mut()
254 .filter(|it| A::can_grow(it) > 0 as _)
255 .max_by(|a, b| actual_stretch(a.stretch).total_cmp(&b.stretch))
256 {
257 A::distribute(it, to_distribute as Coord);
258 }
259 return Some(());
260 }
261 }
262 }
263
264 pub fn layout_items(data: &mut [LayoutData], start_pos: Coord, size: Coord, spacing: Coord) {
265 let size_without_spacing = size - spacing * (data.len() - 1) as Coord;
266
267 let mut pref = 0 as Coord;
268 for it in data.iter_mut() {
269 it.size = it.pref;
270 pref += it.pref;
271 }
272 if size_without_spacing >= pref {
273 adjust_items::<Grow>(data, size_without_spacing);
274 } else if size_without_spacing < pref {
275 adjust_items::<Shrink>(data, size_without_spacing);
276 }
277
278 let mut pos = start_pos;
279 for it in data.iter_mut() {
280 it.pos = pos;
281 pos = Saturating::add(pos, Saturating::add(it.size, spacing));
282 }
283 }
284
285 #[test]
286 #[allow(clippy::float_cmp)] fn test_layout_items() {
288 let my_items = &mut [
289 LayoutData { min: 100., max: 200., pref: 100., stretch: 1., ..Default::default() },
290 LayoutData { min: 50., max: 300., pref: 100., stretch: 1., ..Default::default() },
291 LayoutData { min: 50., max: 150., pref: 100., stretch: 1., ..Default::default() },
292 ];
293
294 layout_items(my_items, 100., 650., 0.);
295 assert_eq!(my_items[0].size, 200.);
296 assert_eq!(my_items[1].size, 300.);
297 assert_eq!(my_items[2].size, 150.);
298
299 layout_items(my_items, 100., 200., 0.);
300 assert_eq!(my_items[0].size, 100.);
301 assert_eq!(my_items[1].size, 50.);
302 assert_eq!(my_items[2].size, 50.);
303
304 layout_items(my_items, 100., 300., 0.);
305 assert_eq!(my_items[0].size, 100.);
306 assert_eq!(my_items[1].size, 100.);
307 assert_eq!(my_items[2].size, 100.);
308 }
309
310 pub fn to_layout_data(
313 organized_data: &GridLayoutOrganizedData,
314 constraints: Slice<LayoutItemInfo>,
315 orientation: Orientation,
316 repeater_indices: Slice<u32>,
317 repeater_steps: Slice<u32>,
318 spacing: Coord,
319 size: Option<Coord>,
320 ) -> Vec<LayoutData> {
321 assert!(organized_data.len().is_multiple_of(4));
322 let num = organized_data.max_value(
323 constraints.len(),
324 orientation,
325 &repeater_indices,
326 &repeater_steps,
327 ) as usize;
328 if num < 1 {
329 return Default::default();
330 }
331 let mut layout_data =
332 alloc::vec![grid_internal::LayoutData { stretch: 1., ..Default::default() }; num];
333 let mut has_spans = false;
334 for (idx, cell_data) in constraints.iter().enumerate() {
335 let constraint = &cell_data.constraint;
336 let mut max = constraint.max;
337 if let Some(size) = size {
338 max = max.min(size * constraint.max_percent / 100 as Coord);
339 }
340 let (col_or_row, span) = organized_data.col_or_row_and_span(
341 idx,
342 orientation,
343 &repeater_indices,
344 &repeater_steps,
345 );
346 for c in 0..(span as usize) {
347 let cdata = &mut layout_data[col_or_row as usize + c];
348 cdata.max = cdata.max.min(max);
349 }
350 if span == 1 {
351 let mut min = constraint.min;
352 if let Some(size) = size {
353 min = min.max(size * constraint.min_percent / 100 as Coord);
354 }
355 let pref = constraint.preferred.min(max).max(min);
356 let cdata = &mut layout_data[col_or_row as usize];
357 cdata.min = cdata.min.max(min);
358 cdata.pref = cdata.pref.max(pref);
359 cdata.stretch = cdata.stretch.min(constraint.stretch);
360 } else {
361 has_spans = true;
362 }
363 }
364 if has_spans {
365 for (idx, cell_data) in constraints.iter().enumerate() {
366 let constraint = &cell_data.constraint;
367 let (col_or_row, span) = organized_data.col_or_row_and_span(
368 idx,
369 orientation,
370 &repeater_indices,
371 &repeater_steps,
372 );
373 if span > 1 {
374 let span_data =
375 &mut layout_data[(col_or_row as usize)..(col_or_row + span) as usize];
376
377 let mut min = constraint.min;
379 if let Some(size) = size {
380 min = min.max(size * constraint.min_percent / 100 as Coord);
381 }
382 grid_internal::layout_items(span_data, 0 as _, min, spacing);
383 for cdata in span_data.iter_mut() {
384 if cdata.min < cdata.size {
385 cdata.min = cdata.size;
386 }
387 }
388
389 let mut max = constraint.max;
391 if let Some(size) = size {
392 max = max.min(size * constraint.max_percent / 100 as Coord);
393 }
394 grid_internal::layout_items(span_data, 0 as _, max, spacing);
395 for cdata in span_data.iter_mut() {
396 if cdata.max > cdata.size {
397 cdata.max = cdata.size;
398 }
399 }
400
401 grid_internal::layout_items(span_data, 0 as _, constraint.preferred, spacing);
403 for cdata in span_data.iter_mut() {
404 cdata.pref = cdata.pref.max(cdata.size).min(cdata.max).max(cdata.min);
405 }
406
407 let total_stretch: f32 = span_data.iter().map(|c| c.stretch).sum();
409 if total_stretch > constraint.stretch {
410 for cdata in span_data.iter_mut() {
411 cdata.stretch *= constraint.stretch / total_stretch;
412 }
413 }
414 }
415 }
416 }
417 layout_data
418 }
419}
420
421#[repr(C)]
422pub struct Constraint {
423 pub min: Coord,
424 pub max: Coord,
425}
426
427impl Default for Constraint {
428 fn default() -> Self {
429 Constraint { min: 0 as Coord, max: Coord::MAX }
430 }
431}
432
433#[repr(C)]
434#[derive(Copy, Clone, Debug, Default)]
435pub struct Padding {
436 pub begin: Coord,
437 pub end: Coord,
438}
439
440#[repr(C)]
441#[derive(Debug)]
442pub struct GridLayoutData {
444 pub size: Coord,
445 pub spacing: Coord,
446 pub padding: Padding,
447 pub organized_data: GridLayoutOrganizedData,
448}
449
450#[repr(C)]
453#[derive(Default, Debug, Clone)]
454pub struct GridLayoutInputData {
455 pub new_row: bool,
457 pub col: f32,
461 pub row: f32,
462 pub colspan: f32,
465 pub rowspan: f32,
466}
467
468pub type GridLayoutOrganizedData = SharedVector<u16>;
471
472impl GridLayoutOrganizedData {
473 fn push_cell(&mut self, col: u16, colspan: u16, row: u16, rowspan: u16) {
474 self.push(col);
475 self.push(colspan);
476 self.push(row);
477 self.push(rowspan);
478 }
479
480 fn col_or_row_and_span(
481 &self,
482 cell_number: usize,
483 orientation: Orientation,
484 repeater_indices: &Slice<u32>,
485 repeater_steps: &Slice<u32>,
486 ) -> (u16, u16) {
487 let mut final_idx = 0;
490 let mut cell_nr_adj = 0i32; let cell_number = cell_number as i32;
492 for rep_idx in 0..(repeater_indices.len() / 2) {
494 let ri_start_cell = repeater_indices[rep_idx * 2] as i32;
495 if cell_number < ri_start_cell {
496 break;
497 }
498 let ri_cell_count = repeater_indices[rep_idx * 2 + 1] as i32;
499 let step = repeater_steps.get(rep_idx).copied().unwrap_or(1) as i32;
500 let cells_in_repeater = ri_cell_count * step;
501 if cells_in_repeater > 0
502 && cell_number >= ri_start_cell
503 && cell_number < ri_start_cell + cells_in_repeater
504 {
505 let cell_in_rep = cell_number - ri_start_cell;
506 let jump_pos = (ri_start_cell - cell_nr_adj) as usize * 4;
507 final_idx = self[jump_pos] as usize + (cell_in_rep * 4) as usize;
508 break;
509 }
510 cell_nr_adj += cells_in_repeater - step;
513 }
514 if final_idx == 0 {
515 final_idx = ((cell_number - cell_nr_adj) * 4) as usize;
516 }
517 let offset = if orientation == Orientation::Horizontal { 0 } else { 2 };
518 (self[final_idx + offset], self[final_idx + offset + 1])
519 }
520
521 fn max_value(
522 &self,
523 num_cells: usize,
524 orientation: Orientation,
525 repeater_indices: &Slice<u32>,
526 repeater_steps: &Slice<u32>,
527 ) -> u16 {
528 let mut max = 0;
529 for idx in 0..num_cells {
532 let (col_or_row, span) =
533 self.col_or_row_and_span(idx, orientation, repeater_indices, repeater_steps);
534 max = max.max(col_or_row + span.max(1));
535 }
536 max
537 }
538}
539
540struct OrganizedDataGenerator<'a> {
541 repeater_indices: &'a [u32],
543 repeater_steps: &'a [u32],
544 counter: usize,
546 repeat_offset: usize,
548 next_rep: usize,
550 current_offset: usize,
552 result: &'a mut GridLayoutOrganizedData,
554}
555
556impl<'a> OrganizedDataGenerator<'a> {
557 fn new(
558 repeater_indices: &'a [u32],
559 repeater_steps: &'a [u32],
560 result: &'a mut GridLayoutOrganizedData,
561 ) -> Self {
562 let total_repeated_cells: usize = repeater_indices
564 .chunks(2)
565 .enumerate()
566 .map(|(i, chunk)| {
567 let count = chunk.get(1).copied().unwrap_or(0) as usize;
568 let step = repeater_steps.get(i).copied().unwrap_or(1) as usize;
569 count * step
570 })
571 .sum();
572 assert!(result.len() >= total_repeated_cells * 4);
573 let repeat_offset = result.len() / 4 - total_repeated_cells;
574 Self {
575 repeater_indices,
576 repeater_steps,
577 counter: 0,
578 repeat_offset,
579 next_rep: 0,
580 current_offset: 0,
581 result,
582 }
583 }
584 fn add(&mut self, col: u16, colspan: u16, row: u16, rowspan: u16) {
585 let res = self.result.make_mut_slice();
586 let o = loop {
587 if let Some(nr) = self.repeater_indices.get(self.next_rep * 2) {
588 let nr = *nr as usize;
589 let step = self.repeater_steps.get(self.next_rep).copied().unwrap_or(1) as usize;
590 if nr == self.counter {
591 for s in 0..step {
593 for o in 0..4 {
594 res[(self.current_offset + s) * 4 + o] =
595 ((self.repeat_offset + s) * 4 + o) as _;
596 }
597 }
598 self.current_offset += step;
599 }
600 if self.counter >= nr {
601 let rep_count = self.repeater_indices[self.next_rep * 2 + 1] as usize;
602 let cells_in_repeater = rep_count * step;
603 if self.counter - nr == cells_in_repeater {
604 self.repeat_offset += cells_in_repeater;
606 self.next_rep += 1;
607 continue;
608 }
609 let cell_in_rep = self.counter - nr;
611 let row_in_rep = cell_in_rep / step;
612 let col_in_rep = cell_in_rep % step;
613 let offset = self.repeat_offset + col_in_rep + row_in_rep * step;
614 break offset;
615 }
616 }
617 self.current_offset += 1;
618 break self.current_offset - 1;
619 };
620 res[o * 4] = col;
621 res[o * 4 + 1] = colspan;
622 res[o * 4 + 2] = row;
623 res[o * 4 + 3] = rowspan;
624 self.counter += 1;
625 }
626}
627
628pub fn organize_dialog_button_layout(
631 input_data: Slice<GridLayoutInputData>,
632 dialog_button_roles: Slice<DialogButtonRole>,
633) -> GridLayoutOrganizedData {
634 let mut organized_data = GridLayoutOrganizedData::default();
635 organized_data.reserve(input_data.len() * 4);
636
637 #[cfg(feature = "std")]
638 fn is_kde() -> bool {
639 std::env::var("XDG_CURRENT_DESKTOP")
641 .ok()
642 .and_then(|v| v.as_bytes().first().copied())
643 .is_some_and(|x| x.eq_ignore_ascii_case(&b'K'))
644 }
645 #[cfg(not(feature = "std"))]
646 let is_kde = || true;
647
648 let expected_order: &[DialogButtonRole] = match crate::detect_operating_system() {
649 crate::items::OperatingSystemType::Windows => {
650 &[
651 DialogButtonRole::Reset,
652 DialogButtonRole::None, DialogButtonRole::Accept,
654 DialogButtonRole::Action,
655 DialogButtonRole::Reject,
656 DialogButtonRole::Apply,
657 DialogButtonRole::Help,
658 ]
659 }
660 crate::items::OperatingSystemType::Macos | crate::items::OperatingSystemType::Ios => {
661 &[
662 DialogButtonRole::Help,
663 DialogButtonRole::Reset,
664 DialogButtonRole::Apply,
665 DialogButtonRole::Action,
666 DialogButtonRole::None, DialogButtonRole::Reject,
668 DialogButtonRole::Accept,
669 ]
670 }
671 _ if is_kde() => {
672 &[
674 DialogButtonRole::Help,
675 DialogButtonRole::Reset,
676 DialogButtonRole::None, DialogButtonRole::Action,
678 DialogButtonRole::Accept,
679 DialogButtonRole::Apply,
680 DialogButtonRole::Reject,
681 ]
682 }
683 _ => {
684 &[
686 DialogButtonRole::Help,
687 DialogButtonRole::Reset,
688 DialogButtonRole::None, DialogButtonRole::Action,
690 DialogButtonRole::Accept,
691 DialogButtonRole::Apply,
692 DialogButtonRole::Reject,
693 ]
694 }
695 };
696
697 let mut column_for_input: Vec<usize> = Vec::with_capacity(dialog_button_roles.len());
699 for role in expected_order.iter() {
700 if role == &DialogButtonRole::None {
701 column_for_input.push(usize::MAX); continue;
703 }
704 for (idx, r) in dialog_button_roles.as_slice().iter().enumerate() {
705 if *r == *role {
706 column_for_input.push(idx);
707 }
708 }
709 }
710
711 for (input_index, cell) in input_data.as_slice().iter().enumerate() {
712 let col = column_for_input.iter().position(|&x| x == input_index);
713 if let Some(col) = col {
714 organized_data.push_cell(col as _, cell.colspan as _, cell.row as _, cell.rowspan as _);
715 } else {
716 organized_data.push_cell(
719 cell.col as _,
720 cell.colspan as _,
721 cell.row as _,
722 cell.rowspan as _,
723 );
724 }
725 }
726 organized_data
727}
728
729type Errors = Vec<String>;
730
731pub fn organize_grid_layout(
732 input_data: Slice<GridLayoutInputData>,
733 repeater_indices: Slice<u32>,
734 repeater_steps: Slice<u32>,
735) -> GridLayoutOrganizedData {
736 let (organized_data, errors) =
737 organize_grid_layout_impl(input_data, repeater_indices, repeater_steps);
738 for error in errors {
739 crate::debug_log!("Slint layout error: {}", error);
740 }
741 organized_data
742}
743
744fn organize_grid_layout_impl(
746 input_data: Slice<GridLayoutInputData>,
747 repeater_indices: Slice<u32>,
748 repeater_steps: Slice<u32>,
749) -> (GridLayoutOrganizedData, Errors) {
750 let mut organized_data = GridLayoutOrganizedData::default();
751 let extra_jump_entries: usize =
754 repeater_steps.iter().map(|&s| (s as usize).saturating_sub(1)).sum();
755 organized_data
756 .resize(input_data.len() * 4 + repeater_indices.len() * 2 + extra_jump_entries * 4, 0 as _);
757 let mut generator = OrganizedDataGenerator::new(
758 repeater_indices.as_slice(),
759 repeater_steps.as_slice(),
760 &mut organized_data,
761 );
762 let mut errors = Vec::new();
763
764 fn clamp_to_u16(value: f32, field_name: &str, errors: &mut Vec<String>) -> u16 {
765 if value < 0.0 {
766 errors.push(format!("cell {field_name} {value} is negative, clamping to 0"));
767 0
768 } else if value > u16::MAX as f32 {
769 errors
770 .push(format!("cell {field_name} {value} is too large, clamping to {}", u16::MAX));
771 u16::MAX
772 } else {
773 value as u16
774 }
775 }
776
777 let mut row = 0;
778 let mut col = 0;
779 let mut first = true;
780 for cell in input_data.as_slice().iter() {
781 if cell.new_row && !first {
782 row += 1;
783 col = 0;
784 }
785 first = false;
786
787 if cell.row != i_slint_common::ROW_COL_AUTO {
788 let cell_row = clamp_to_u16(cell.row, "row", &mut errors);
789 if row != cell_row {
790 row = cell_row;
791 col = 0;
792 }
793 }
794 if cell.col != i_slint_common::ROW_COL_AUTO {
795 col = clamp_to_u16(cell.col, "col", &mut errors);
796 }
797
798 let colspan = clamp_to_u16(cell.colspan, "colspan", &mut errors);
799 let rowspan = clamp_to_u16(cell.rowspan, "rowspan", &mut errors);
800 col = col.min(u16::MAX - colspan); generator.add(col, colspan, row, rowspan);
802 col += colspan;
803 }
804 (organized_data, errors)
805}
806
807struct LayoutCacheGenerator<'a> {
814 repeater_indices: &'a [u32],
816 repeater_steps: &'a [u32],
817 counter: usize,
819 repeat_offset: usize,
821 next_rep: usize,
823 current_offset: usize,
825 result: &'a mut SharedVector<Coord>,
827}
828
829impl<'a> LayoutCacheGenerator<'a> {
830 fn new(
831 repeater_indices: &'a [u32],
832 repeater_steps: &'a [u32],
833 result: &'a mut SharedVector<Coord>,
834 ) -> Self {
835 let total_repeated_cells: usize = repeater_indices
837 .chunks(2)
838 .enumerate()
839 .map(|(i, chunk)| {
840 let count = chunk.get(1).copied().unwrap_or(0) as usize;
841 let step = repeater_steps.get(i).copied().unwrap_or(1) as usize;
842 count * step
843 })
844 .sum();
845 assert!(result.len() >= total_repeated_cells * 2);
846 let repeat_offset = result.len() / 2 - total_repeated_cells;
847 Self {
848 repeater_indices,
849 repeater_steps,
850 counter: 0,
851 repeat_offset,
852 next_rep: 0,
853 current_offset: 0,
854 result,
855 }
856 }
857 fn add(&mut self, pos: Coord, size: Coord) {
858 let res = self.result.make_mut_slice();
859 let o = loop {
860 if let Some(nr) = self.repeater_indices.get(self.next_rep * 2) {
861 let nr = *nr as usize;
862 let step = self.repeater_steps.get(self.next_rep).copied().unwrap_or(1) as usize;
863 if nr == self.counter {
864 for s in 0..step {
866 for o in 0..2 {
867 res[(self.current_offset + s) * 2 + o] =
868 ((self.repeat_offset + s) * 2 + o) as _;
869 }
870 }
871 self.current_offset += step;
872 }
873 if self.counter >= nr {
874 let rep_count = self.repeater_indices[self.next_rep * 2 + 1] as usize;
875 let cells_in_repeater = rep_count * step;
876 if self.counter - nr == cells_in_repeater {
877 self.repeat_offset += cells_in_repeater;
879 self.next_rep += 1;
880 continue;
881 }
882 let cell_in_rep = self.counter - nr;
884 let row_in_rep = cell_in_rep / step;
885 let col_in_rep = cell_in_rep % step;
886 let offset = self.repeat_offset + col_in_rep + row_in_rep * step;
887 break offset;
888 }
889 }
890 self.current_offset += 1;
891 break self.current_offset - 1;
892 };
893 res[o * 2] = pos;
894 res[o * 2 + 1] = size;
895 self.counter += 1;
896 }
897}
898
899pub fn solve_grid_layout(
902 data: &GridLayoutData,
903 constraints: Slice<LayoutItemInfo>,
904 orientation: Orientation,
905 repeater_indices: Slice<u32>,
906 repeater_steps: Slice<u32>,
907) -> SharedVector<Coord> {
908 let mut layout_data = grid_internal::to_layout_data(
909 &data.organized_data,
910 constraints,
911 orientation,
912 repeater_indices,
913 repeater_steps,
914 data.spacing,
915 Some(data.size),
916 );
917
918 if layout_data.is_empty() {
919 return Default::default();
920 }
921
922 grid_internal::layout_items(
923 &mut layout_data,
924 data.padding.begin,
925 data.size - (data.padding.begin + data.padding.end),
926 data.spacing,
927 );
928
929 let mut result = SharedVector::<Coord>::default();
930 let extra_jump_entries: usize =
933 repeater_steps.iter().map(|&s| (s as usize).saturating_sub(1)).sum();
934 result.resize(2 * constraints.len() + repeater_indices.len() + extra_jump_entries * 2, 0 as _);
935 let mut generator = LayoutCacheGenerator::new(&repeater_indices, &repeater_steps, &mut result);
936
937 for idx in 0..constraints.len() {
938 let (col_or_row, span) = data.organized_data.col_or_row_and_span(
939 idx,
940 orientation,
941 &repeater_indices,
942 &repeater_steps,
943 );
944 let cdata = &layout_data[col_or_row as usize];
945 let size = if span > 0 {
946 let last_cell = &layout_data[col_or_row as usize + span as usize - 1];
947 last_cell.pos + last_cell.size - cdata.pos
948 } else {
949 0 as Coord
950 };
951 generator.add(cdata.pos, size);
952 }
953 result
954}
955
956pub fn grid_layout_info(
957 organized_data: GridLayoutOrganizedData, constraints: Slice<LayoutItemInfo>,
959 repeater_indices: Slice<u32>,
960 repeater_steps: Slice<u32>,
961 spacing: Coord,
962 padding: &Padding,
963 orientation: Orientation,
964) -> LayoutInfo {
965 let layout_data = grid_internal::to_layout_data(
966 &organized_data,
967 constraints,
968 orientation,
969 repeater_indices,
970 repeater_steps,
971 spacing,
972 None,
973 );
974 if layout_data.is_empty() {
975 return Default::default();
976 }
977 let spacing_w = spacing * (layout_data.len() - 1) as Coord + padding.begin + padding.end;
978 let min = layout_data.iter().map(|data| data.min).sum::<Coord>() + spacing_w;
979 let max = layout_data.iter().map(|data| data.max).fold(spacing_w, Saturating::add);
980 let preferred = layout_data.iter().map(|data| data.pref).sum::<Coord>() + spacing_w;
981 let stretch = layout_data.iter().map(|data| data.stretch).sum::<f32>();
982 LayoutInfo { min, max, min_percent: 0 as _, max_percent: 100 as _, preferred, stretch }
983}
984
985#[repr(C)]
986#[derive(Debug)]
987pub struct BoxLayoutData<'a> {
991 pub size: Coord,
992 pub spacing: Coord,
993 pub padding: Padding,
994 pub alignment: LayoutAlignment,
995 pub cells: Slice<'a, LayoutItemInfo>,
996}
997
998#[repr(C)]
999#[derive(Default, Debug, Clone)]
1000pub struct LayoutItemInfo {
1003 pub constraint: LayoutInfo,
1004}
1005
1006pub fn solve_box_layout(data: &BoxLayoutData, repeater_indices: Slice<u32>) -> SharedVector<Coord> {
1008 let mut result = SharedVector::<Coord>::default();
1009 result.resize(data.cells.len() * 2 + repeater_indices.len(), 0 as _);
1010
1011 if data.cells.is_empty() {
1012 return result;
1013 }
1014
1015 let mut layout_data: Vec<_> = data
1016 .cells
1017 .iter()
1018 .map(|c| {
1019 let min = c.constraint.min.max(c.constraint.min_percent * data.size / 100 as Coord);
1020 let max = c.constraint.max.min(c.constraint.max_percent * data.size / 100 as Coord);
1021 grid_internal::LayoutData {
1022 min,
1023 max,
1024 pref: c.constraint.preferred.min(max).max(min),
1025 stretch: c.constraint.stretch,
1026 ..Default::default()
1027 }
1028 })
1029 .collect();
1030
1031 let size_without_padding = data.size - data.padding.begin - data.padding.end;
1032 let pref_size: Coord = layout_data.iter().map(|it| it.pref).sum();
1033 let num_spacings = (layout_data.len() - 1) as Coord;
1034 let spacings = data.spacing * num_spacings;
1035
1036 let align = match data.alignment {
1037 LayoutAlignment::Stretch => {
1038 grid_internal::layout_items(
1039 &mut layout_data,
1040 data.padding.begin,
1041 size_without_padding,
1042 data.spacing,
1043 );
1044 None
1045 }
1046 _ if size_without_padding <= pref_size + spacings => {
1047 grid_internal::layout_items(
1048 &mut layout_data,
1049 data.padding.begin,
1050 size_without_padding,
1051 data.spacing,
1052 );
1053 None
1054 }
1055 LayoutAlignment::Center => Some((
1056 data.padding.begin + (size_without_padding - pref_size - spacings) / 2 as Coord,
1057 data.spacing,
1058 )),
1059 LayoutAlignment::Start => Some((data.padding.begin, data.spacing)),
1060 LayoutAlignment::End => {
1061 Some((data.padding.begin + (size_without_padding - pref_size - spacings), data.spacing))
1062 }
1063 LayoutAlignment::SpaceBetween => {
1064 Some((data.padding.begin, (size_without_padding - pref_size) / num_spacings))
1065 }
1066 LayoutAlignment::SpaceAround => {
1067 let spacing = (size_without_padding - pref_size) / (num_spacings + 1 as Coord);
1068 Some((data.padding.begin + spacing / 2 as Coord, spacing))
1069 }
1070 LayoutAlignment::SpaceEvenly => {
1071 let spacing = (size_without_padding - pref_size) / (num_spacings + 2 as Coord);
1072 Some((data.padding.begin + spacing, spacing))
1073 }
1074 };
1075 if let Some((mut pos, spacing)) = align {
1076 for it in &mut layout_data {
1077 it.pos = pos;
1078 it.size = it.pref;
1079 pos += spacing + it.size;
1080 }
1081 }
1082
1083 let mut generator = LayoutCacheGenerator::new(&repeater_indices, &[], &mut result);
1084 for layout in layout_data.iter() {
1085 generator.add(layout.pos, layout.size);
1086 }
1087 result
1088}
1089
1090pub fn box_layout_info(
1092 cells: Slice<LayoutItemInfo>,
1093 spacing: Coord,
1094 padding: &Padding,
1095 alignment: LayoutAlignment,
1096) -> LayoutInfo {
1097 let count = cells.len();
1098 let is_stretch = alignment == LayoutAlignment::Stretch;
1099 if count < 1 {
1100 let mut info = LayoutInfo::default();
1101 info.min = padding.begin + padding.end;
1102 info.preferred = info.min;
1103 if is_stretch {
1104 info.max = info.min;
1105 }
1106 return info;
1107 };
1108 let extra_w = padding.begin + padding.end + spacing * (count - 1) as Coord;
1109 let min = cells.iter().map(|c| c.constraint.min).sum::<Coord>() + extra_w;
1110 let max = if is_stretch {
1111 (cells.iter().map(|c| c.constraint.max).fold(extra_w, Saturating::add)).max(min)
1112 } else {
1113 Coord::MAX
1114 };
1115 let preferred = cells.iter().map(|c| c.constraint.preferred_bounded()).sum::<Coord>() + extra_w;
1116 let stretch = cells.iter().map(|c| c.constraint.stretch).sum::<f32>();
1117 LayoutInfo { min, max, min_percent: 0 as _, max_percent: 100 as _, preferred, stretch }
1118}
1119
1120pub fn box_layout_info_ortho(cells: Slice<LayoutItemInfo>, padding: &Padding) -> LayoutInfo {
1121 let extra_w = padding.begin + padding.end;
1122 let mut fold =
1123 cells.iter().fold(LayoutInfo { stretch: f32::MAX, ..Default::default() }, |a, b| {
1124 a.merge(&b.constraint)
1125 });
1126 fold.max = fold.max.max(fold.min);
1127 fold.preferred = fold.preferred.clamp(fold.min, fold.max);
1128 fold.min += extra_w;
1129 fold.max = Saturating::add(fold.max, extra_w);
1130 fold.preferred += extra_w;
1131 fold
1132}
1133
1134#[cfg(feature = "ffi")]
1135pub(crate) mod ffi {
1136 #![allow(unsafe_code)]
1137
1138 use super::*;
1139
1140 #[unsafe(no_mangle)]
1141 pub extern "C" fn slint_organize_grid_layout(
1142 input_data: Slice<GridLayoutInputData>,
1143 repeater_indices: Slice<u32>,
1144 repeater_steps: Slice<u32>,
1145 result: &mut GridLayoutOrganizedData,
1146 ) {
1147 *result = super::organize_grid_layout(input_data, repeater_indices, repeater_steps);
1148 }
1149
1150 #[unsafe(no_mangle)]
1151 pub extern "C" fn slint_organize_dialog_button_layout(
1152 input_data: Slice<GridLayoutInputData>,
1153 dialog_button_roles: Slice<DialogButtonRole>,
1154 result: &mut GridLayoutOrganizedData,
1155 ) {
1156 *result = super::organize_dialog_button_layout(input_data, dialog_button_roles);
1157 }
1158
1159 #[unsafe(no_mangle)]
1160 pub extern "C" fn slint_solve_grid_layout(
1161 data: &GridLayoutData,
1162 constraints: Slice<LayoutItemInfo>,
1163 orientation: Orientation,
1164 repeater_indices: Slice<u32>,
1165 repeater_steps: Slice<u32>,
1166 result: &mut SharedVector<Coord>,
1167 ) {
1168 *result = super::solve_grid_layout(
1169 data,
1170 constraints,
1171 orientation,
1172 repeater_indices,
1173 repeater_steps,
1174 )
1175 }
1176
1177 #[unsafe(no_mangle)]
1178 pub extern "C" fn slint_grid_layout_info(
1179 organized_data: &GridLayoutOrganizedData,
1180 constraints: Slice<LayoutItemInfo>,
1181 repeater_indices: Slice<u32>,
1182 repeater_steps: Slice<u32>,
1183 spacing: Coord,
1184 padding: &Padding,
1185 orientation: Orientation,
1186 ) -> LayoutInfo {
1187 super::grid_layout_info(
1188 organized_data.clone(),
1189 constraints,
1190 repeater_indices,
1191 repeater_steps,
1192 spacing,
1193 padding,
1194 orientation,
1195 )
1196 }
1197
1198 #[unsafe(no_mangle)]
1199 pub extern "C" fn slint_solve_box_layout(
1200 data: &BoxLayoutData,
1201 repeater_indices: Slice<u32>,
1202 result: &mut SharedVector<Coord>,
1203 ) {
1204 *result = super::solve_box_layout(data, repeater_indices)
1205 }
1206
1207 #[unsafe(no_mangle)]
1208 pub extern "C" fn slint_box_layout_info(
1210 cells: Slice<LayoutItemInfo>,
1211 spacing: Coord,
1212 padding: &Padding,
1213 alignment: LayoutAlignment,
1214 ) -> LayoutInfo {
1215 super::box_layout_info(cells, spacing, padding, alignment)
1216 }
1217
1218 #[unsafe(no_mangle)]
1219 pub extern "C" fn slint_box_layout_info_ortho(
1221 cells: Slice<LayoutItemInfo>,
1222 padding: &Padding,
1223 ) -> LayoutInfo {
1224 super::box_layout_info_ortho(cells, padding)
1225 }
1226}
1227
1228#[cfg(test)]
1229mod tests {
1230 use super::*;
1231
1232 fn collect_from_organized_data(
1233 organized_data: &GridLayoutOrganizedData,
1234 num_cells: usize,
1235 repeater_indices: Slice<u32>,
1236 repeater_steps: Slice<u32>,
1237 ) -> Vec<(u16, u16, u16, u16)> {
1238 let mut result = Vec::new();
1239 for i in 0..num_cells {
1240 let col_and_span = organized_data.col_or_row_and_span(
1241 i,
1242 Orientation::Horizontal,
1243 &repeater_indices,
1244 &repeater_steps,
1245 );
1246 let row_and_span = organized_data.col_or_row_and_span(
1247 i,
1248 Orientation::Vertical,
1249 &repeater_indices,
1250 &repeater_steps,
1251 );
1252 result.push((col_and_span.0, col_and_span.1, row_and_span.0, row_and_span.1));
1253 }
1254 result
1255 }
1256
1257 #[test]
1258 fn test_organized_data_generator_2_fixed_cells() {
1259 let mut result = GridLayoutOrganizedData::default();
1261 let num_cells = 2;
1262 result.resize(num_cells * 4, 0 as _);
1263 let mut generator = OrganizedDataGenerator::new(&[], &[], &mut result);
1264 generator.add(0, 1, 0, 1);
1265 generator.add(1, 2, 0, 3);
1266 assert_eq!(result.as_slice(), &[0, 1, 0, 1, 1, 2, 0, 3]);
1267
1268 let repeater_indices = Slice::from_slice(&[]);
1269 let empty_steps = Slice::from_slice(&[]);
1270 let collected_data =
1271 collect_from_organized_data(&result, num_cells, repeater_indices, empty_steps);
1272 assert_eq!(collected_data.as_slice(), &[(0, 1, 0, 1), (1, 2, 0, 3)]);
1273
1274 assert_eq!(
1275 result.max_value(num_cells, Orientation::Horizontal, &repeater_indices, &empty_steps),
1276 3
1277 );
1278 assert_eq!(
1279 result.max_value(num_cells, Orientation::Vertical, &repeater_indices, &empty_steps),
1280 3
1281 );
1282 }
1283
1284 #[test]
1285 fn test_organized_data_generator_1_fixed_cell_1_repeater() {
1286 let mut result = GridLayoutOrganizedData::default();
1288 let num_cells = 4;
1289 let repeater_indices = &[1u32, 3u32];
1290 result.resize(num_cells * 4 + 2 * repeater_indices.len(), 0 as _);
1291 let mut generator = OrganizedDataGenerator::new(repeater_indices, &[], &mut result);
1292 generator.add(0, 1, 0, 2); generator.add(1, 2, 1, 3); generator.add(1, 1, 2, 4);
1295 generator.add(2, 2, 3, 5);
1296 assert_eq!(
1297 result.as_slice(),
1298 &[
1299 0, 1, 0, 2, 8, 9, 10, 11, 1, 2, 1, 3, 1, 1, 2, 4, 2, 2, 3, 5 ]
1303 );
1304 let repeater_indices = Slice::from_slice(repeater_indices);
1305 let empty_steps = Slice::from_slice(&[]);
1306 let collected_data =
1307 collect_from_organized_data(&result, num_cells, repeater_indices, empty_steps);
1308 assert_eq!(
1309 collected_data.as_slice(),
1310 &[(0, 1, 0, 2), (1, 2, 1, 3), (1, 1, 2, 4), (2, 2, 3, 5)]
1311 );
1312
1313 assert_eq!(
1314 result.max_value(num_cells, Orientation::Horizontal, &repeater_indices, &empty_steps),
1315 4
1316 );
1317 assert_eq!(
1318 result.max_value(num_cells, Orientation::Vertical, &repeater_indices, &empty_steps),
1319 8
1320 );
1321 }
1322
1323 #[test]
1324
1325 fn test_organize_data_with_auto_and_spans() {
1326 let auto = i_slint_common::ROW_COL_AUTO;
1327 let input = std::vec![
1328 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 2., rowspan: -1. },
1329 GridLayoutInputData { new_row: false, col: auto, row: auto, colspan: 1., rowspan: 2. },
1330 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 2., rowspan: 1. },
1331 GridLayoutInputData { new_row: true, col: -2., row: 80000., colspan: 2., rowspan: 1. },
1332 ];
1333 let repeater_indices = Slice::from_slice(&[]);
1334 let (organized_data, errors) = organize_grid_layout_impl(
1335 Slice::from_slice(&input),
1336 repeater_indices,
1337 Slice::from_slice(&[]),
1338 );
1339 assert_eq!(
1340 organized_data.as_slice(),
1341 &[
1342 0, 2, 0, 0, 2, 1, 0, 2, 0, 2, 1, 1, 0, 2, 65535, 1, ]
1347 );
1348 assert_eq!(errors.len(), 3);
1349 assert_eq!(errors[0], "cell rowspan -1 is negative, clamping to 0");
1351 assert_eq!(errors[1], "cell row 80000 is too large, clamping to 65535");
1352 assert_eq!(errors[2], "cell col -2 is negative, clamping to 0");
1353 let empty_steps = Slice::from_slice(&[]);
1354 let collected_data = collect_from_organized_data(
1355 &organized_data,
1356 input.len(),
1357 repeater_indices,
1358 empty_steps,
1359 );
1360 assert_eq!(
1361 collected_data.as_slice(),
1362 &[(0, 2, 0, 0), (2, 1, 0, 2), (0, 2, 1, 1), (0, 2, 65535, 1)]
1363 );
1364 assert_eq!(
1365 organized_data.max_value(3, Orientation::Horizontal, &repeater_indices, &empty_steps),
1366 3
1367 );
1368 assert_eq!(
1369 organized_data.max_value(3, Orientation::Vertical, &repeater_indices, &empty_steps),
1370 2
1371 );
1372 }
1373
1374 #[test]
1375 fn test_organize_data_1_empty_repeater() {
1376 let auto = i_slint_common::ROW_COL_AUTO;
1378 let cell =
1379 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. };
1380 let input = std::vec![cell];
1381 let repeater_indices = Slice::from_slice(&[1u32, 0u32]);
1382 let (organized_data, errors) = organize_grid_layout_impl(
1383 Slice::from_slice(&input),
1384 repeater_indices,
1385 Slice::from_slice(&[]),
1386 );
1387 assert_eq!(
1388 organized_data.as_slice(),
1389 &[
1390 0, 1, 0, 1, 0, 0, 0, 0
1392 ] );
1394 assert_eq!(errors.len(), 0);
1395 let empty_steps = Slice::from_slice(&[]);
1396 let collected_data = collect_from_organized_data(
1397 &organized_data,
1398 input.len(),
1399 repeater_indices,
1400 empty_steps,
1401 );
1402 assert_eq!(collected_data.as_slice(), &[(0, 1, 0, 1)]);
1403 assert_eq!(
1404 organized_data.max_value(1, Orientation::Horizontal, &repeater_indices, &empty_steps),
1405 1
1406 );
1407 }
1408
1409 #[test]
1410 fn test_organize_data_4_repeaters() {
1411 let auto = i_slint_common::ROW_COL_AUTO;
1412 let mut cell =
1413 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. };
1414 let mut input = std::vec![cell.clone()];
1415 for _ in 0..8 {
1416 cell.new_row = false;
1417 input.push(cell.clone());
1418 }
1419 let repeater_indices = Slice::from_slice(&[0u32, 0u32, 1u32, 4u32, 6u32, 2u32, 8u32, 0u32]);
1420 let (organized_data, errors) = organize_grid_layout_impl(
1421 Slice::from_slice(&input),
1422 repeater_indices,
1423 Slice::from_slice(&[]),
1424 );
1425 assert_eq!(
1426 organized_data.as_slice(),
1427 &[
1428 28, 29, 30, 31, 0, 1, 0, 1, 28, 29, 30, 31, 5, 1, 0, 1, 44, 45, 46, 47, 52, 53, 54, 55, 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 ]
1440 );
1441 assert_eq!(errors.len(), 0);
1442 let empty_steps = Slice::from_slice(&[]);
1443 let collected_data = collect_from_organized_data(
1444 &organized_data,
1445 input.len(),
1446 repeater_indices,
1447 empty_steps,
1448 );
1449 assert_eq!(
1450 collected_data.as_slice(),
1451 &[
1452 (0, 1, 0, 1),
1453 (1, 1, 0, 1),
1454 (2, 1, 0, 1),
1455 (3, 1, 0, 1),
1456 (4, 1, 0, 1),
1457 (5, 1, 0, 1),
1458 (6, 1, 0, 1),
1459 (7, 1, 0, 1),
1460 (8, 1, 0, 1),
1461 ]
1462 );
1463 let empty_steps = Slice::from_slice(&[]);
1464 assert_eq!(
1465 organized_data.max_value(
1466 input.len(),
1467 Orientation::Horizontal,
1468 &repeater_indices,
1469 &empty_steps
1470 ),
1471 9
1472 );
1473 }
1474
1475 #[test]
1476 fn test_organize_data_repeated_rows() {
1477 let auto = i_slint_common::ROW_COL_AUTO;
1478 let mut input = Vec::new();
1479 let num_rows: u32 = 3;
1480 let num_columns: u32 = 2;
1481 for _ in 0..num_rows {
1483 let mut cell = GridLayoutInputData {
1484 new_row: true,
1485 col: auto,
1486 row: auto,
1487 colspan: 1.,
1488 rowspan: 1.,
1489 };
1490 input.push(cell.clone());
1491 cell.new_row = false;
1492 input.push(cell.clone());
1493 }
1494 let repeater_indices_arr = [0_u32, num_rows];
1496 let repeater_steps_arr = [num_columns];
1497 let repeater_steps = Slice::from_slice(&repeater_steps_arr);
1498 let repeater_indices = Slice::from_slice(&repeater_indices_arr);
1499 let (organized_data, errors) =
1500 organize_grid_layout_impl(Slice::from_slice(&input), repeater_indices, repeater_steps);
1501 assert_eq!(
1502 organized_data.as_slice(),
1503 &[
1504 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 2, 1, 1, 1, 2, 1, ]
1513 );
1514 assert_eq!(errors.len(), 0);
1515 let collected_data = collect_from_organized_data(
1516 &organized_data,
1517 input.len(),
1518 repeater_indices,
1519 repeater_steps,
1520 );
1521 assert_eq!(
1522 collected_data.as_slice(),
1523 &[(0, 1, 0, 1), (1, 1, 0, 1), (0, 1, 1, 1), (1, 1, 1, 1), (0, 1, 2, 1), (1, 1, 2, 1),]
1525 );
1526 assert_eq!(
1527 organized_data.max_value(
1528 input.len(),
1529 Orientation::Horizontal,
1530 &repeater_indices,
1531 &repeater_steps
1532 ),
1533 2
1534 );
1535 assert_eq!(
1536 organized_data.max_value(
1537 input.len(),
1538 Orientation::Vertical,
1539 &repeater_indices,
1540 &repeater_steps
1541 ),
1542 3
1543 );
1544
1545 let mut layout_cache_v = SharedVector::<Coord>::default();
1547 layout_cache_v.resize((num_columns * 2 + num_columns * num_rows * 2) as usize, 0 as _);
1549 let mut generator = LayoutCacheGenerator::new(
1550 repeater_indices.as_slice(),
1551 repeater_steps.as_slice(),
1552 &mut layout_cache_v,
1553 );
1554 generator.add(0., 50.);
1556 generator.add(0., 50.);
1557 generator.add(50., 50.);
1559 generator.add(50., 50.);
1560 generator.add(100., 50.);
1562 generator.add(100., 50.);
1563 assert_eq!(
1564 layout_cache_v.as_slice(),
1565 &[
1566 4., 5., 6., 7., 0., 50., 0., 50., 50., 50., 50., 50., 100., 50., 100., 50., ]
1571 );
1572
1573 let layout_cache_v_access = |index: usize, repeater_index: usize, step: usize| -> Coord {
1574 let entries_per_item = 2usize;
1575 let offset = repeater_index;
1576 *layout_cache_v
1578 .get((layout_cache_v[index] as usize) + offset as usize * entries_per_item * step)
1579 .unwrap()
1580 };
1581 assert_eq!(layout_cache_v_access(0, 0, 2), 0.);
1583 assert_eq!(layout_cache_v_access(0, 1, 2), 50.);
1584 assert_eq!(layout_cache_v_access(0, 2, 2), 100.);
1585 assert_eq!(layout_cache_v_access(1 * 2, 0, 2), 0.);
1587 assert_eq!(layout_cache_v_access(1 * 2, 1, 2), 50.);
1588 assert_eq!(layout_cache_v_access(1 * 2, 2, 2), 100.);
1589 }
1590
1591 #[test]
1592 fn test_organize_data_repeated_rows_multiple_repeaters() {
1593 let auto = i_slint_common::ROW_COL_AUTO;
1594 let mut input = Vec::new();
1595 let num_rows: u32 = 5;
1596 let mut cell =
1597 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. };
1598 for _ in 0..3 {
1600 cell.new_row = true;
1601 input.push(cell.clone());
1602 cell.new_row = false;
1603 input.push(cell.clone());
1604 }
1605 for _ in 0..2 {
1607 cell.new_row = true;
1608 input.push(cell.clone());
1609 cell.new_row = false;
1610 input.push(cell.clone());
1611 cell.new_row = false;
1612 input.push(cell.clone());
1613 }
1614 let repeater_indices_arr = [0_u32, 3, 6, 2];
1617 let repeater_steps_arr = [2, 3];
1618 let repeater_steps = Slice::from_slice(&repeater_steps_arr);
1619 let repeater_indices = Slice::from_slice(&repeater_indices_arr);
1620 let (organized_data, errors) =
1621 organize_grid_layout_impl(Slice::from_slice(&input), repeater_indices, repeater_steps);
1622 assert_eq!(
1623 organized_data.as_slice(),
1624 &[
1625 20, 21, 22, 23, 24, 25, 26, 27, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 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, ]
1645 );
1646 assert_eq!(errors.len(), 0);
1647 let collected_data = collect_from_organized_data(
1648 &organized_data,
1649 input.len(),
1650 repeater_indices,
1651 repeater_steps,
1652 );
1653 assert_eq!(
1654 collected_data.as_slice(),
1655 &[
1657 (0, 1, 0, 1),
1658 (1, 1, 0, 1),
1659 (0, 1, 1, 1),
1660 (1, 1, 1, 1),
1661 (0, 1, 2, 1),
1662 (1, 1, 2, 1),
1663 (0, 1, 3, 1),
1664 (1, 1, 3, 1),
1665 (2, 1, 3, 1),
1666 (0, 1, 4, 1),
1667 (1, 1, 4, 1),
1668 (2, 1, 4, 1)
1669 ]
1670 );
1671 assert_eq!(
1672 organized_data.max_value(
1673 input.len(),
1674 Orientation::Horizontal,
1675 &repeater_indices,
1676 &repeater_steps
1677 ),
1678 3 );
1680 assert_eq!(
1681 organized_data.max_value(
1682 input.len(),
1683 Orientation::Vertical,
1684 &repeater_indices,
1685 &repeater_steps
1686 ),
1687 num_rows as u16 );
1689
1690 let mut layout_cache_v = SharedVector::<Coord>::default();
1692 layout_cache_v.resize((5 * 2 + (3 * 2 + 2 * 3) * 2) as usize, 0 as _);
1694 let mut generator = LayoutCacheGenerator::new(
1695 repeater_indices.as_slice(),
1696 repeater_steps.as_slice(),
1697 &mut layout_cache_v,
1698 );
1699 generator.add(0., 50.);
1701 generator.add(0., 50.);
1702 generator.add(50., 50.);
1704 generator.add(50., 50.);
1705 generator.add(100., 50.);
1707 generator.add(100., 50.);
1708 generator.add(150., 50.);
1710 generator.add(150., 50.);
1711 generator.add(150., 50.);
1712 generator.add(200., 50.);
1714 generator.add(200., 50.);
1715 generator.add(200., 50.);
1716 assert_eq!(
1717 layout_cache_v.as_slice(),
1718 &[
1719 10., 11., 12., 13., 22., 23., 24., 25., 26., 27., 0., 50., 0., 50., 50., 50., 50., 50., 100., 50., 100., 50., 150., 50., 150., 50., 150., 50., 200., 50., 200., 50., 200., 50., ]
1727 );
1728
1729 let layout_cache_v_access = |index: usize, repeater_index: usize, step: usize| -> Coord {
1730 let entries_per_item = 2usize;
1731 let offset = repeater_index;
1732 *layout_cache_v
1734 .get((layout_cache_v[index] as usize) + offset as usize * entries_per_item * step)
1735 .unwrap()
1736 };
1737 assert_eq!(layout_cache_v_access(0, 0, 2), 0.);
1739 assert_eq!(layout_cache_v_access(0, 1, 2), 50.);
1740 assert_eq!(layout_cache_v_access(0, 2, 2), 100.);
1741 assert_eq!(layout_cache_v_access(1 * 2, 0, 2), 0.);
1743 assert_eq!(layout_cache_v_access(1 * 2, 1, 2), 50.);
1744 assert_eq!(layout_cache_v_access(1 * 2, 2, 2), 100.);
1745 }
1746
1747 #[test]
1748 fn test_layout_cache_generator_2_fixed_cells() {
1749 let mut result = SharedVector::<Coord>::default();
1751 result.resize(2 * 2, 0 as _);
1752 let mut generator = LayoutCacheGenerator::new(&[], &[], &mut result);
1753 generator.add(0., 50.); generator.add(80., 50.); assert_eq!(result.as_slice(), &[0., 50., 80., 50.]);
1756 }
1757
1758 #[test]
1759 fn test_layout_cache_generator_1_fixed_cell_1_repeater() {
1760 let mut result = SharedVector::<Coord>::default();
1762 let repeater_indices = &[1, 3];
1763 result.resize(4 * 2 + repeater_indices.len(), 0 as _);
1764 let mut generator = LayoutCacheGenerator::new(repeater_indices, &[], &mut result);
1765 generator.add(0., 50.); generator.add(80., 50.); generator.add(160., 50.);
1768 generator.add(240., 50.);
1769 assert_eq!(
1770 result.as_slice(),
1771 &[
1772 0., 50., 4., 5., 80., 50., 160., 50., 240., 50. ]
1776 );
1777 }
1778
1779 #[test]
1780 fn test_layout_cache_generator_4_repeaters() {
1781 let mut result = SharedVector::<Coord>::default();
1783 let repeater_indices = &[1, 0, 1, 4, 6, 2, 8, 0];
1784 result.resize(8 * 2 + repeater_indices.len(), 0 as _);
1785 let mut generator = LayoutCacheGenerator::new(repeater_indices, &[], &mut result);
1786 generator.add(0., 50.); generator.add(80., 10.); generator.add(160., 10.);
1789 generator.add(240., 10.);
1790 generator.add(320., 10.); generator.add(400., 80.); generator.add(500., 20.); generator.add(600., 20.); assert_eq!(
1795 result.as_slice(),
1796 &[
1797 0., 50., 12., 13., 12., 13., 400., 80., 20., 21., 0., 0., 80., 10., 160., 10., 240., 10., 320., 10., 500., 20., 600., 20. ]
1806 );
1807 }
1808}