1use crate::items::{
9 DialogButtonRole, FlexboxLayoutAlignContent, FlexboxLayoutAlignItems, FlexboxLayoutAlignSelf,
10 FlexboxLayoutDirection, FlexboxLayoutWrap, 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]
55 pub fn merge(&self, other: &LayoutInfo) -> Self {
56 Self {
57 min: self.min.max(other.min),
58 max: self.max.min(other.max),
59 min_percent: self.min_percent.max(other.min_percent),
60 max_percent: self.max_percent.min(other.max_percent),
61 preferred: self.preferred.max(other.preferred),
62 stretch: self.stretch.min(other.stretch),
63 }
64 }
65
66 #[must_use]
68 pub fn preferred_bounded(&self) -> Coord {
69 self.preferred.min(self.max).max(self.min)
70 }
71}
72
73impl core::ops::Add for LayoutInfo {
74 type Output = Self;
75
76 fn add(self, rhs: Self) -> Self::Output {
77 self.merge(&rhs)
78 }
79}
80
81pub fn min_max_size_for_layout_constraints(
83 constraints_horizontal: LayoutInfo,
84 constraints_vertical: LayoutInfo,
85) -> (Option<crate::api::LogicalSize>, Option<crate::api::LogicalSize>) {
86 let min_width = constraints_horizontal.min.min(constraints_horizontal.max) as f32;
87 let min_height = constraints_vertical.min.min(constraints_vertical.max) as f32;
88 let max_width = constraints_horizontal.max.max(constraints_horizontal.min) as f32;
89 let max_height = constraints_vertical.max.max(constraints_vertical.min) as f32;
90
91 let min_size = if min_width > 0. || min_height > 0. || cfg!(target_arch = "wasm32") {
95 Some(crate::api::LogicalSize::new(min_width, min_height))
96 } else {
97 None
98 };
99
100 let max_size = if (max_width > 0.
101 && max_height > 0.
102 && (max_width < i32::MAX as f32 || max_height < i32::MAX as f32))
103 || cfg!(target_arch = "wasm32")
104 {
105 let window_size_max = 16_777_215.;
107 Some(crate::api::LogicalSize::new(
108 max_width.min(window_size_max),
109 max_height.min(window_size_max),
110 ))
111 } else {
112 None
113 };
114
115 (min_size, max_size)
116}
117
118trait Saturating {
121 fn add(_: Self, _: Self) -> Self;
122}
123impl Saturating for i32 {
124 #[inline]
125 fn add(a: Self, b: Self) -> Self {
126 a.saturating_add(b)
127 }
128}
129impl Saturating for f32 {
130 #[inline]
131 fn add(a: Self, b: Self) -> Self {
132 a + b
133 }
134}
135
136mod grid_internal {
137 use super::*;
138
139 fn order_coord<T: PartialOrd>(a: &T, b: &T) -> core::cmp::Ordering {
140 a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)
141 }
142
143 #[derive(Debug, Clone)]
144 pub struct LayoutData {
145 pub min: Coord,
147 pub max: Coord,
148 pub pref: Coord,
149 pub stretch: f32,
150
151 pub pos: Coord,
153 pub size: Coord,
154 }
155
156 impl Default for LayoutData {
157 fn default() -> Self {
158 LayoutData {
159 min: 0 as _,
160 max: Coord::MAX,
161 pref: 0 as _,
162 stretch: f32::MAX,
163 pos: 0 as _,
164 size: 0 as _,
165 }
166 }
167 }
168
169 trait Adjust {
170 fn can_grow(_: &LayoutData) -> Coord;
171 fn to_distribute(expected_size: Coord, current_size: Coord) -> Coord;
172 fn distribute(_: &mut LayoutData, val: Coord);
173 }
174
175 struct Grow;
176 impl Adjust for Grow {
177 fn can_grow(it: &LayoutData) -> Coord {
178 it.max - it.size
179 }
180
181 fn to_distribute(expected_size: Coord, current_size: Coord) -> Coord {
182 expected_size - current_size
183 }
184
185 fn distribute(it: &mut LayoutData, val: Coord) {
186 it.size += val;
187 }
188 }
189
190 struct Shrink;
191 impl Adjust for Shrink {
192 fn can_grow(it: &LayoutData) -> Coord {
193 it.size - it.min
194 }
195
196 fn to_distribute(expected_size: Coord, current_size: Coord) -> Coord {
197 current_size - expected_size
198 }
199
200 fn distribute(it: &mut LayoutData, val: Coord) {
201 it.size -= val;
202 }
203 }
204
205 #[allow(clippy::unnecessary_cast)] fn adjust_items<A: Adjust>(data: &mut [LayoutData], size_without_spacing: Coord) -> Option<()> {
207 loop {
208 let size_cannot_grow: Coord = data
209 .iter()
210 .filter(|it| A::can_grow(it) <= 0 as _)
211 .map(|it| it.size)
212 .fold(0 as Coord, Saturating::add);
213
214 let total_stretch: f32 =
215 data.iter().filter(|it| A::can_grow(it) > 0 as _).map(|it| it.stretch).sum();
216
217 let actual_stretch = |s: f32| if total_stretch <= 0. { 1. } else { s };
218
219 let max_grow = data
220 .iter()
221 .filter(|it| A::can_grow(it) > 0 as _)
222 .map(|it| A::can_grow(it) as f32 / actual_stretch(it.stretch))
223 .min_by(order_coord)?;
224
225 let current_size: Coord = data
226 .iter()
227 .filter(|it| A::can_grow(it) > 0 as _)
228 .map(|it| it.size)
229 .fold(0 as _, Saturating::add);
230
231 let to_distribute =
233 A::to_distribute(size_without_spacing, size_cannot_grow + current_size) as f32;
234 if to_distribute <= 0. || max_grow <= 0. {
235 return Some(());
236 }
237
238 let grow = if total_stretch <= 0. {
239 to_distribute
240 / (data.iter().filter(|it| A::can_grow(it) > 0 as _).count() as Coord) as f32
241 } else {
242 to_distribute / total_stretch
243 }
244 .min(max_grow);
245
246 let mut distributed = 0 as Coord;
247 for it in data.iter_mut().filter(|it| A::can_grow(it) > 0 as Coord) {
248 let val = (grow * actual_stretch(it.stretch)) as Coord;
249 A::distribute(it, val);
250 distributed += val;
251 }
252
253 if distributed <= 0 as Coord {
254 if let Some(it) = data
257 .iter_mut()
258 .filter(|it| A::can_grow(it) > 0 as _)
259 .max_by(|a, b| actual_stretch(a.stretch).total_cmp(&b.stretch))
260 {
261 A::distribute(it, to_distribute as Coord);
262 }
263 return Some(());
264 }
265 }
266 }
267
268 pub fn layout_items(data: &mut [LayoutData], start_pos: Coord, size: Coord, spacing: Coord) {
269 let size_without_spacing = size - spacing * (data.len() - 1) as Coord;
270
271 let mut pref = 0 as Coord;
272 for it in data.iter_mut() {
273 it.size = it.pref;
274 pref += it.pref;
275 }
276 if size_without_spacing >= pref {
277 adjust_items::<Grow>(data, size_without_spacing);
278 } else if size_without_spacing < pref {
279 adjust_items::<Shrink>(data, size_without_spacing);
280 }
281
282 let mut pos = start_pos;
283 for it in data.iter_mut() {
284 it.pos = pos;
285 pos = Saturating::add(pos, Saturating::add(it.size, spacing));
286 }
287 }
288
289 #[test]
290 #[allow(clippy::float_cmp)] fn test_layout_items() {
292 let my_items = &mut [
293 LayoutData { min: 100., max: 200., pref: 100., stretch: 1., ..Default::default() },
294 LayoutData { min: 50., max: 300., pref: 100., stretch: 1., ..Default::default() },
295 LayoutData { min: 50., max: 150., pref: 100., stretch: 1., ..Default::default() },
296 ];
297
298 layout_items(my_items, 100., 650., 0.);
299 assert_eq!(my_items[0].size, 200.);
300 assert_eq!(my_items[1].size, 300.);
301 assert_eq!(my_items[2].size, 150.);
302
303 layout_items(my_items, 100., 200., 0.);
304 assert_eq!(my_items[0].size, 100.);
305 assert_eq!(my_items[1].size, 50.);
306 assert_eq!(my_items[2].size, 50.);
307
308 layout_items(my_items, 100., 300., 0.);
309 assert_eq!(my_items[0].size, 100.);
310 assert_eq!(my_items[1].size, 100.);
311 assert_eq!(my_items[2].size, 100.);
312 }
313
314 pub fn to_layout_data(
317 organized_data: &GridLayoutOrganizedData,
318 constraints: Slice<LayoutItemInfo>,
319 orientation: Orientation,
320 repeater_indices: Slice<u32>,
321 repeater_steps: Slice<u32>,
322 spacing: Coord,
323 size: Option<Coord>,
324 ) -> Vec<LayoutData> {
325 assert!(organized_data.len().is_multiple_of(4));
326 let num = organized_data.max_value(
327 constraints.len(),
328 orientation,
329 &repeater_indices,
330 &repeater_steps,
331 ) as usize;
332 if num < 1 {
333 return Default::default();
334 }
335 let marker_for_empty = -1.;
336 let mut layout_data = alloc::vec![grid_internal::LayoutData { max: 0 as Coord, stretch: marker_for_empty, ..Default::default() }; num];
337 let mut has_spans = false;
338 for (idx, cell_data) in constraints.iter().enumerate() {
339 let constraint = &cell_data.constraint;
340 let mut max = constraint.max;
341 if let Some(size) = size {
342 max = max.min(size * constraint.max_percent / 100 as Coord);
343 }
344 let (col_or_row, span) = organized_data.col_or_row_and_span(
345 idx,
346 orientation,
347 &repeater_indices,
348 &repeater_steps,
349 );
350 for c in 0..(span as usize) {
351 let cdata = &mut layout_data[col_or_row as usize + c];
352 if cdata.stretch == marker_for_empty {
355 cdata.max = Coord::MAX;
356 cdata.stretch = 1.;
357 }
358 cdata.max = cdata.max.min(max);
359 }
360 if span == 1 {
361 let mut min = constraint.min;
362 if let Some(size) = size {
363 min = min.max(size * constraint.min_percent / 100 as Coord);
364 }
365 let pref = constraint.preferred.min(max).max(min);
366 let cdata = &mut layout_data[col_or_row as usize];
367 cdata.min = cdata.min.max(min);
368 cdata.pref = cdata.pref.max(pref);
369 cdata.stretch = cdata.stretch.min(constraint.stretch);
370 } else {
371 has_spans = true;
372 }
373 }
374 if has_spans {
375 for (idx, cell_data) in constraints.iter().enumerate() {
376 let constraint = &cell_data.constraint;
377 let (col_or_row, span) = organized_data.col_or_row_and_span(
378 idx,
379 orientation,
380 &repeater_indices,
381 &repeater_steps,
382 );
383 if span > 1 {
384 let span_data =
385 &mut layout_data[(col_or_row as usize)..(col_or_row + span) as usize];
386
387 let mut min = constraint.min;
389 if let Some(size) = size {
390 min = min.max(size * constraint.min_percent / 100 as Coord);
391 }
392 grid_internal::layout_items(span_data, 0 as _, min, spacing);
393 for cdata in span_data.iter_mut() {
394 if cdata.min < cdata.size {
395 cdata.min = cdata.size;
396 }
397 }
398
399 let mut max = constraint.max;
401 if let Some(size) = size {
402 max = max.min(size * constraint.max_percent / 100 as Coord);
403 }
404 grid_internal::layout_items(span_data, 0 as _, max, spacing);
405 for cdata in span_data.iter_mut() {
406 if cdata.max > cdata.size {
407 cdata.max = cdata.size;
408 }
409 }
410
411 grid_internal::layout_items(span_data, 0 as _, constraint.preferred, spacing);
413 for cdata in span_data.iter_mut() {
414 cdata.pref = cdata.pref.max(cdata.size).min(cdata.max).max(cdata.min);
415 }
416
417 let total_stretch: f32 = span_data.iter().map(|c| c.stretch).sum();
419 if total_stretch > constraint.stretch {
420 for cdata in span_data.iter_mut() {
421 cdata.stretch *= constraint.stretch / total_stretch;
422 }
423 }
424 }
425 }
426 }
427 for cdata in layout_data.iter_mut() {
428 if cdata.stretch == marker_for_empty {
429 cdata.stretch = 0.;
430 }
431 }
432 layout_data
433 }
434}
435
436#[repr(C)]
437pub struct Constraint {
438 pub min: Coord,
439 pub max: Coord,
440}
441
442impl Default for Constraint {
443 fn default() -> Self {
444 Constraint { min: 0 as Coord, max: Coord::MAX }
445 }
446}
447
448#[repr(C)]
449#[derive(Copy, Clone, Debug, Default)]
450pub struct Padding {
451 pub begin: Coord,
452 pub end: Coord,
453}
454
455#[repr(C)]
456#[derive(Debug)]
457pub struct GridLayoutData {
459 pub size: Coord,
460 pub spacing: Coord,
461 pub padding: Padding,
462 pub organized_data: GridLayoutOrganizedData,
463}
464
465#[repr(C)]
468#[derive(Debug, Clone)]
469pub struct GridLayoutInputData {
470 pub new_row: bool,
472 pub col: f32,
476 pub row: f32,
477 pub colspan: f32,
480 pub rowspan: f32,
481}
482
483impl Default for GridLayoutInputData {
484 fn default() -> Self {
485 Self {
486 new_row: false,
487 col: i_slint_common::ROW_COL_AUTO,
488 row: i_slint_common::ROW_COL_AUTO,
489 colspan: 1.0,
490 rowspan: 1.0,
491 }
492 }
493}
494
495pub type GridLayoutOrganizedData = SharedVector<u16>;
498
499impl GridLayoutOrganizedData {
500 fn push_cell(&mut self, col: u16, colspan: u16, row: u16, rowspan: u16) {
501 self.push(col);
502 self.push(colspan);
503 self.push(row);
504 self.push(rowspan);
505 }
506
507 fn col_or_row_and_span(
508 &self,
509 cell_number: usize,
510 orientation: Orientation,
511 repeater_indices: &Slice<u32>,
512 repeater_steps: &Slice<u32>,
513 ) -> (u16, u16) {
514 let mut final_idx = 0;
523 let mut cell_nr_adj = 0i32; let cell_number = cell_number as i32;
525 for rep_idx in 0..(repeater_indices.len() / 2) {
527 let ri_start_cell = repeater_indices[rep_idx * 2] as i32;
528 if cell_number < ri_start_cell {
529 break;
530 }
531 let ri_cell_count = repeater_indices[rep_idx * 2 + 1] as i32;
532 let step = repeater_steps.get(rep_idx).copied().unwrap_or(1) as i32;
533 let cells_in_repeater = ri_cell_count * step;
534 if cells_in_repeater > 0
535 && cell_number >= ri_start_cell
536 && cell_number < ri_start_cell + cells_in_repeater
537 {
538 let cell_in_rep = cell_number - ri_start_cell;
539 let row_in_rep = cell_in_rep / step;
540 let col_in_rep = cell_in_rep % step;
541 let jump_pos = (ri_start_cell - cell_nr_adj) as usize * 4;
542 let data_base = self[jump_pos] as usize;
543 let stride = self[jump_pos + 1] as usize;
544 final_idx = data_base + row_in_rep as usize * stride + col_in_rep as usize * 4;
545 break;
546 }
547 cell_nr_adj += cells_in_repeater - 1;
550 }
551 if final_idx == 0 {
552 final_idx = ((cell_number - cell_nr_adj) * 4) as usize;
553 }
554 let offset = if orientation == Orientation::Horizontal { 0 } else { 2 };
555 (self[final_idx + offset], self[final_idx + offset + 1])
556 }
557
558 fn max_value(
559 &self,
560 num_cells: usize,
561 orientation: Orientation,
562 repeater_indices: &Slice<u32>,
563 repeater_steps: &Slice<u32>,
564 ) -> u16 {
565 let mut max = 0;
566 for idx in 0..num_cells {
569 let (col_or_row, span) =
570 self.col_or_row_and_span(idx, orientation, repeater_indices, repeater_steps);
571 max = max.max(col_or_row + span.max(1));
572 }
573 max
574 }
575}
576
577struct OrganizedDataGenerator<'a> {
584 repeater_indices: &'a [u32],
586 repeater_steps: &'a [u32],
587 counter: usize,
589 repeat_u16_offset: usize,
591 next_rep: usize,
593 current_offset: usize,
595 result: &'a mut GridLayoutOrganizedData,
597}
598
599impl<'a> OrganizedDataGenerator<'a> {
600 fn new(
601 repeater_indices: &'a [u32],
602 repeater_steps: &'a [u32],
603 static_cells: usize,
604 num_repeaters: usize,
605 total_repeated_cells_count: usize,
606 result: &'a mut GridLayoutOrganizedData,
607 ) -> Self {
608 result.resize((static_cells + num_repeaters + total_repeated_cells_count) * 4, 0 as _);
609 let repeat_u16_offset = (static_cells + num_repeaters) * 4;
610 Self {
611 repeater_indices,
612 repeater_steps,
613 counter: 0,
614 repeat_u16_offset,
615 next_rep: 0,
616 current_offset: 0,
617 result,
618 }
619 }
620 fn add(&mut self, col: u16, colspan: u16, row: u16, rowspan: u16) {
621 let res = self.result.make_mut_slice();
622 loop {
623 if let Some(nr) = self.repeater_indices.get(self.next_rep * 2) {
624 let nr = *nr as usize;
625 let step = self.repeater_steps.get(self.next_rep).copied().unwrap_or(1) as usize;
626 let rep_count = self.repeater_indices[self.next_rep * 2 + 1] as usize;
627
628 if nr == self.counter {
629 let data_u16_start = self.repeat_u16_offset;
631 let stride = step * 4;
632
633 res[self.current_offset * 4] = data_u16_start as _;
635 res[self.current_offset * 4 + 1] = stride as _;
636 self.current_offset += 1;
637 }
638 if self.counter >= nr {
639 let cells_in_repeater = rep_count * step;
640 if self.counter - nr == cells_in_repeater {
641 self.repeat_u16_offset += cells_in_repeater * 4;
643 self.next_rep += 1;
644 continue;
645 }
646 let cell_in_rep = self.counter - nr;
648 let row_in_rep = cell_in_rep / step;
649 let col_in_rep = cell_in_rep % step;
650 let data_u16_start = self.repeat_u16_offset;
651 let u16_pos = data_u16_start + row_in_rep * step * 4 + col_in_rep * 4;
652 res[u16_pos] = col;
653 res[u16_pos + 1] = colspan;
654 res[u16_pos + 2] = row;
655 res[u16_pos + 3] = rowspan;
656 self.counter += 1;
657 return;
658 }
659 }
660 res[self.current_offset * 4] = col;
662 res[self.current_offset * 4 + 1] = colspan;
663 res[self.current_offset * 4 + 2] = row;
664 res[self.current_offset * 4 + 3] = rowspan;
665 self.current_offset += 1;
666 self.counter += 1;
667 return;
668 }
669 }
670}
671
672pub fn organize_dialog_button_layout(
675 input_data: Slice<GridLayoutInputData>,
676 dialog_button_roles: Slice<DialogButtonRole>,
677) -> GridLayoutOrganizedData {
678 let mut organized_data = GridLayoutOrganizedData::default();
679 organized_data.reserve(input_data.len() * 4);
680
681 #[cfg(feature = "std")]
682 fn is_kde() -> bool {
683 std::env::var("XDG_CURRENT_DESKTOP")
685 .ok()
686 .and_then(|v| v.as_bytes().first().copied())
687 .is_some_and(|x| x.eq_ignore_ascii_case(&b'K'))
688 }
689 #[cfg(not(feature = "std"))]
690 let is_kde = || true;
691
692 let expected_order: &[DialogButtonRole] = match crate::detect_operating_system() {
693 crate::items::OperatingSystemType::Windows => {
694 &[
695 DialogButtonRole::Reset,
696 DialogButtonRole::None, DialogButtonRole::Accept,
698 DialogButtonRole::Action,
699 DialogButtonRole::Reject,
700 DialogButtonRole::Apply,
701 DialogButtonRole::Help,
702 ]
703 }
704 crate::items::OperatingSystemType::Macos | crate::items::OperatingSystemType::Ios => {
705 &[
706 DialogButtonRole::Help,
707 DialogButtonRole::Reset,
708 DialogButtonRole::Apply,
709 DialogButtonRole::Action,
710 DialogButtonRole::None, DialogButtonRole::Reject,
712 DialogButtonRole::Accept,
713 ]
714 }
715 _ if is_kde() => {
716 &[
718 DialogButtonRole::Help,
719 DialogButtonRole::Reset,
720 DialogButtonRole::None, DialogButtonRole::Action,
722 DialogButtonRole::Accept,
723 DialogButtonRole::Apply,
724 DialogButtonRole::Reject,
725 ]
726 }
727 _ => {
728 &[
730 DialogButtonRole::Help,
731 DialogButtonRole::Reset,
732 DialogButtonRole::None, DialogButtonRole::Action,
734 DialogButtonRole::Accept,
735 DialogButtonRole::Apply,
736 DialogButtonRole::Reject,
737 ]
738 }
739 };
740
741 let mut column_for_input: Vec<usize> = Vec::with_capacity(dialog_button_roles.len());
743 for role in expected_order.iter() {
744 if role == &DialogButtonRole::None {
745 column_for_input.push(usize::MAX); continue;
747 }
748 for (idx, r) in dialog_button_roles.as_slice().iter().enumerate() {
749 if *r == *role {
750 column_for_input.push(idx);
751 }
752 }
753 }
754
755 for (input_index, cell) in input_data.as_slice().iter().enumerate() {
756 let col = column_for_input.iter().position(|&x| x == input_index);
757 if let Some(col) = col {
758 organized_data.push_cell(col as _, cell.colspan as _, cell.row as _, cell.rowspan as _);
759 } else {
760 organized_data.push_cell(
763 cell.col as _,
764 cell.colspan as _,
765 cell.row as _,
766 cell.rowspan as _,
767 );
768 }
769 }
770 organized_data
771}
772
773fn total_repeated_cells<'a>(repeater_indices: &'a [u32], repeater_steps: &'a [u32]) -> usize {
775 repeater_indices
776 .chunks(2)
777 .enumerate()
778 .map(|(i, chunk)| {
779 let count = chunk.get(1).copied().unwrap_or(0) as usize;
780 let step = repeater_steps.get(i).copied().unwrap_or(1) as usize;
781 count * step
782 })
783 .sum()
784}
785
786type Errors = Vec<String>;
787
788pub fn organize_grid_layout(
789 input_data: Slice<GridLayoutInputData>,
790 repeater_indices: Slice<u32>,
791 repeater_steps: Slice<u32>,
792) -> GridLayoutOrganizedData {
793 let (organized_data, errors) =
794 organize_grid_layout_impl(input_data, repeater_indices, repeater_steps);
795 for error in errors {
796 crate::debug_log!("Slint layout error: {}", error);
797 }
798 organized_data
799}
800
801fn organize_grid_layout_impl(
803 input_data: Slice<GridLayoutInputData>,
804 repeater_indices: Slice<u32>,
805 repeater_steps: Slice<u32>,
806) -> (GridLayoutOrganizedData, Errors) {
807 let mut organized_data = GridLayoutOrganizedData::default();
808 let num_repeaters = repeater_indices.len() / 2;
811 let total_repeated_cells =
812 total_repeated_cells(repeater_indices.as_slice(), repeater_steps.as_slice());
813 let static_cells = input_data.len() - total_repeated_cells;
814 let mut generator = OrganizedDataGenerator::new(
815 repeater_indices.as_slice(),
816 repeater_steps.as_slice(),
817 static_cells,
818 num_repeaters,
819 total_repeated_cells,
820 &mut organized_data,
821 );
822 let mut errors = Vec::new();
823
824 fn clamp_to_u16(value: f32, field_name: &str, errors: &mut Vec<String>) -> u16 {
825 if value < 0.0 {
826 errors.push(format!("cell {field_name} {value} is negative, clamping to 0"));
827 0
828 } else if value > u16::MAX as f32 {
829 errors
830 .push(format!("cell {field_name} {value} is too large, clamping to {}", u16::MAX));
831 u16::MAX
832 } else {
833 value as u16
834 }
835 }
836
837 let mut row = 0;
838 let mut col = 0;
839 let mut first = true;
840 for cell in input_data.as_slice().iter() {
841 if cell.new_row && !first {
842 row += 1;
843 col = 0;
844 }
845 first = false;
846
847 if cell.row != i_slint_common::ROW_COL_AUTO {
848 let cell_row = clamp_to_u16(cell.row, "row", &mut errors);
849 if row != cell_row {
850 row = cell_row;
851 col = 0;
852 }
853 }
854 if cell.col != i_slint_common::ROW_COL_AUTO {
855 col = clamp_to_u16(cell.col, "col", &mut errors);
856 }
857
858 let colspan = clamp_to_u16(cell.colspan, "colspan", &mut errors);
859 let rowspan = clamp_to_u16(cell.rowspan, "rowspan", &mut errors);
860 col = col.min(u16::MAX - colspan); generator.add(col, colspan, row, rowspan);
862 col += colspan;
863 }
864 (organized_data, errors)
865}
866
867struct LayoutCacheGenerator<'a> {
875 repeater_indices: &'a [u32],
877 counter: usize,
879 repeat_offset: usize,
881 next_rep: usize,
883 current_offset: usize,
885 result: &'a mut SharedVector<Coord>,
887}
888
889impl<'a> LayoutCacheGenerator<'a> {
890 fn new(repeater_indices: &'a [u32], result: &'a mut SharedVector<Coord>) -> Self {
891 let total_repeated_cells: usize = repeater_indices
892 .chunks(2)
893 .map(|chunk| chunk.get(1).copied().unwrap_or(0) as usize)
894 .sum();
895 assert!(result.len() >= total_repeated_cells * 2);
896 let repeat_offset = result.len() / 2 - total_repeated_cells;
897 Self { repeater_indices, counter: 0, repeat_offset, next_rep: 0, current_offset: 0, result }
898 }
899 fn add(&mut self, pos: Coord, size: Coord) {
900 let res = self.result.make_mut_slice();
901 let o = loop {
902 if let Some(nr) = self.repeater_indices.get(self.next_rep * 2) {
903 let nr = *nr as usize;
904 if nr == self.counter {
905 for o in 0..2 {
907 res[self.current_offset * 2 + o] = (self.repeat_offset * 2 + o) as _;
908 }
909 self.current_offset += 1;
910 }
911 if self.counter >= nr {
912 let rep_count = self.repeater_indices[self.next_rep * 2 + 1] as usize;
913 if self.counter - nr == rep_count {
914 self.repeat_offset += rep_count;
915 self.next_rep += 1;
916 continue;
917 }
918 let offset = self.repeat_offset + (self.counter - nr);
919 break offset;
920 }
921 }
922 self.current_offset += 1;
923 break self.current_offset - 1;
924 };
925 res[o * 2] = pos;
926 res[o * 2 + 1] = size;
927 self.counter += 1;
928 }
929}
930
931struct GridLayoutCacheGenerator<'a> {
935 repeater_indices: &'a [u32],
937 repeater_steps: &'a [u32],
938 counter: usize,
940 repeat_f32_offset: usize,
942 next_rep: usize,
944 current_offset: usize,
946 result: &'a mut SharedVector<Coord>,
948}
949
950impl<'a> GridLayoutCacheGenerator<'a> {
951 fn new(
952 repeater_indices: &'a [u32],
953 repeater_steps: &'a [u32],
954 static_cells: usize,
955 num_repeaters: usize,
956 total_repeated_cells_count: usize,
957 result: &'a mut SharedVector<Coord>,
958 ) -> Self {
959 result.resize((static_cells + num_repeaters + total_repeated_cells_count) * 2, 0 as _);
960 let repeat_f32_offset = (static_cells + num_repeaters) * 2;
961 Self {
962 repeater_indices,
963 repeater_steps,
964 counter: 0,
965 repeat_f32_offset,
966 next_rep: 0,
967 current_offset: 0,
968 result,
969 }
970 }
971 fn add(&mut self, pos: Coord, size: Coord) {
972 let res = self.result.make_mut_slice();
973 loop {
974 if let Some(nr) = self.repeater_indices.get(self.next_rep * 2) {
975 let nr = *nr as usize;
976 let step = self.repeater_steps.get(self.next_rep).copied().unwrap_or(1) as usize;
977 let rep_count = self.repeater_indices[self.next_rep * 2 + 1] as usize;
978
979 if nr == self.counter {
980 let data_f32_start = self.repeat_f32_offset;
982 let stride = step * 2;
983
984 res[self.current_offset * 2] = data_f32_start as _;
986 res[self.current_offset * 2 + 1] = stride as _;
987 self.current_offset += 1;
988 }
989 if self.counter >= nr {
990 let cells_in_repeater = rep_count * step;
991 if self.counter - nr == cells_in_repeater {
992 self.repeat_f32_offset += cells_in_repeater * 2;
994 self.next_rep += 1;
995 continue;
996 }
997 let cell_in_rep = self.counter - nr;
999 let row_in_rep = cell_in_rep / step;
1000 let col_in_rep = cell_in_rep % step;
1001 let data_f32_start = self.repeat_f32_offset;
1002 let f32_pos = data_f32_start + row_in_rep * step * 2 + col_in_rep * 2;
1003 res[f32_pos] = pos;
1004 res[f32_pos + 1] = size;
1005 self.counter += 1;
1006 return;
1007 }
1008 }
1009 res[self.current_offset * 2] = pos;
1011 res[self.current_offset * 2 + 1] = size;
1012 self.current_offset += 1;
1013 self.counter += 1;
1014 return;
1015 }
1016 }
1017}
1018
1019pub fn solve_grid_layout(
1022 data: &GridLayoutData,
1023 constraints: Slice<LayoutItemInfo>,
1024 orientation: Orientation,
1025 repeater_indices: Slice<u32>,
1026 repeater_steps: Slice<u32>,
1027) -> SharedVector<Coord> {
1028 let mut layout_data = grid_internal::to_layout_data(
1029 &data.organized_data,
1030 constraints,
1031 orientation,
1032 repeater_indices,
1033 repeater_steps,
1034 data.spacing,
1035 Some(data.size),
1036 );
1037
1038 if layout_data.is_empty() {
1039 return Default::default();
1040 }
1041
1042 grid_internal::layout_items(
1043 &mut layout_data,
1044 data.padding.begin,
1045 data.size - (data.padding.begin + data.padding.end),
1046 data.spacing,
1047 );
1048
1049 let mut result = SharedVector::<Coord>::default();
1050 let num_repeaters = repeater_indices.len() / 2;
1051 let total_repeated_cells =
1052 total_repeated_cells(repeater_indices.as_slice(), repeater_steps.as_slice());
1053 let static_cells = constraints.len() - total_repeated_cells;
1054 let mut generator = GridLayoutCacheGenerator::new(
1055 repeater_indices.as_slice(),
1056 repeater_steps.as_slice(),
1057 static_cells,
1058 num_repeaters,
1059 total_repeated_cells,
1060 &mut result,
1061 );
1062
1063 for idx in 0..constraints.len() {
1064 let (col_or_row, span) = data.organized_data.col_or_row_and_span(
1065 idx,
1066 orientation,
1067 &repeater_indices,
1068 &repeater_steps,
1069 );
1070 let cdata = &layout_data[col_or_row as usize];
1071 let size = if span > 0 {
1072 let last_cell = &layout_data[col_or_row as usize + span as usize - 1];
1073 last_cell.pos + last_cell.size - cdata.pos
1074 } else {
1075 0 as Coord
1076 };
1077 generator.add(cdata.pos, size);
1078 }
1079 result
1080}
1081
1082pub fn grid_layout_info(
1083 organized_data: GridLayoutOrganizedData, constraints: Slice<LayoutItemInfo>,
1085 repeater_indices: Slice<u32>,
1086 repeater_steps: Slice<u32>,
1087 spacing: Coord,
1088 padding: &Padding,
1089 orientation: Orientation,
1090) -> LayoutInfo {
1091 let layout_data = grid_internal::to_layout_data(
1092 &organized_data,
1093 constraints,
1094 orientation,
1095 repeater_indices,
1096 repeater_steps,
1097 spacing,
1098 None,
1099 );
1100 if layout_data.is_empty() {
1101 let mut info = LayoutInfo::default();
1102 info.min = padding.begin + padding.end;
1103 info.preferred = info.min;
1104 info.max = info.min;
1105 return info;
1106 }
1107 let spacing_w = spacing * (layout_data.len() - 1) as Coord + padding.begin + padding.end;
1108 let min = layout_data.iter().map(|data| data.min).sum::<Coord>() + spacing_w;
1109 let max = layout_data.iter().map(|data| data.max).fold(spacing_w, Saturating::add);
1110 let preferred = layout_data.iter().map(|data| data.pref).sum::<Coord>() + spacing_w;
1111 let stretch = layout_data.iter().map(|data| data.stretch).sum::<f32>();
1112 LayoutInfo { min, max, min_percent: 0 as _, max_percent: 100 as _, preferred, stretch }
1113}
1114
1115#[repr(C)]
1116#[derive(Debug)]
1117pub struct BoxLayoutData<'a> {
1121 pub size: Coord,
1122 pub spacing: Coord,
1123 pub padding: Padding,
1124 pub alignment: LayoutAlignment,
1125 pub cells: Slice<'a, LayoutItemInfo>,
1126}
1127
1128#[repr(C)]
1129#[derive(Debug)]
1130pub struct FlexboxLayoutData<'a> {
1132 pub width: Coord,
1133 pub height: Coord,
1134 pub spacing_h: Coord,
1135 pub spacing_v: Coord,
1136 pub padding_h: Padding,
1137 pub padding_v: Padding,
1138 pub alignment: LayoutAlignment,
1139 pub direction: FlexboxLayoutDirection,
1140 pub align_content: FlexboxLayoutAlignContent,
1141 pub align_items: FlexboxLayoutAlignItems,
1142 pub flex_wrap: FlexboxLayoutWrap,
1143 pub cells_h: Slice<'a, FlexboxLayoutItemInfo>,
1145 pub cells_v: Slice<'a, FlexboxLayoutItemInfo>,
1147}
1148
1149#[repr(C)]
1150#[derive(Debug, Clone, Default)]
1151pub struct LayoutItemInfo {
1153 pub constraint: LayoutInfo,
1154}
1155
1156#[repr(C)]
1157#[derive(Debug, Clone)]
1158pub struct FlexboxLayoutItemInfo {
1160 pub constraint: LayoutInfo,
1161 pub flex_grow: f32,
1163 pub flex_shrink: f32,
1165 pub flex_basis: Coord,
1167 pub flex_align_self: FlexboxLayoutAlignSelf,
1169 pub flex_order: i32,
1171}
1172
1173impl Default for FlexboxLayoutItemInfo {
1174 fn default() -> Self {
1175 Self {
1176 constraint: LayoutInfo::default(),
1177 flex_grow: 0.0,
1178 flex_shrink: 1.0,
1179 flex_basis: -1 as _,
1180 flex_align_self: FlexboxLayoutAlignSelf::Auto,
1181 flex_order: 0,
1182 }
1183 }
1184}
1185
1186impl From<LayoutItemInfo> for FlexboxLayoutItemInfo {
1187 fn from(info: LayoutItemInfo) -> Self {
1188 Self { constraint: info.constraint, ..Default::default() }
1189 }
1190}
1191
1192pub fn solve_box_layout(data: &BoxLayoutData, repeater_indices: Slice<u32>) -> SharedVector<Coord> {
1194 let mut result = SharedVector::<Coord>::default();
1195 result.resize(data.cells.len() * 2 + repeater_indices.len(), 0 as _);
1197
1198 if data.cells.is_empty() {
1199 return result;
1200 }
1201
1202 let size_without_padding = data.size - data.padding.begin - data.padding.end;
1203 let num_spacings = (data.cells.len() - 1) as Coord;
1204 let spacings = data.spacing * num_spacings;
1205 let content_size = size_without_padding - spacings; let mut layout_data: Vec<_> = data
1207 .cells
1208 .iter()
1209 .map(|c| {
1210 let min = c.constraint.min.max(c.constraint.min_percent * content_size / 100 as Coord);
1211 let max = c.constraint.max.min(c.constraint.max_percent * content_size / 100 as Coord);
1212 grid_internal::LayoutData {
1213 min,
1214 max,
1215 pref: c.constraint.preferred.min(max).max(min),
1216 stretch: c.constraint.stretch,
1217 ..Default::default()
1218 }
1219 })
1220 .collect();
1221
1222 let pref_size: Coord = layout_data.iter().map(|it| it.pref).sum();
1223
1224 let align = match data.alignment {
1225 LayoutAlignment::Stretch => {
1226 grid_internal::layout_items(
1227 &mut layout_data,
1228 data.padding.begin,
1229 size_without_padding,
1230 data.spacing,
1231 );
1232 None
1233 }
1234 _ if size_without_padding <= pref_size + spacings => {
1235 grid_internal::layout_items(
1236 &mut layout_data,
1237 data.padding.begin,
1238 size_without_padding,
1239 data.spacing,
1240 );
1241 None
1242 }
1243 LayoutAlignment::Center => Some((
1244 data.padding.begin + (size_without_padding - pref_size - spacings) / 2 as Coord,
1245 data.spacing,
1246 )),
1247 LayoutAlignment::Start => Some((data.padding.begin, data.spacing)),
1248 LayoutAlignment::End => {
1249 Some((data.padding.begin + (size_without_padding - pref_size - spacings), data.spacing))
1250 }
1251 LayoutAlignment::SpaceBetween => {
1252 Some((data.padding.begin, (size_without_padding - pref_size) / num_spacings))
1253 }
1254 LayoutAlignment::SpaceAround => {
1255 let spacing = (size_without_padding - pref_size) / (num_spacings + 1 as Coord);
1256 Some((data.padding.begin + spacing / 2 as Coord, spacing))
1257 }
1258 LayoutAlignment::SpaceEvenly => {
1259 let spacing = (size_without_padding - pref_size) / (num_spacings + 2 as Coord);
1260 Some((data.padding.begin + spacing, spacing))
1261 }
1262 };
1263 if let Some((mut pos, spacing)) = align {
1264 for it in &mut layout_data {
1265 it.pos = pos;
1266 it.size = it.pref;
1267 pos += spacing + it.size;
1268 }
1269 }
1270
1271 let mut generator = LayoutCacheGenerator::new(&repeater_indices, &mut result);
1272 for layout in layout_data.iter() {
1273 generator.add(layout.pos, layout.size);
1274 }
1275 result
1276}
1277
1278pub fn box_layout_info(
1280 cells: Slice<LayoutItemInfo>,
1281 spacing: Coord,
1282 padding: &Padding,
1283 alignment: LayoutAlignment,
1284) -> LayoutInfo {
1285 let count = cells.len();
1286 let is_stretch = alignment == LayoutAlignment::Stretch;
1287 if count < 1 {
1288 let mut info = LayoutInfo::default();
1289 info.min = padding.begin + padding.end;
1290 info.preferred = info.min;
1291 if is_stretch {
1292 info.max = info.min;
1293 }
1294 return info;
1295 };
1296 let extra_w = padding.begin + padding.end + spacing * (count - 1) as Coord;
1297 let min = cells.iter().map(|c| c.constraint.min).sum::<Coord>() + extra_w; let max = if is_stretch {
1299 (cells.iter().map(|c| c.constraint.max).fold(extra_w, Saturating::add)).max(min)
1300 } else {
1301 Coord::MAX
1302 }; let preferred = cells.iter().map(|c| c.constraint.preferred_bounded()).sum::<Coord>() + extra_w;
1304 let stretch = cells.iter().map(|c| c.constraint.stretch).sum::<f32>();
1305 LayoutInfo { min, max, min_percent: 0 as _, max_percent: 100 as _, preferred, stretch }
1306}
1307
1308pub fn box_layout_info_ortho(cells: Slice<LayoutItemInfo>, padding: &Padding) -> LayoutInfo {
1309 let extra_w = padding.begin + padding.end;
1310 let mut fold =
1311 cells.iter().fold(LayoutInfo { stretch: f32::MAX, ..Default::default() }, |a, b| {
1312 a.merge(&b.constraint)
1313 });
1314 fold.max = fold.max.max(fold.min);
1315 fold.preferred = fold.preferred.clamp(fold.min, fold.max);
1316 fold.min += extra_w;
1317 fold.max = Saturating::add(fold.max, extra_w);
1318 fold.preferred += extra_w;
1319 fold
1320}
1321
1322mod flexbox_taffy {
1324 use super::{
1325 Coord, FlexboxLayoutAlignContent, FlexboxLayoutAlignItems, FlexboxLayoutAlignSelf,
1326 FlexboxLayoutItemInfo, FlexboxLayoutWrap as SlintFlexboxLayoutWrap, LayoutAlignment,
1327 Padding, Slice,
1328 };
1329 use alloc::vec::Vec;
1330 pub use taffy::prelude::FlexDirection as TaffyFlexDirection;
1331 use taffy::prelude::*;
1332
1333 pub struct FlexboxLayoutParams<'a> {
1335 pub cells_h: &'a Slice<'a, FlexboxLayoutItemInfo>,
1336 pub cells_v: &'a Slice<'a, FlexboxLayoutItemInfo>,
1337 pub spacing_h: Coord,
1338 pub spacing_v: Coord,
1339 pub padding_h: &'a Padding,
1340 pub padding_v: &'a Padding,
1341 pub alignment: LayoutAlignment,
1342 pub align_content: FlexboxLayoutAlignContent,
1343 pub align_items: FlexboxLayoutAlignItems,
1344 pub flex_wrap: SlintFlexboxLayoutWrap,
1345 pub flex_direction: TaffyFlexDirection,
1346 pub container_width: Option<Coord>,
1347 pub container_height: Option<Coord>,
1348 pub use_measure_for_cross_axis: bool,
1351 }
1352
1353 pub struct FlexboxTaffyBuilder {
1356 pub taffy: TaffyTree<usize>,
1357 pub children: Vec<NodeId>,
1358 pub container: NodeId,
1359 pub order_map: Vec<usize>,
1361 }
1362
1363 impl FlexboxTaffyBuilder {
1364 pub fn new(params: FlexboxLayoutParams) -> Self {
1366 let mut taffy = TaffyTree::<usize>::new();
1367
1368 let mut children: Vec<NodeId> = params
1370 .cells_h
1371 .iter()
1372 .enumerate()
1373 .map(|(idx, cell_h)| {
1374 let cell_v = params.cells_v.get(idx);
1375 let h_constraint = &cell_h.constraint;
1376 let v_constraint = cell_v.map(|c| &c.constraint);
1377
1378 let preferred_width = h_constraint.preferred_bounded();
1380 let preferred_height =
1381 v_constraint.map(|vc| vc.preferred_bounded()).unwrap_or(0 as Coord);
1382
1383 let flex_basis = if cell_h.flex_basis >= 0 as Coord {
1385 Dimension::length(cell_h.flex_basis as _)
1386 } else {
1387 match params.flex_direction {
1388 TaffyFlexDirection::Row | TaffyFlexDirection::RowReverse => {
1389 Dimension::length(preferred_width as _)
1390 }
1391 TaffyFlexDirection::Column | TaffyFlexDirection::ColumnReverse => {
1392 Dimension::length(preferred_height as _)
1393 }
1394 }
1395 };
1396
1397 taffy
1398 .new_leaf_with_context(
1399 Style {
1400 flex_basis,
1401 size: Size {
1402 width: match params.flex_direction {
1403 TaffyFlexDirection::Column
1404 | TaffyFlexDirection::ColumnReverse => {
1405 if let Some(cw) = params.container_width {
1406 Dimension::length(cw as _)
1407 } else if preferred_width > 0 as Coord {
1408 Dimension::length(preferred_width as _)
1409 } else {
1410 Dimension::auto()
1411 }
1412 }
1413 _ => Dimension::auto(),
1414 },
1415 height: match params.flex_direction {
1416 TaffyFlexDirection::Row
1417 | TaffyFlexDirection::RowReverse => {
1418 if params.use_measure_for_cross_axis {
1421 Dimension::auto()
1422 } else if preferred_height > 0 as Coord {
1423 Dimension::length(preferred_height as _)
1424 } else {
1425 Dimension::auto()
1426 }
1427 }
1428 _ => Dimension::auto(),
1429 },
1430 },
1431 min_size: Size {
1432 width: Dimension::length(h_constraint.min as _),
1433 height: Dimension::length(
1434 v_constraint.map(|vc| vc.min as f32).unwrap_or(0.0),
1435 ),
1436 },
1437 max_size: Size {
1438 width: if h_constraint.max < Coord::MAX {
1439 Dimension::length(h_constraint.max as _)
1440 } else {
1441 Dimension::auto()
1442 },
1443 height: if let Some(vc) = v_constraint {
1444 if vc.max < Coord::MAX {
1445 Dimension::length(vc.max as _)
1446 } else {
1447 Dimension::auto()
1448 }
1449 } else {
1450 Dimension::auto()
1451 },
1452 },
1453 flex_grow: cell_h.flex_grow,
1454 flex_shrink: cell_h.flex_shrink,
1455 align_self: match cell_h.flex_align_self {
1456 FlexboxLayoutAlignSelf::Auto => None,
1457 FlexboxLayoutAlignSelf::Stretch => Some(AlignSelf::Stretch),
1458 FlexboxLayoutAlignSelf::Start => Some(AlignSelf::FlexStart),
1459 FlexboxLayoutAlignSelf::End => Some(AlignSelf::FlexEnd),
1460 FlexboxLayoutAlignSelf::Center => Some(AlignSelf::Center),
1461 },
1462 ..Default::default()
1463 },
1464 idx,
1465 )
1466 .unwrap() })
1468 .collect();
1469
1470 let has_order = params.cells_h.iter().any(|c| c.flex_order != 0);
1473 let order_map: Vec<usize> = if has_order {
1474 let mut indices: Vec<usize> = (0..children.len()).collect();
1475 indices.sort_by_key(|&i| params.cells_h.get(i).map_or(0, |c| c.flex_order));
1477 let sorted_children: Vec<NodeId> = indices.iter().map(|&i| children[i]).collect();
1478 children = sorted_children;
1479 indices
1480 } else {
1481 Vec::new()
1482 };
1483
1484 let container = taffy
1486 .new_with_children(
1487 Style {
1488 display: Display::Flex,
1489 flex_direction: params.flex_direction,
1490 flex_wrap: match params.flex_wrap {
1491 SlintFlexboxLayoutWrap::Wrap => FlexWrap::Wrap,
1492 SlintFlexboxLayoutWrap::NoWrap => FlexWrap::NoWrap,
1493 SlintFlexboxLayoutWrap::WrapReverse => FlexWrap::WrapReverse,
1494 },
1495 justify_content: Some(match params.alignment {
1496 LayoutAlignment::Start => AlignContent::FlexStart,
1499 LayoutAlignment::End => AlignContent::FlexEnd,
1500 LayoutAlignment::Center => AlignContent::Center,
1501 LayoutAlignment::Stretch => AlignContent::Stretch,
1502 LayoutAlignment::SpaceBetween => AlignContent::SpaceBetween,
1503 LayoutAlignment::SpaceAround => AlignContent::SpaceAround,
1504 LayoutAlignment::SpaceEvenly => AlignContent::SpaceEvenly,
1505 }),
1506 align_items: Some(match params.align_items {
1507 FlexboxLayoutAlignItems::Stretch => AlignItems::Stretch,
1508 FlexboxLayoutAlignItems::Start => AlignItems::FlexStart,
1509 FlexboxLayoutAlignItems::End => AlignItems::FlexEnd,
1510 FlexboxLayoutAlignItems::Center => AlignItems::Center,
1511 }),
1512 align_content: Some(match params.align_content {
1513 FlexboxLayoutAlignContent::Stretch => AlignContent::Stretch,
1514 FlexboxLayoutAlignContent::Start => AlignContent::FlexStart,
1515 FlexboxLayoutAlignContent::End => AlignContent::FlexEnd,
1516 FlexboxLayoutAlignContent::Center => AlignContent::Center,
1517 FlexboxLayoutAlignContent::SpaceBetween => AlignContent::SpaceBetween,
1518 FlexboxLayoutAlignContent::SpaceAround => AlignContent::SpaceAround,
1519 FlexboxLayoutAlignContent::SpaceEvenly => AlignContent::SpaceEvenly,
1520 }),
1521 gap: Size {
1522 width: LengthPercentage::length(params.spacing_h as _),
1523 height: LengthPercentage::length(params.spacing_v as _),
1524 },
1525 padding: Rect {
1526 left: LengthPercentage::length(params.padding_h.begin as _),
1527 right: LengthPercentage::length(params.padding_h.end as _),
1528 top: LengthPercentage::length(params.padding_v.begin as _),
1529 bottom: LengthPercentage::length(params.padding_v.end as _),
1530 },
1531 size: Size {
1532 width: params
1533 .container_width
1534 .map(|w| Dimension::length(w as _))
1535 .unwrap_or(Dimension::auto()),
1536 height: params
1537 .container_height
1538 .map(|h| Dimension::length(h as _))
1539 .unwrap_or(Dimension::auto()),
1540 },
1541 ..Default::default()
1542 },
1543 &children,
1544 )
1545 .unwrap(); Self { taffy, children, container, order_map }
1548 }
1549
1550 pub fn compute_layout(
1558 &mut self,
1559 available_width: Coord,
1560 available_height: Coord,
1561 mut measure: Option<
1562 &mut dyn FnMut(usize, Option<Coord>, Option<Coord>) -> (Coord, Coord),
1563 >,
1564 ) {
1565 let available_space = taffy::prelude::Size {
1566 width: if available_width < Coord::MAX {
1567 AvailableSpace::Definite(available_width as _)
1568 } else {
1569 AvailableSpace::MaxContent
1570 },
1571 height: if available_height < Coord::MAX {
1572 AvailableSpace::Definite(available_height as _)
1573 } else {
1574 AvailableSpace::MaxContent
1575 },
1576 };
1577 self.taffy
1578 .compute_layout_with_measure(
1579 self.container,
1580 available_space,
1581 |known_dimensions, _available_space, _node_id, node_context, _style| {
1582 if let (Some(measure), Some(&mut child_index)) =
1583 (measure.as_deref_mut(), node_context)
1584 {
1585 let known_w = known_dimensions.width.map(|w| w as Coord);
1586 let known_h = known_dimensions.height.map(|h| h as Coord);
1587 let (w, h) = measure(child_index, known_w, known_h);
1588 taffy::prelude::Size { width: w as f32, height: h as f32 }
1589 } else {
1590 taffy::prelude::Size::ZERO
1591 }
1592 },
1593 )
1594 .unwrap_or_else(|e| {
1595 crate::debug_log!("FlexboxLayout computation error: {}", e);
1596 });
1597 }
1598
1599 pub fn container_size(&self) -> (Coord, Coord) {
1601 let layout = self.taffy.layout(self.container).unwrap();
1602 (layout.size.width as Coord, layout.size.height as Coord)
1603 }
1604
1605 pub fn child_geometry(&self, idx: usize) -> (Coord, Coord, Coord, Coord) {
1607 let layout = self.taffy.layout(self.children[idx]).unwrap();
1608 (
1609 layout.location.x as Coord,
1610 layout.location.y as Coord,
1611 layout.size.width as Coord,
1612 layout.size.height as Coord,
1613 )
1614 }
1615
1616 pub fn original_index(&self, taffy_idx: usize) -> usize {
1618 if self.order_map.is_empty() { taffy_idx } else { self.order_map[taffy_idx] }
1619 }
1620 }
1621}
1622
1623struct FlexboxLayoutCacheGenerator<'a> {
1625 repeater_indices: &'a [u32],
1627 counter: usize,
1629 repeat_offset: usize,
1631 next_rep: usize,
1633 current_offset: usize,
1635 result: &'a mut SharedVector<Coord>,
1637}
1638
1639impl<'a> FlexboxLayoutCacheGenerator<'a> {
1640 fn new(repeater_indices: &'a [u32], result: &'a mut SharedVector<Coord>) -> Self {
1641 let total_repeated_cells: usize = repeater_indices
1643 .chunks(2)
1644 .map(|chunk| chunk.get(1).copied().unwrap_or(0) as usize)
1645 .sum();
1646 assert!(result.len() >= total_repeated_cells * 4);
1647 let repeat_offset = result.len() / 4 - total_repeated_cells;
1648 Self { repeater_indices, counter: 0, repeat_offset, next_rep: 0, current_offset: 0, result }
1649 }
1650
1651 fn add(&mut self, x: Coord, y: Coord, w: Coord, h: Coord) {
1652 let res = self.result.make_mut_slice();
1653 let o = loop {
1654 if let Some(nr) = self.repeater_indices.get(self.next_rep * 2) {
1655 let nr = *nr as usize;
1656 if nr == self.counter {
1657 res[self.current_offset * 4] = (self.repeat_offset * 4) as Coord;
1660 res[self.current_offset * 4 + 1] = (self.repeat_offset * 4 + 1) as Coord;
1661 res[self.current_offset * 4 + 2] = (self.repeat_offset * 4 + 2) as Coord;
1662 res[self.current_offset * 4 + 3] = (self.repeat_offset * 4 + 3) as Coord;
1663 self.current_offset += 1;
1664 }
1665 if self.counter >= nr {
1666 let rep_count = self.repeater_indices[self.next_rep * 2 + 1] as usize;
1667 if self.counter - nr == rep_count {
1668 self.repeat_offset += rep_count;
1670 self.next_rep += 1;
1671 continue;
1672 }
1673 let cell_in_rep = self.counter - nr;
1675 let offset = self.repeat_offset + cell_in_rep;
1676 break offset;
1677 }
1678 }
1679 self.current_offset += 1;
1680 break self.current_offset - 1;
1681 };
1682 res[o * 4] = x;
1683 res[o * 4 + 1] = y;
1684 res[o * 4 + 2] = w;
1685 res[o * 4 + 3] = h;
1686 self.counter += 1;
1687 }
1688}
1689
1690pub type FlexboxMeasureFn<'a> =
1697 Option<&'a mut dyn FnMut(usize, Option<Coord>, Option<Coord>) -> (Coord, Coord)>;
1698
1699pub fn solve_flexbox_layout(
1700 data: &FlexboxLayoutData,
1701 repeater_indices: Slice<u32>,
1702) -> SharedVector<Coord> {
1703 let mut measure = |child_index: usize,
1709 known_w: Option<Coord>,
1710 known_h: Option<Coord>|
1711 -> (Coord, Coord) {
1712 let w = known_w.unwrap_or_else(|| {
1713 data.cells_h.get(child_index).map_or(0 as Coord, |c| c.constraint.preferred_bounded())
1714 });
1715 let h = known_h.unwrap_or_else(|| {
1716 data.cells_v.get(child_index).map_or(0 as Coord, |c| c.constraint.preferred_bounded())
1717 });
1718 (w, h)
1719 };
1720 solve_flexbox_layout_with_measure(data, repeater_indices, Some(&mut measure))
1721}
1722
1723pub fn solve_flexbox_layout_with_measure(
1726 data: &FlexboxLayoutData,
1727 repeater_indices: Slice<u32>,
1728 measure: FlexboxMeasureFn<'_>,
1729) -> SharedVector<Coord> {
1730 let mut result = SharedVector::<Coord>::default();
1732 result.resize(data.cells_h.len() * 4 + repeater_indices.len() * 2, 0 as _);
1733
1734 if data.cells_h.is_empty() {
1735 return result;
1736 }
1737
1738 let taffy_direction = match data.direction {
1739 FlexboxLayoutDirection::Row => flexbox_taffy::TaffyFlexDirection::Row,
1740 FlexboxLayoutDirection::RowReverse => flexbox_taffy::TaffyFlexDirection::RowReverse,
1741 FlexboxLayoutDirection::Column => flexbox_taffy::TaffyFlexDirection::Column,
1742 FlexboxLayoutDirection::ColumnReverse => flexbox_taffy::TaffyFlexDirection::ColumnReverse,
1743 };
1744
1745 let (container_width, container_height) = (
1746 if data.width > 0 as Coord { Some(data.width) } else { None },
1747 if data.height > 0 as Coord { Some(data.height) } else { None },
1748 );
1749
1750 let use_measure = measure.is_some();
1751 let mut builder = flexbox_taffy::FlexboxTaffyBuilder::new(flexbox_taffy::FlexboxLayoutParams {
1752 cells_h: &data.cells_h,
1753 cells_v: &data.cells_v,
1754 spacing_h: data.spacing_h,
1755 spacing_v: data.spacing_v,
1756 padding_h: &data.padding_h,
1757 padding_v: &data.padding_v,
1758 alignment: data.alignment,
1759 align_content: data.align_content,
1760 align_items: data.align_items,
1761 flex_wrap: data.flex_wrap,
1762 flex_direction: taffy_direction,
1763 container_width,
1764 container_height,
1765 use_measure_for_cross_axis: use_measure,
1766 });
1767
1768 let (available_width, available_height) = match data.direction {
1769 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
1770 (data.width, Coord::MAX)
1771 }
1772 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
1773 (Coord::MAX, data.height)
1774 }
1775 };
1776
1777 builder.compute_layout(available_width, available_height, measure);
1778
1779 if builder.order_map.is_empty() {
1783 let mut generator = FlexboxLayoutCacheGenerator::new(&repeater_indices, &mut result);
1784 for idx in 0..data.cells_h.len() {
1785 let (x, y, w, h) = builder.child_geometry(idx);
1786 generator.add(x, y, w, h);
1787 }
1788 } else {
1789 let count = data.cells_h.len();
1790 let mut geom = alloc::vec![(0 as Coord, 0 as Coord, 0 as Coord, 0 as Coord); count];
1791 for taffy_idx in 0..count {
1792 let orig_idx = builder.original_index(taffy_idx);
1793 geom[orig_idx] = builder.child_geometry(taffy_idx);
1794 }
1795 let mut generator = FlexboxLayoutCacheGenerator::new(&repeater_indices, &mut result);
1796 for (x, y, w, h) in geom {
1797 generator.add(x, y, w, h);
1798 }
1799 }
1800
1801 result
1802}
1803
1804pub fn flexbox_layout_info_main_axis(
1807 cells: Slice<FlexboxLayoutItemInfo>,
1808 spacing: Coord,
1809 padding: &Padding,
1810 flex_wrap: FlexboxLayoutWrap,
1811) -> LayoutInfo {
1812 let extra_pad = padding.begin + padding.end;
1813 if cells.is_empty() {
1814 return LayoutInfo {
1815 min: extra_pad,
1816 preferred: extra_pad,
1817 max: extra_pad,
1818 ..Default::default()
1819 };
1820 }
1821 let num_spacings = cells.len().saturating_sub(1) as Coord;
1822 let min = if matches!(flex_wrap, FlexboxLayoutWrap::NoWrap) {
1823 cells.iter().map(|c| c.constraint.min).sum::<Coord>() + spacing * num_spacings + extra_pad
1824 } else {
1825 cells.iter().map(|c| c.constraint.min).fold(0.0 as Coord, |a, b| a.max(b)) + extra_pad
1827 };
1828 let preferred = if matches!(flex_wrap, FlexboxLayoutWrap::NoWrap) {
1829 cells.iter().map(|c| c.constraint.preferred_bounded()).sum::<Coord>()
1831 + spacing * num_spacings
1832 + extra_pad
1833 } else {
1834 let total_area: Coord = cells
1838 .iter()
1839 .map(|c| {
1840 let w = c.constraint.preferred_bounded();
1841 w * w
1842 })
1843 .sum();
1844 let count = cells.len();
1845 Float::sqrt(total_area as f32) as Coord + spacing * (count - 1) as Coord + extra_pad
1846 };
1847 let stretch = cells.iter().map(|c| c.constraint.stretch).sum::<f32>();
1848 LayoutInfo {
1849 min,
1850 max: Coord::MAX,
1851 min_percent: 0 as _,
1852 max_percent: 100 as _,
1853 preferred,
1854 stretch,
1855 }
1856}
1857
1858pub fn flexbox_layout_info_cross_axis(
1867 cells_h: Slice<FlexboxLayoutItemInfo>,
1868 cells_v: Slice<FlexboxLayoutItemInfo>,
1869 spacing_h: Coord,
1870 spacing_v: Coord,
1871 padding_h: &Padding,
1872 padding_v: &Padding,
1873 direction: FlexboxLayoutDirection,
1874 flex_wrap: FlexboxLayoutWrap,
1875 constraint_size: Coord,
1876) -> LayoutInfo {
1877 if cells_h.is_empty() {
1878 assert!(cells_v.is_empty());
1879 let orientation = match direction {
1880 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
1881 Orientation::Vertical
1882 }
1883 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
1884 Orientation::Horizontal
1885 }
1886 };
1887 let padding = match orientation {
1888 Orientation::Horizontal => padding_h,
1889 Orientation::Vertical => padding_v,
1890 };
1891 let pad = padding.begin + padding.end;
1892 return LayoutInfo { min: pad, preferred: pad, max: pad, ..Default::default() };
1893 }
1894
1895 let cross_cells = match direction {
1897 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => &cells_v,
1898 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => &cells_h,
1899 };
1900
1901 let (main_cells, main_spacing, main_padding) = match direction {
1904 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
1905 (&cells_h, spacing_h, padding_h)
1906 }
1907 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
1908 (&cells_v, spacing_v, padding_v)
1909 }
1910 };
1911 let main_extra_pad = main_padding.begin + main_padding.end;
1912 let main_axis_constraint = if constraint_size > 0 as Coord && constraint_size < Coord::MAX {
1913 constraint_size
1915 } else if matches!(flex_wrap, FlexboxLayoutWrap::NoWrap) {
1916 Coord::MAX
1917 } else {
1918 let total_area: Coord = main_cells
1921 .iter()
1922 .zip(cross_cells.iter())
1923 .map(|(m, c)| m.constraint.preferred_bounded() * c.constraint.preferred_bounded())
1924 .sum();
1925 let count = main_cells.len();
1926 Float::sqrt(total_area as f32) as Coord
1927 + main_spacing * (count - 1) as Coord
1928 + main_extra_pad
1929 };
1930
1931 let taffy_direction = match direction {
1932 FlexboxLayoutDirection::Row => flexbox_taffy::TaffyFlexDirection::Row,
1933 FlexboxLayoutDirection::RowReverse => flexbox_taffy::TaffyFlexDirection::RowReverse,
1934 FlexboxLayoutDirection::Column => flexbox_taffy::TaffyFlexDirection::Column,
1935 FlexboxLayoutDirection::ColumnReverse => flexbox_taffy::TaffyFlexDirection::ColumnReverse,
1936 };
1937
1938 let (container_width, container_height) = match direction {
1939 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
1940 (Some(main_axis_constraint), None)
1941 }
1942 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
1943 (None, Some(main_axis_constraint))
1944 }
1945 };
1946
1947 let mut builder = flexbox_taffy::FlexboxTaffyBuilder::new(flexbox_taffy::FlexboxLayoutParams {
1948 cells_h: &cells_h,
1949 cells_v: &cells_v,
1950 spacing_h,
1951 spacing_v,
1952 padding_h,
1953 padding_v,
1954 alignment: LayoutAlignment::Start,
1955 align_content: FlexboxLayoutAlignContent::Stretch,
1956 align_items: FlexboxLayoutAlignItems::Stretch,
1957 flex_wrap,
1958 flex_direction: taffy_direction,
1959 container_width,
1960 container_height,
1961 use_measure_for_cross_axis: false,
1962 });
1963
1964 let (available_width, available_height) = match direction {
1965 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
1966 (main_axis_constraint, Coord::MAX)
1967 }
1968 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
1969 (Coord::MAX, main_axis_constraint)
1970 }
1971 };
1972
1973 builder.compute_layout(available_width, available_height, None);
1974
1975 let (total_width, total_height) = builder.container_size();
1976 let cross_size = match direction {
1977 FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => total_height,
1978 FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => total_width,
1979 };
1980
1981 LayoutInfo {
1982 min: cross_size,
1983 max: Coord::MAX,
1984 min_percent: 0 as _,
1985 max_percent: 100 as _,
1986 preferred: cross_size,
1987 stretch: 0.0,
1988 }
1989}
1990
1991#[cfg(feature = "ffi")]
1992pub(crate) mod ffi {
1993 #![allow(unsafe_code)]
1994
1995 use super::*;
1996
1997 #[unsafe(no_mangle)]
1998 pub extern "C" fn slint_organize_grid_layout(
1999 input_data: Slice<GridLayoutInputData>,
2000 repeater_indices: Slice<u32>,
2001 repeater_steps: Slice<u32>,
2002 result: &mut GridLayoutOrganizedData,
2003 ) {
2004 *result = super::organize_grid_layout(input_data, repeater_indices, repeater_steps);
2005 }
2006
2007 #[unsafe(no_mangle)]
2008 pub extern "C" fn slint_organize_dialog_button_layout(
2009 input_data: Slice<GridLayoutInputData>,
2010 dialog_button_roles: Slice<DialogButtonRole>,
2011 result: &mut GridLayoutOrganizedData,
2012 ) {
2013 *result = super::organize_dialog_button_layout(input_data, dialog_button_roles);
2014 }
2015
2016 #[unsafe(no_mangle)]
2017 pub extern "C" fn slint_solve_grid_layout(
2018 data: &GridLayoutData,
2019 constraints: Slice<LayoutItemInfo>,
2020 orientation: Orientation,
2021 repeater_indices: Slice<u32>,
2022 repeater_steps: Slice<u32>,
2023 result: &mut SharedVector<Coord>,
2024 ) {
2025 *result = super::solve_grid_layout(
2026 data,
2027 constraints,
2028 orientation,
2029 repeater_indices,
2030 repeater_steps,
2031 )
2032 }
2033
2034 #[unsafe(no_mangle)]
2035 pub extern "C" fn slint_grid_layout_info(
2036 organized_data: &GridLayoutOrganizedData,
2037 constraints: Slice<LayoutItemInfo>,
2038 repeater_indices: Slice<u32>,
2039 repeater_steps: Slice<u32>,
2040 spacing: Coord,
2041 padding: &Padding,
2042 orientation: Orientation,
2043 ) -> LayoutInfo {
2044 super::grid_layout_info(
2045 organized_data.clone(),
2046 constraints,
2047 repeater_indices,
2048 repeater_steps,
2049 spacing,
2050 padding,
2051 orientation,
2052 )
2053 }
2054
2055 #[unsafe(no_mangle)]
2056 pub extern "C" fn slint_solve_box_layout(
2057 data: &BoxLayoutData,
2058 repeater_indices: Slice<u32>,
2059 result: &mut SharedVector<Coord>,
2060 ) {
2061 *result = super::solve_box_layout(data, repeater_indices)
2062 }
2063
2064 #[unsafe(no_mangle)]
2065 pub extern "C" fn slint_box_layout_info(
2067 cells: Slice<LayoutItemInfo>,
2068 spacing: Coord,
2069 padding: &Padding,
2070 alignment: LayoutAlignment,
2071 ) -> LayoutInfo {
2072 super::box_layout_info(cells, spacing, padding, alignment)
2073 }
2074
2075 #[unsafe(no_mangle)]
2076 pub extern "C" fn slint_box_layout_info_ortho(
2078 cells: Slice<LayoutItemInfo>,
2079 padding: &Padding,
2080 ) -> LayoutInfo {
2081 super::box_layout_info_ortho(cells, padding)
2082 }
2083
2084 pub type FlexboxMeasureFnC = unsafe extern "C" fn(
2088 user_data: *mut core::ffi::c_void,
2089 child_index: usize,
2090 known_width: Coord,
2091 known_height: Coord,
2092 out_width: *mut Coord,
2093 out_height: *mut Coord,
2094 );
2095
2096 #[unsafe(no_mangle)]
2097 #[allow(unsafe_code)]
2098 pub extern "C" fn slint_solve_flexbox_layout(
2099 data: &FlexboxLayoutData,
2100 repeater_indices: Slice<u32>,
2101 result: &mut SharedVector<Coord>,
2102 measure_fn: *const core::ffi::c_void,
2103 measure_user_data: *mut core::ffi::c_void,
2104 ) {
2105 const {
2108 assert!(
2109 core::mem::size_of::<*const core::ffi::c_void>()
2110 == core::mem::size_of::<FlexboxMeasureFnC>()
2111 );
2112 }
2113 let measure_fn: Option<FlexboxMeasureFnC> = if measure_fn.is_null() {
2114 None
2115 } else {
2116 Some(unsafe {
2117 core::mem::transmute::<*const core::ffi::c_void, FlexboxMeasureFnC>(measure_fn)
2118 })
2119 };
2120 if let Some(c_measure) = measure_fn {
2121 let mut measure = |child_index: usize,
2122 known_w: Option<Coord>,
2123 known_h: Option<Coord>|
2124 -> (Coord, Coord) {
2125 let mut out_w: Coord = 0 as _;
2126 let mut out_h: Coord = 0 as _;
2127 unsafe {
2130 c_measure(
2131 measure_user_data,
2132 child_index,
2133 known_w.unwrap_or(-1 as _),
2134 known_h.unwrap_or(-1 as _),
2135 &mut out_w,
2136 &mut out_h,
2137 );
2138 }
2139 (out_w, out_h)
2140 };
2141 *result = super::solve_flexbox_layout_with_measure(
2142 data,
2143 repeater_indices,
2144 Some(&mut measure),
2145 );
2146 } else {
2147 *result = super::solve_flexbox_layout(data, repeater_indices);
2148 }
2149 }
2150
2151 #[unsafe(no_mangle)]
2152 pub extern "C" fn slint_flexbox_layout_info_main_axis(
2154 cells: Slice<FlexboxLayoutItemInfo>,
2155 spacing: Coord,
2156 padding: &Padding,
2157 flex_wrap: FlexboxLayoutWrap,
2158 ) -> LayoutInfo {
2159 super::flexbox_layout_info_main_axis(cells, spacing, padding, flex_wrap)
2160 }
2161
2162 #[unsafe(no_mangle)]
2163 pub extern "C" fn slint_flexbox_layout_info_cross_axis(
2165 cells_h: Slice<FlexboxLayoutItemInfo>,
2166 cells_v: Slice<FlexboxLayoutItemInfo>,
2167 spacing_h: Coord,
2168 spacing_v: Coord,
2169 padding_h: &Padding,
2170 padding_v: &Padding,
2171 direction: FlexboxLayoutDirection,
2172 flex_wrap: FlexboxLayoutWrap,
2173 constraint_size: Coord,
2174 ) -> LayoutInfo {
2175 super::flexbox_layout_info_cross_axis(
2176 cells_h,
2177 cells_v,
2178 spacing_h,
2179 spacing_v,
2180 padding_h,
2181 padding_v,
2182 direction,
2183 flex_wrap,
2184 constraint_size,
2185 )
2186 }
2187}
2188
2189#[cfg(test)]
2190mod tests {
2191 use super::*;
2192
2193 fn collect_from_organized_data(
2194 organized_data: &GridLayoutOrganizedData,
2195 num_cells: usize,
2196 repeater_indices: Slice<u32>,
2197 repeater_steps: Slice<u32>,
2198 ) -> Vec<(u16, u16, u16, u16)> {
2199 let mut result = Vec::new();
2200 for i in 0..num_cells {
2201 let col_and_span = organized_data.col_or_row_and_span(
2202 i,
2203 Orientation::Horizontal,
2204 &repeater_indices,
2205 &repeater_steps,
2206 );
2207 let row_and_span = organized_data.col_or_row_and_span(
2208 i,
2209 Orientation::Vertical,
2210 &repeater_indices,
2211 &repeater_steps,
2212 );
2213 result.push((col_and_span.0, col_and_span.1, row_and_span.0, row_and_span.1));
2214 }
2215 result
2216 }
2217
2218 #[test]
2219 fn test_organized_data_generator_2_fixed_cells() {
2220 let mut result = GridLayoutOrganizedData::default();
2222 let num_cells = 2;
2223 let mut generator = OrganizedDataGenerator::new(&[], &[], num_cells, 0, 0, &mut result);
2224 generator.add(0, 1, 0, 1);
2225 generator.add(1, 2, 0, 3);
2226 assert_eq!(result.as_slice(), &[0, 1, 0, 1, 1, 2, 0, 3]);
2227
2228 let repeater_indices = Slice::from_slice(&[]);
2229 let empty_steps = Slice::from_slice(&[]);
2230 let collected_data =
2231 collect_from_organized_data(&result, num_cells, repeater_indices, empty_steps);
2232 assert_eq!(collected_data.as_slice(), &[(0, 1, 0, 1), (1, 2, 0, 3)]);
2233
2234 assert_eq!(
2235 result.max_value(num_cells, Orientation::Horizontal, &repeater_indices, &empty_steps),
2236 3
2237 );
2238 assert_eq!(
2239 result.max_value(num_cells, Orientation::Vertical, &repeater_indices, &empty_steps),
2240 3
2241 );
2242 }
2243
2244 #[test]
2245 fn test_organized_data_generator_1_fixed_cell_1_repeater() {
2246 let mut result = GridLayoutOrganizedData::default();
2248 let num_cells = 4;
2249 let repeater_indices = &[1u32, 3u32];
2250 let mut generator =
2251 OrganizedDataGenerator::new(repeater_indices, &[], 1, 1, 3, &mut result);
2252 generator.add(0, 1, 0, 2); generator.add(1, 2, 1, 3); generator.add(1, 1, 2, 4);
2255 generator.add(2, 2, 3, 5);
2256 assert_eq!(
2257 result.as_slice(),
2258 &[
2259 0, 1, 0, 2, 8, 4, 0, 0, 1, 2, 1, 3, 1, 1, 2, 4, 2, 2, 3, 5, ]
2265 );
2266 let repeater_indices = Slice::from_slice(repeater_indices);
2267 let empty_steps = Slice::from_slice(&[]);
2268 let collected_data =
2269 collect_from_organized_data(&result, num_cells, repeater_indices, empty_steps);
2270 assert_eq!(
2271 collected_data.as_slice(),
2272 &[(0, 1, 0, 2), (1, 2, 1, 3), (1, 1, 2, 4), (2, 2, 3, 5)]
2273 );
2274
2275 assert_eq!(
2276 result.max_value(num_cells, Orientation::Horizontal, &repeater_indices, &empty_steps),
2277 4
2278 );
2279 assert_eq!(
2280 result.max_value(num_cells, Orientation::Vertical, &repeater_indices, &empty_steps),
2281 8
2282 );
2283 }
2284
2285 #[test]
2286
2287 fn test_organize_data_with_auto_and_spans() {
2288 let auto = i_slint_common::ROW_COL_AUTO;
2289 let input = std::vec![
2290 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 2., rowspan: -1. },
2291 GridLayoutInputData { new_row: false, col: auto, row: auto, colspan: 1., rowspan: 2. },
2292 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 2., rowspan: 1. },
2293 GridLayoutInputData { new_row: true, col: -2., row: 80000., colspan: 2., rowspan: 1. },
2294 ];
2295 let repeater_indices = Slice::from_slice(&[]);
2296 let (organized_data, errors) = organize_grid_layout_impl(
2297 Slice::from_slice(&input),
2298 repeater_indices,
2299 Slice::from_slice(&[]),
2300 );
2301 assert_eq!(
2302 organized_data.as_slice(),
2303 &[
2304 0, 2, 0, 0, 2, 1, 0, 2, 0, 2, 1, 1, 0, 2, 65535, 1, ]
2309 );
2310 assert_eq!(errors.len(), 3);
2311 assert_eq!(errors[0], "cell rowspan -1 is negative, clamping to 0");
2313 assert_eq!(errors[1], "cell row 80000 is too large, clamping to 65535");
2314 assert_eq!(errors[2], "cell col -2 is negative, clamping to 0");
2315 let empty_steps = Slice::from_slice(&[]);
2316 let collected_data = collect_from_organized_data(
2317 &organized_data,
2318 input.len(),
2319 repeater_indices,
2320 empty_steps,
2321 );
2322 assert_eq!(
2323 collected_data.as_slice(),
2324 &[(0, 2, 0, 0), (2, 1, 0, 2), (0, 2, 1, 1), (0, 2, 65535, 1)]
2325 );
2326 assert_eq!(
2327 organized_data.max_value(3, Orientation::Horizontal, &repeater_indices, &empty_steps),
2328 3
2329 );
2330 assert_eq!(
2331 organized_data.max_value(3, Orientation::Vertical, &repeater_indices, &empty_steps),
2332 2
2333 );
2334 }
2335
2336 #[test]
2337 fn test_organize_data_1_empty_repeater() {
2338 let auto = i_slint_common::ROW_COL_AUTO;
2340 let cell =
2341 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. };
2342 let input = std::vec![cell];
2343 let repeater_indices = Slice::from_slice(&[1u32, 0u32]);
2344 let (organized_data, errors) = organize_grid_layout_impl(
2345 Slice::from_slice(&input),
2346 repeater_indices,
2347 Slice::from_slice(&[]),
2348 );
2349 assert_eq!(
2350 organized_data.as_slice(),
2351 &[
2352 0, 1, 0, 1, 0, 0, 0, 0
2354 ] );
2356 assert_eq!(errors.len(), 0);
2357 let empty_steps = Slice::from_slice(&[]);
2358 let collected_data = collect_from_organized_data(
2359 &organized_data,
2360 input.len(),
2361 repeater_indices,
2362 empty_steps,
2363 );
2364 assert_eq!(collected_data.as_slice(), &[(0, 1, 0, 1)]);
2365 assert_eq!(
2366 organized_data.max_value(1, Orientation::Horizontal, &repeater_indices, &empty_steps),
2367 1
2368 );
2369 }
2370
2371 #[test]
2372 fn test_organize_data_4_repeaters() {
2373 let auto = i_slint_common::ROW_COL_AUTO;
2374 let mut cell =
2375 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. };
2376 let mut input = std::vec![cell.clone()];
2377 for _ in 0..8 {
2378 cell.new_row = false;
2379 input.push(cell.clone());
2380 }
2381 let repeater_indices = Slice::from_slice(&[0u32, 0u32, 1u32, 4u32, 6u32, 2u32, 8u32, 0u32]);
2382 let (organized_data, errors) = organize_grid_layout_impl(
2383 Slice::from_slice(&input),
2384 repeater_indices,
2385 Slice::from_slice(&[]),
2386 );
2387 assert_eq!(
2388 organized_data.as_slice(),
2389 &[
2390 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, ]
2404 );
2405 assert_eq!(errors.len(), 0);
2406 let empty_steps = Slice::from_slice(&[]);
2407 let collected_data = collect_from_organized_data(
2408 &organized_data,
2409 input.len(),
2410 repeater_indices,
2411 empty_steps,
2412 );
2413 assert_eq!(
2414 collected_data.as_slice(),
2415 &[
2416 (0, 1, 0, 1),
2417 (1, 1, 0, 1),
2418 (2, 1, 0, 1),
2419 (3, 1, 0, 1),
2420 (4, 1, 0, 1),
2421 (5, 1, 0, 1),
2422 (6, 1, 0, 1),
2423 (7, 1, 0, 1),
2424 (8, 1, 0, 1),
2425 ]
2426 );
2427 let empty_steps = Slice::from_slice(&[]);
2428 assert_eq!(
2429 organized_data.max_value(
2430 input.len(),
2431 Orientation::Horizontal,
2432 &repeater_indices,
2433 &empty_steps
2434 ),
2435 9
2436 );
2437 }
2438
2439 #[test]
2440 fn test_organize_data_repeated_rows() {
2441 let auto = i_slint_common::ROW_COL_AUTO;
2442 let mut input = Vec::new();
2443 let num_rows: u32 = 3;
2444 let num_columns: u32 = 2;
2445 for _ in 0..num_rows {
2447 let mut cell = GridLayoutInputData {
2448 new_row: true,
2449 col: auto,
2450 row: auto,
2451 colspan: 1.,
2452 rowspan: 1.,
2453 };
2454 input.push(cell.clone());
2455 cell.new_row = false;
2456 input.push(cell.clone());
2457 }
2458 let repeater_indices_arr = [0_u32, num_rows];
2460 let repeater_steps_arr = [num_columns];
2461 let repeater_steps = Slice::from_slice(&repeater_steps_arr);
2462 let repeater_indices = Slice::from_slice(&repeater_indices_arr);
2463 let (organized_data, errors) =
2464 organize_grid_layout_impl(Slice::from_slice(&input), repeater_indices, repeater_steps);
2465 assert_eq!(
2466 organized_data.as_slice(),
2467 &[
2468 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, ]
2473 );
2474 assert_eq!(errors.len(), 0);
2475 let collected_data = collect_from_organized_data(
2476 &organized_data,
2477 input.len(),
2478 repeater_indices,
2479 repeater_steps,
2480 );
2481 assert_eq!(
2482 collected_data.as_slice(),
2483 &[(0, 1, 0, 1), (1, 1, 0, 1), (0, 1, 1, 1), (1, 1, 1, 1), (0, 1, 2, 1), (1, 1, 2, 1),]
2485 );
2486 assert_eq!(
2487 organized_data.max_value(
2488 input.len(),
2489 Orientation::Horizontal,
2490 &repeater_indices,
2491 &repeater_steps
2492 ),
2493 2
2494 );
2495 assert_eq!(
2496 organized_data.max_value(
2497 input.len(),
2498 Orientation::Vertical,
2499 &repeater_indices,
2500 &repeater_steps
2501 ),
2502 3
2503 );
2504
2505 let mut layout_cache_v = SharedVector::<Coord>::default();
2507 let mut generator = GridLayoutCacheGenerator::new(
2508 repeater_indices.as_slice(),
2509 repeater_steps.as_slice(),
2510 0, 1, 6, &mut layout_cache_v,
2514 );
2515 generator.add(0., 50.);
2517 generator.add(0., 50.);
2518 generator.add(50., 50.);
2520 generator.add(50., 50.);
2521 generator.add(100., 50.);
2523 generator.add(100., 50.);
2524 assert_eq!(
2525 layout_cache_v.as_slice(),
2526 &[
2527 2., 4., 0., 50., 0., 50., 50., 50., 50., 50., 100., 50., 100., 50., ]
2532 );
2533
2534 let layout_cache_v_access = |jump_index: usize,
2536 repeater_index: usize,
2537 stride: usize,
2538 child_offset: usize|
2539 -> Coord {
2540 let base = layout_cache_v[jump_index] as usize;
2541 let data_idx = base + repeater_index * stride + child_offset;
2542 layout_cache_v[data_idx]
2543 };
2544 assert_eq!(layout_cache_v_access(0, 0, 4, 0), 0.);
2547 assert_eq!(layout_cache_v_access(0, 1, 4, 0), 50.);
2548 assert_eq!(layout_cache_v_access(0, 2, 4, 0), 100.);
2549 assert_eq!(layout_cache_v_access(0, 0, 4, 2), 0.);
2551 assert_eq!(layout_cache_v_access(0, 1, 4, 2), 50.);
2552 assert_eq!(layout_cache_v_access(0, 2, 4, 2), 100.);
2553 }
2554
2555 #[test]
2556 fn test_organize_data_repeated_rows_multiple_repeaters() {
2557 let auto = i_slint_common::ROW_COL_AUTO;
2558 let mut input = Vec::new();
2559 let num_rows: u32 = 5;
2560 let mut cell =
2561 GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. };
2562 for _ in 0..3 {
2564 cell.new_row = true;
2565 input.push(cell.clone());
2566 cell.new_row = false;
2567 input.push(cell.clone());
2568 }
2569 for _ in 0..2 {
2571 cell.new_row = true;
2572 input.push(cell.clone());
2573 cell.new_row = false;
2574 input.push(cell.clone());
2575 cell.new_row = false;
2576 input.push(cell.clone());
2577 }
2578 let repeater_indices_arr = [0_u32, 3, 6, 2];
2581 let repeater_steps_arr = [2, 3];
2582 let repeater_steps = Slice::from_slice(&repeater_steps_arr);
2583 let repeater_indices = Slice::from_slice(&repeater_indices_arr);
2584 let (organized_data, errors) =
2585 organize_grid_layout_impl(Slice::from_slice(&input), repeater_indices, repeater_steps);
2586 assert_eq!(
2587 organized_data.as_slice(),
2588 &[
2589 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, ]
2599 );
2600 assert_eq!(errors.len(), 0);
2601 let collected_data = collect_from_organized_data(
2602 &organized_data,
2603 input.len(),
2604 repeater_indices,
2605 repeater_steps,
2606 );
2607 assert_eq!(
2608 collected_data.as_slice(),
2609 &[
2611 (0, 1, 0, 1),
2612 (1, 1, 0, 1),
2613 (0, 1, 1, 1),
2614 (1, 1, 1, 1),
2615 (0, 1, 2, 1),
2616 (1, 1, 2, 1),
2617 (0, 1, 3, 1),
2618 (1, 1, 3, 1),
2619 (2, 1, 3, 1),
2620 (0, 1, 4, 1),
2621 (1, 1, 4, 1),
2622 (2, 1, 4, 1)
2623 ]
2624 );
2625 assert_eq!(
2626 organized_data.max_value(
2627 input.len(),
2628 Orientation::Horizontal,
2629 &repeater_indices,
2630 &repeater_steps
2631 ),
2632 3 );
2634 assert_eq!(
2635 organized_data.max_value(
2636 input.len(),
2637 Orientation::Vertical,
2638 &repeater_indices,
2639 &repeater_steps
2640 ),
2641 num_rows as u16 );
2643
2644 let mut layout_cache_v = SharedVector::<Coord>::default();
2646 let mut generator = GridLayoutCacheGenerator::new(
2647 repeater_indices.as_slice(),
2648 repeater_steps.as_slice(),
2649 0, 2, 12, &mut layout_cache_v,
2653 );
2654 generator.add(0., 50.);
2656 generator.add(0., 50.);
2657 generator.add(50., 50.);
2659 generator.add(50., 50.);
2660 generator.add(100., 50.);
2662 generator.add(100., 50.);
2663 generator.add(150., 50.);
2665 generator.add(150., 50.);
2666 generator.add(150., 50.);
2667 generator.add(200., 50.);
2669 generator.add(200., 50.);
2670 generator.add(200., 50.);
2671 assert_eq!(
2672 layout_cache_v.as_slice(),
2673 &[
2674 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., ]
2682 );
2683
2684 let layout_cache_v_access = |jump_index: usize,
2686 repeater_index: usize,
2687 stride: usize,
2688 child_offset: usize|
2689 -> Coord {
2690 let base = layout_cache_v[jump_index] as usize;
2691 let data_idx = base + repeater_index * stride + child_offset;
2692 layout_cache_v[data_idx]
2693 };
2694 assert_eq!(layout_cache_v_access(0, 0, 4, 0), 0.);
2696 assert_eq!(layout_cache_v_access(0, 1, 4, 0), 50.);
2697 assert_eq!(layout_cache_v_access(0, 2, 4, 0), 100.);
2698 assert_eq!(layout_cache_v_access(0, 0, 4, 2), 0.);
2700 assert_eq!(layout_cache_v_access(0, 1, 4, 2), 50.);
2701 assert_eq!(layout_cache_v_access(0, 2, 4, 2), 100.);
2702 assert_eq!(layout_cache_v_access(2, 0, 6, 0), 150.);
2704 assert_eq!(layout_cache_v_access(2, 1, 6, 0), 200.);
2705 assert_eq!(layout_cache_v_access(2, 0, 6, 4), 150.);
2707 assert_eq!(layout_cache_v_access(2, 1, 6, 4), 200.);
2708 }
2709
2710 #[test]
2711 fn test_layout_cache_generator_2_fixed_cells() {
2712 let mut result = SharedVector::<Coord>::default();
2714 result.resize(2 * 2, 0 as _);
2715 let mut generator = LayoutCacheGenerator::new(&[], &mut result);
2716 generator.add(0., 50.); generator.add(80., 50.); assert_eq!(result.as_slice(), &[0., 50., 80., 50.]);
2719 }
2720
2721 #[test]
2722 fn test_layout_cache_generator_1_fixed_cell_1_repeater() {
2723 let mut result = SharedVector::<Coord>::default();
2725 let repeater_indices = &[1, 3];
2726 result.resize(4 * 2 + repeater_indices.len(), 0 as _);
2727 let mut generator = LayoutCacheGenerator::new(repeater_indices, &mut result);
2728 generator.add(0., 50.); generator.add(80., 50.); generator.add(160., 50.);
2731 generator.add(240., 50.);
2732 assert_eq!(
2733 result.as_slice(),
2734 &[
2735 0., 50., 4., 5., 80., 50., 160., 50., 240., 50. ]
2739 );
2740 }
2741
2742 #[test]
2743 fn test_layout_cache_generator_4_repeaters() {
2744 let mut result = SharedVector::<Coord>::default();
2746 let repeater_indices = &[1, 0, 1, 4, 6, 2, 8, 0];
2747 result.resize(8 * 2 + repeater_indices.len(), 0 as _);
2748 let mut generator = LayoutCacheGenerator::new(repeater_indices, &mut result);
2749 generator.add(0., 50.); generator.add(80., 10.); generator.add(160., 10.);
2752 generator.add(240., 10.);
2753 generator.add(320., 10.); generator.add(400., 80.); generator.add(500., 20.); generator.add(600., 20.); assert_eq!(
2758 result.as_slice(),
2759 &[
2760 0., 50., 12., 13., 12., 13., 400., 80., 20., 21., 0., 0., 80., 10., 160., 10., 240., 10., 320., 10., 500., 20., 600., 20. ]
2769 );
2770 }
2771}