use crate::items::{
DialogButtonRole, FlexboxLayoutAlignContent, FlexboxLayoutAlignItems, FlexboxLayoutAlignSelf,
FlexboxLayoutDirection, FlexboxLayoutWrap, LayoutAlignment,
};
use crate::{Coord, SharedVector, slice::Slice};
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use num_traits::Float;
pub use crate::items::Orientation;
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LayoutInfo {
pub max: Coord,
pub max_percent: Coord,
pub min: Coord,
pub min_percent: Coord,
pub preferred: Coord,
pub stretch: f32,
}
impl Default for LayoutInfo {
fn default() -> Self {
LayoutInfo {
min: 0 as _,
max: Coord::MAX,
min_percent: 0 as _,
max_percent: 100 as _,
preferred: 0 as _,
stretch: 0 as _,
}
}
}
impl LayoutInfo {
#[must_use]
pub fn merge(&self, other: &LayoutInfo) -> Self {
Self {
min: self.min.max(other.min),
max: self.max.min(other.max),
min_percent: self.min_percent.max(other.min_percent),
max_percent: self.max_percent.min(other.max_percent),
preferred: self.preferred.max(other.preferred),
stretch: self.stretch.min(other.stretch),
}
}
#[must_use]
pub fn preferred_bounded(&self) -> Coord {
self.preferred.min(self.max).max(self.min)
}
}
impl core::ops::Add for LayoutInfo {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
self.merge(&rhs)
}
}
pub fn min_max_size_for_layout_constraints(
constraints_horizontal: LayoutInfo,
constraints_vertical: LayoutInfo,
) -> (Option<crate::api::LogicalSize>, Option<crate::api::LogicalSize>) {
let min_width = constraints_horizontal.min.min(constraints_horizontal.max) as f32;
let min_height = constraints_vertical.min.min(constraints_vertical.max) as f32;
let max_width = constraints_horizontal.max.max(constraints_horizontal.min) as f32;
let max_height = constraints_vertical.max.max(constraints_vertical.min) as f32;
let min_size = if min_width > 0. || min_height > 0. || cfg!(target_arch = "wasm32") {
Some(crate::api::LogicalSize::new(min_width, min_height))
} else {
None
};
let max_size = if (max_width > 0.
&& max_height > 0.
&& (max_width < i32::MAX as f32 || max_height < i32::MAX as f32))
|| cfg!(target_arch = "wasm32")
{
let window_size_max = 16_777_215.;
Some(crate::api::LogicalSize::new(
max_width.min(window_size_max),
max_height.min(window_size_max),
))
} else {
None
};
(min_size, max_size)
}
trait Saturating {
fn add(_: Self, _: Self) -> Self;
}
impl Saturating for i32 {
#[inline]
fn add(a: Self, b: Self) -> Self {
a.saturating_add(b)
}
}
impl Saturating for f32 {
#[inline]
fn add(a: Self, b: Self) -> Self {
a + b
}
}
mod grid_internal {
use super::*;
fn order_coord<T: PartialOrd>(a: &T, b: &T) -> core::cmp::Ordering {
a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)
}
#[derive(Debug, Clone)]
pub struct LayoutData {
pub min: Coord,
pub max: Coord,
pub pref: Coord,
pub stretch: f32,
pub pos: Coord,
pub size: Coord,
}
impl Default for LayoutData {
fn default() -> Self {
LayoutData {
min: 0 as _,
max: Coord::MAX,
pref: 0 as _,
stretch: f32::MAX,
pos: 0 as _,
size: 0 as _,
}
}
}
trait Adjust {
fn can_grow(_: &LayoutData) -> Coord;
fn to_distribute(expected_size: Coord, current_size: Coord) -> Coord;
fn distribute(_: &mut LayoutData, val: Coord);
}
struct Grow;
impl Adjust for Grow {
fn can_grow(it: &LayoutData) -> Coord {
it.max - it.size
}
fn to_distribute(expected_size: Coord, current_size: Coord) -> Coord {
expected_size - current_size
}
fn distribute(it: &mut LayoutData, val: Coord) {
it.size += val;
}
}
struct Shrink;
impl Adjust for Shrink {
fn can_grow(it: &LayoutData) -> Coord {
it.size - it.min
}
fn to_distribute(expected_size: Coord, current_size: Coord) -> Coord {
current_size - expected_size
}
fn distribute(it: &mut LayoutData, val: Coord) {
it.size -= val;
}
}
#[allow(clippy::unnecessary_cast)] fn adjust_items<A: Adjust>(data: &mut [LayoutData], size_without_spacing: Coord) -> Option<()> {
loop {
let size_cannot_grow: Coord = data
.iter()
.filter(|it| A::can_grow(it) <= 0 as _)
.map(|it| it.size)
.fold(0 as Coord, Saturating::add);
let total_stretch: f32 =
data.iter().filter(|it| A::can_grow(it) > 0 as _).map(|it| it.stretch).sum();
let actual_stretch = |s: f32| if total_stretch <= 0. { 1. } else { s };
let max_grow = data
.iter()
.filter(|it| A::can_grow(it) > 0 as _)
.map(|it| A::can_grow(it) as f32 / actual_stretch(it.stretch))
.min_by(order_coord)?;
let current_size: Coord = data
.iter()
.filter(|it| A::can_grow(it) > 0 as _)
.map(|it| it.size)
.fold(0 as _, Saturating::add);
let to_distribute =
A::to_distribute(size_without_spacing, size_cannot_grow + current_size) as f32;
if to_distribute <= 0. || max_grow <= 0. {
return Some(());
}
let grow = if total_stretch <= 0. {
to_distribute
/ (data.iter().filter(|it| A::can_grow(it) > 0 as _).count() as Coord) as f32
} else {
to_distribute / total_stretch
}
.min(max_grow);
let mut distributed = 0 as Coord;
for it in data.iter_mut().filter(|it| A::can_grow(it) > 0 as Coord) {
let val = (grow * actual_stretch(it.stretch)) as Coord;
A::distribute(it, val);
distributed += val;
}
if distributed <= 0 as Coord {
if let Some(it) = data
.iter_mut()
.filter(|it| A::can_grow(it) > 0 as _)
.max_by(|a, b| actual_stretch(a.stretch).total_cmp(&b.stretch))
{
A::distribute(it, to_distribute as Coord);
}
return Some(());
}
}
}
pub fn layout_items(data: &mut [LayoutData], start_pos: Coord, size: Coord, spacing: Coord) {
let size_without_spacing = size - spacing * (data.len() - 1) as Coord;
let mut pref = 0 as Coord;
for it in data.iter_mut() {
it.size = it.pref;
pref += it.pref;
}
if size_without_spacing >= pref {
adjust_items::<Grow>(data, size_without_spacing);
} else if size_without_spacing < pref {
adjust_items::<Shrink>(data, size_without_spacing);
}
let mut pos = start_pos;
for it in data.iter_mut() {
it.pos = pos;
pos = Saturating::add(pos, Saturating::add(it.size, spacing));
}
}
#[test]
#[allow(clippy::float_cmp)] fn test_layout_items() {
let my_items = &mut [
LayoutData { min: 100., max: 200., pref: 100., stretch: 1., ..Default::default() },
LayoutData { min: 50., max: 300., pref: 100., stretch: 1., ..Default::default() },
LayoutData { min: 50., max: 150., pref: 100., stretch: 1., ..Default::default() },
];
layout_items(my_items, 100., 650., 0.);
assert_eq!(my_items[0].size, 200.);
assert_eq!(my_items[1].size, 300.);
assert_eq!(my_items[2].size, 150.);
layout_items(my_items, 100., 200., 0.);
assert_eq!(my_items[0].size, 100.);
assert_eq!(my_items[1].size, 50.);
assert_eq!(my_items[2].size, 50.);
layout_items(my_items, 100., 300., 0.);
assert_eq!(my_items[0].size, 100.);
assert_eq!(my_items[1].size, 100.);
assert_eq!(my_items[2].size, 100.);
}
pub fn to_layout_data(
organized_data: &GridLayoutOrganizedData,
constraints: Slice<LayoutItemInfo>,
orientation: Orientation,
repeater_indices: Slice<u32>,
repeater_steps: Slice<u32>,
spacing: Coord,
size: Option<Coord>,
) -> Vec<LayoutData> {
assert!(organized_data.len().is_multiple_of(4));
let num = organized_data.max_value(
constraints.len(),
orientation,
&repeater_indices,
&repeater_steps,
) as usize;
if num < 1 {
return Default::default();
}
let marker_for_empty = -1.;
let mut layout_data = alloc::vec![grid_internal::LayoutData { max: 0 as Coord, stretch: marker_for_empty, ..Default::default() }; num];
let mut has_spans = false;
for (idx, cell_data) in constraints.iter().enumerate() {
let constraint = &cell_data.constraint;
let mut max = constraint.max;
if let Some(size) = size {
max = max.min(size * constraint.max_percent / 100 as Coord);
}
let (col_or_row, span) = organized_data.col_or_row_and_span(
idx,
orientation,
&repeater_indices,
&repeater_steps,
);
for c in 0..(span as usize) {
let cdata = &mut layout_data[col_or_row as usize + c];
if cdata.stretch == marker_for_empty {
cdata.max = Coord::MAX;
cdata.stretch = 1.;
}
cdata.max = cdata.max.min(max);
}
if span == 1 {
let mut min = constraint.min;
if let Some(size) = size {
min = min.max(size * constraint.min_percent / 100 as Coord);
}
let pref = constraint.preferred.min(max).max(min);
let cdata = &mut layout_data[col_or_row as usize];
cdata.min = cdata.min.max(min);
cdata.pref = cdata.pref.max(pref);
cdata.stretch = cdata.stretch.min(constraint.stretch);
} else {
has_spans = true;
}
}
if has_spans {
for (idx, cell_data) in constraints.iter().enumerate() {
let constraint = &cell_data.constraint;
let (col_or_row, span) = organized_data.col_or_row_and_span(
idx,
orientation,
&repeater_indices,
&repeater_steps,
);
if span > 1 {
let span_data =
&mut layout_data[(col_or_row as usize)..(col_or_row + span) as usize];
let mut min = constraint.min;
if let Some(size) = size {
min = min.max(size * constraint.min_percent / 100 as Coord);
}
grid_internal::layout_items(span_data, 0 as _, min, spacing);
for cdata in span_data.iter_mut() {
if cdata.min < cdata.size {
cdata.min = cdata.size;
}
}
let mut max = constraint.max;
if let Some(size) = size {
max = max.min(size * constraint.max_percent / 100 as Coord);
}
grid_internal::layout_items(span_data, 0 as _, max, spacing);
for cdata in span_data.iter_mut() {
if cdata.max > cdata.size {
cdata.max = cdata.size;
}
}
grid_internal::layout_items(span_data, 0 as _, constraint.preferred, spacing);
for cdata in span_data.iter_mut() {
cdata.pref = cdata.pref.max(cdata.size).min(cdata.max).max(cdata.min);
}
let total_stretch: f32 = span_data.iter().map(|c| c.stretch).sum();
if total_stretch > constraint.stretch {
for cdata in span_data.iter_mut() {
cdata.stretch *= constraint.stretch / total_stretch;
}
}
}
}
}
for cdata in layout_data.iter_mut() {
if cdata.stretch == marker_for_empty {
cdata.stretch = 0.;
}
}
layout_data
}
}
#[repr(C)]
pub struct Constraint {
pub min: Coord,
pub max: Coord,
}
impl Default for Constraint {
fn default() -> Self {
Constraint { min: 0 as Coord, max: Coord::MAX }
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct Padding {
pub begin: Coord,
pub end: Coord,
}
#[repr(C)]
#[derive(Debug)]
pub struct GridLayoutData {
pub size: Coord,
pub spacing: Coord,
pub padding: Padding,
pub organized_data: GridLayoutOrganizedData,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct GridLayoutInputData {
pub new_row: bool,
pub col: f32,
pub row: f32,
pub colspan: f32,
pub rowspan: f32,
}
impl Default for GridLayoutInputData {
fn default() -> Self {
Self {
new_row: false,
col: i_slint_common::ROW_COL_AUTO,
row: i_slint_common::ROW_COL_AUTO,
colspan: 1.0,
rowspan: 1.0,
}
}
}
pub type GridLayoutOrganizedData = SharedVector<u16>;
impl GridLayoutOrganizedData {
fn push_cell(&mut self, col: u16, colspan: u16, row: u16, rowspan: u16) {
self.push(col);
self.push(colspan);
self.push(row);
self.push(rowspan);
}
fn col_or_row_and_span(
&self,
cell_number: usize,
orientation: Orientation,
repeater_indices: &Slice<u32>,
repeater_steps: &Slice<u32>,
) -> (u16, u16) {
let mut final_idx = 0;
let mut cell_nr_adj = 0i32; let cell_number = cell_number as i32;
for rep_idx in 0..(repeater_indices.len() / 2) {
let ri_start_cell = repeater_indices[rep_idx * 2] as i32;
if cell_number < ri_start_cell {
break;
}
let ri_cell_count = repeater_indices[rep_idx * 2 + 1] as i32;
let step = repeater_steps.get(rep_idx).copied().unwrap_or(1) as i32;
let cells_in_repeater = ri_cell_count * step;
if cells_in_repeater > 0
&& cell_number >= ri_start_cell
&& cell_number < ri_start_cell + cells_in_repeater
{
let cell_in_rep = cell_number - ri_start_cell;
let row_in_rep = cell_in_rep / step;
let col_in_rep = cell_in_rep % step;
let jump_pos = (ri_start_cell - cell_nr_adj) as usize * 4;
let data_base = self[jump_pos] as usize;
let stride = self[jump_pos + 1] as usize;
final_idx = data_base + row_in_rep as usize * stride + col_in_rep as usize * 4;
break;
}
cell_nr_adj += cells_in_repeater - 1;
}
if final_idx == 0 {
final_idx = ((cell_number - cell_nr_adj) * 4) as usize;
}
let offset = if orientation == Orientation::Horizontal { 0 } else { 2 };
(self[final_idx + offset], self[final_idx + offset + 1])
}
fn max_value(
&self,
num_cells: usize,
orientation: Orientation,
repeater_indices: &Slice<u32>,
repeater_steps: &Slice<u32>,
) -> u16 {
let mut max = 0;
for idx in 0..num_cells {
let (col_or_row, span) =
self.col_or_row_and_span(idx, orientation, repeater_indices, repeater_steps);
max = max.max(col_or_row + span.max(1));
}
max
}
}
struct OrganizedDataGenerator<'a> {
repeater_indices: &'a [u32],
repeater_steps: &'a [u32],
counter: usize,
repeat_u16_offset: usize,
next_rep: usize,
current_offset: usize,
result: &'a mut GridLayoutOrganizedData,
}
impl<'a> OrganizedDataGenerator<'a> {
fn new(
repeater_indices: &'a [u32],
repeater_steps: &'a [u32],
static_cells: usize,
num_repeaters: usize,
total_repeated_cells_count: usize,
result: &'a mut GridLayoutOrganizedData,
) -> Self {
result.resize((static_cells + num_repeaters + total_repeated_cells_count) * 4, 0 as _);
let repeat_u16_offset = (static_cells + num_repeaters) * 4;
Self {
repeater_indices,
repeater_steps,
counter: 0,
repeat_u16_offset,
next_rep: 0,
current_offset: 0,
result,
}
}
fn add(&mut self, col: u16, colspan: u16, row: u16, rowspan: u16) {
let res = self.result.make_mut_slice();
loop {
if let Some(nr) = self.repeater_indices.get(self.next_rep * 2) {
let nr = *nr as usize;
let step = self.repeater_steps.get(self.next_rep).copied().unwrap_or(1) as usize;
let rep_count = self.repeater_indices[self.next_rep * 2 + 1] as usize;
if nr == self.counter {
let data_u16_start = self.repeat_u16_offset;
let stride = step * 4;
res[self.current_offset * 4] = data_u16_start as _;
res[self.current_offset * 4 + 1] = stride as _;
self.current_offset += 1;
}
if self.counter >= nr {
let cells_in_repeater = rep_count * step;
if self.counter - nr == cells_in_repeater {
self.repeat_u16_offset += cells_in_repeater * 4;
self.next_rep += 1;
continue;
}
let cell_in_rep = self.counter - nr;
let row_in_rep = cell_in_rep / step;
let col_in_rep = cell_in_rep % step;
let data_u16_start = self.repeat_u16_offset;
let u16_pos = data_u16_start + row_in_rep * step * 4 + col_in_rep * 4;
res[u16_pos] = col;
res[u16_pos + 1] = colspan;
res[u16_pos + 2] = row;
res[u16_pos + 3] = rowspan;
self.counter += 1;
return;
}
}
res[self.current_offset * 4] = col;
res[self.current_offset * 4 + 1] = colspan;
res[self.current_offset * 4 + 2] = row;
res[self.current_offset * 4 + 3] = rowspan;
self.current_offset += 1;
self.counter += 1;
return;
}
}
}
pub fn organize_dialog_button_layout(
input_data: Slice<GridLayoutInputData>,
dialog_button_roles: Slice<DialogButtonRole>,
) -> GridLayoutOrganizedData {
let mut organized_data = GridLayoutOrganizedData::default();
organized_data.reserve(input_data.len() * 4);
#[cfg(feature = "std")]
fn is_kde() -> bool {
std::env::var("XDG_CURRENT_DESKTOP")
.ok()
.and_then(|v| v.as_bytes().first().copied())
.is_some_and(|x| x.eq_ignore_ascii_case(&b'K'))
}
#[cfg(not(feature = "std"))]
let is_kde = || true;
let expected_order: &[DialogButtonRole] = match crate::detect_operating_system() {
crate::items::OperatingSystemType::Windows => {
&[
DialogButtonRole::Reset,
DialogButtonRole::None, DialogButtonRole::Accept,
DialogButtonRole::Action,
DialogButtonRole::Reject,
DialogButtonRole::Apply,
DialogButtonRole::Help,
]
}
crate::items::OperatingSystemType::Macos | crate::items::OperatingSystemType::Ios => {
&[
DialogButtonRole::Help,
DialogButtonRole::Reset,
DialogButtonRole::Apply,
DialogButtonRole::Action,
DialogButtonRole::None, DialogButtonRole::Reject,
DialogButtonRole::Accept,
]
}
_ if is_kde() => {
&[
DialogButtonRole::Help,
DialogButtonRole::Reset,
DialogButtonRole::None, DialogButtonRole::Action,
DialogButtonRole::Accept,
DialogButtonRole::Apply,
DialogButtonRole::Reject,
]
}
_ => {
&[
DialogButtonRole::Help,
DialogButtonRole::Reset,
DialogButtonRole::None, DialogButtonRole::Action,
DialogButtonRole::Accept,
DialogButtonRole::Apply,
DialogButtonRole::Reject,
]
}
};
let mut column_for_input: Vec<usize> = Vec::with_capacity(dialog_button_roles.len());
for role in expected_order.iter() {
if role == &DialogButtonRole::None {
column_for_input.push(usize::MAX); continue;
}
for (idx, r) in dialog_button_roles.as_slice().iter().enumerate() {
if *r == *role {
column_for_input.push(idx);
}
}
}
for (input_index, cell) in input_data.as_slice().iter().enumerate() {
let col = column_for_input.iter().position(|&x| x == input_index);
if let Some(col) = col {
organized_data.push_cell(col as _, cell.colspan as _, cell.row as _, cell.rowspan as _);
} else {
organized_data.push_cell(
cell.col as _,
cell.colspan as _,
cell.row as _,
cell.rowspan as _,
);
}
}
organized_data
}
fn total_repeated_cells<'a>(repeater_indices: &'a [u32], repeater_steps: &'a [u32]) -> usize {
repeater_indices
.chunks(2)
.enumerate()
.map(|(i, chunk)| {
let count = chunk.get(1).copied().unwrap_or(0) as usize;
let step = repeater_steps.get(i).copied().unwrap_or(1) as usize;
count * step
})
.sum()
}
type Errors = Vec<String>;
pub fn organize_grid_layout(
input_data: Slice<GridLayoutInputData>,
repeater_indices: Slice<u32>,
repeater_steps: Slice<u32>,
) -> GridLayoutOrganizedData {
let (organized_data, errors) =
organize_grid_layout_impl(input_data, repeater_indices, repeater_steps);
for error in errors {
crate::debug_log!("Slint layout error: {}", error);
}
organized_data
}
fn organize_grid_layout_impl(
input_data: Slice<GridLayoutInputData>,
repeater_indices: Slice<u32>,
repeater_steps: Slice<u32>,
) -> (GridLayoutOrganizedData, Errors) {
let mut organized_data = GridLayoutOrganizedData::default();
let num_repeaters = repeater_indices.len() / 2;
let total_repeated_cells =
total_repeated_cells(repeater_indices.as_slice(), repeater_steps.as_slice());
let static_cells = input_data.len() - total_repeated_cells;
let mut generator = OrganizedDataGenerator::new(
repeater_indices.as_slice(),
repeater_steps.as_slice(),
static_cells,
num_repeaters,
total_repeated_cells,
&mut organized_data,
);
let mut errors = Vec::new();
fn clamp_to_u16(value: f32, field_name: &str, errors: &mut Vec<String>) -> u16 {
if value < 0.0 {
errors.push(format!("cell {field_name} {value} is negative, clamping to 0"));
0
} else if value > u16::MAX as f32 {
errors
.push(format!("cell {field_name} {value} is too large, clamping to {}", u16::MAX));
u16::MAX
} else {
value as u16
}
}
let mut row = 0;
let mut col = 0;
let mut first = true;
for cell in input_data.as_slice().iter() {
if cell.new_row && !first {
row += 1;
col = 0;
}
first = false;
if cell.row != i_slint_common::ROW_COL_AUTO {
let cell_row = clamp_to_u16(cell.row, "row", &mut errors);
if row != cell_row {
row = cell_row;
col = 0;
}
}
if cell.col != i_slint_common::ROW_COL_AUTO {
col = clamp_to_u16(cell.col, "col", &mut errors);
}
let colspan = clamp_to_u16(cell.colspan, "colspan", &mut errors);
let rowspan = clamp_to_u16(cell.rowspan, "rowspan", &mut errors);
col = col.min(u16::MAX - colspan); generator.add(col, colspan, row, rowspan);
col += colspan;
}
(organized_data, errors)
}
struct LayoutCacheGenerator<'a> {
repeater_indices: &'a [u32],
counter: usize,
repeat_offset: usize,
next_rep: usize,
current_offset: usize,
result: &'a mut SharedVector<Coord>,
}
impl<'a> LayoutCacheGenerator<'a> {
fn new(repeater_indices: &'a [u32], result: &'a mut SharedVector<Coord>) -> Self {
let total_repeated_cells: usize = repeater_indices
.chunks(2)
.map(|chunk| chunk.get(1).copied().unwrap_or(0) as usize)
.sum();
assert!(result.len() >= total_repeated_cells * 2);
let repeat_offset = result.len() / 2 - total_repeated_cells;
Self { repeater_indices, counter: 0, repeat_offset, next_rep: 0, current_offset: 0, result }
}
fn add(&mut self, pos: Coord, size: Coord) {
let res = self.result.make_mut_slice();
let o = loop {
if let Some(nr) = self.repeater_indices.get(self.next_rep * 2) {
let nr = *nr as usize;
if nr == self.counter {
for o in 0..2 {
res[self.current_offset * 2 + o] = (self.repeat_offset * 2 + o) as _;
}
self.current_offset += 1;
}
if self.counter >= nr {
let rep_count = self.repeater_indices[self.next_rep * 2 + 1] as usize;
if self.counter - nr == rep_count {
self.repeat_offset += rep_count;
self.next_rep += 1;
continue;
}
let offset = self.repeat_offset + (self.counter - nr);
break offset;
}
}
self.current_offset += 1;
break self.current_offset - 1;
};
res[o * 2] = pos;
res[o * 2 + 1] = size;
self.counter += 1;
}
}
struct GridLayoutCacheGenerator<'a> {
repeater_indices: &'a [u32],
repeater_steps: &'a [u32],
counter: usize,
repeat_f32_offset: usize,
next_rep: usize,
current_offset: usize,
result: &'a mut SharedVector<Coord>,
}
impl<'a> GridLayoutCacheGenerator<'a> {
fn new(
repeater_indices: &'a [u32],
repeater_steps: &'a [u32],
static_cells: usize,
num_repeaters: usize,
total_repeated_cells_count: usize,
result: &'a mut SharedVector<Coord>,
) -> Self {
result.resize((static_cells + num_repeaters + total_repeated_cells_count) * 2, 0 as _);
let repeat_f32_offset = (static_cells + num_repeaters) * 2;
Self {
repeater_indices,
repeater_steps,
counter: 0,
repeat_f32_offset,
next_rep: 0,
current_offset: 0,
result,
}
}
fn add(&mut self, pos: Coord, size: Coord) {
let res = self.result.make_mut_slice();
loop {
if let Some(nr) = self.repeater_indices.get(self.next_rep * 2) {
let nr = *nr as usize;
let step = self.repeater_steps.get(self.next_rep).copied().unwrap_or(1) as usize;
let rep_count = self.repeater_indices[self.next_rep * 2 + 1] as usize;
if nr == self.counter {
let data_f32_start = self.repeat_f32_offset;
let stride = step * 2;
res[self.current_offset * 2] = data_f32_start as _;
res[self.current_offset * 2 + 1] = stride as _;
self.current_offset += 1;
}
if self.counter >= nr {
let cells_in_repeater = rep_count * step;
if self.counter - nr == cells_in_repeater {
self.repeat_f32_offset += cells_in_repeater * 2;
self.next_rep += 1;
continue;
}
let cell_in_rep = self.counter - nr;
let row_in_rep = cell_in_rep / step;
let col_in_rep = cell_in_rep % step;
let data_f32_start = self.repeat_f32_offset;
let f32_pos = data_f32_start + row_in_rep * step * 2 + col_in_rep * 2;
res[f32_pos] = pos;
res[f32_pos + 1] = size;
self.counter += 1;
return;
}
}
res[self.current_offset * 2] = pos;
res[self.current_offset * 2 + 1] = size;
self.current_offset += 1;
self.counter += 1;
return;
}
}
}
pub fn solve_grid_layout(
data: &GridLayoutData,
constraints: Slice<LayoutItemInfo>,
orientation: Orientation,
repeater_indices: Slice<u32>,
repeater_steps: Slice<u32>,
) -> SharedVector<Coord> {
let mut layout_data = grid_internal::to_layout_data(
&data.organized_data,
constraints,
orientation,
repeater_indices,
repeater_steps,
data.spacing,
Some(data.size),
);
if layout_data.is_empty() {
return Default::default();
}
grid_internal::layout_items(
&mut layout_data,
data.padding.begin,
data.size - (data.padding.begin + data.padding.end),
data.spacing,
);
let mut result = SharedVector::<Coord>::default();
let num_repeaters = repeater_indices.len() / 2;
let total_repeated_cells =
total_repeated_cells(repeater_indices.as_slice(), repeater_steps.as_slice());
let static_cells = constraints.len() - total_repeated_cells;
let mut generator = GridLayoutCacheGenerator::new(
repeater_indices.as_slice(),
repeater_steps.as_slice(),
static_cells,
num_repeaters,
total_repeated_cells,
&mut result,
);
for idx in 0..constraints.len() {
let (col_or_row, span) = data.organized_data.col_or_row_and_span(
idx,
orientation,
&repeater_indices,
&repeater_steps,
);
let cdata = &layout_data[col_or_row as usize];
let size = if span > 0 {
let last_cell = &layout_data[col_or_row as usize + span as usize - 1];
last_cell.pos + last_cell.size - cdata.pos
} else {
0 as Coord
};
generator.add(cdata.pos, size);
}
result
}
pub fn grid_layout_info(
organized_data: GridLayoutOrganizedData, constraints: Slice<LayoutItemInfo>,
repeater_indices: Slice<u32>,
repeater_steps: Slice<u32>,
spacing: Coord,
padding: &Padding,
orientation: Orientation,
) -> LayoutInfo {
let layout_data = grid_internal::to_layout_data(
&organized_data,
constraints,
orientation,
repeater_indices,
repeater_steps,
spacing,
None,
);
if layout_data.is_empty() {
let mut info = LayoutInfo::default();
info.min = padding.begin + padding.end;
info.preferred = info.min;
info.max = info.min;
return info;
}
let spacing_w = spacing * (layout_data.len() - 1) as Coord + padding.begin + padding.end;
let min = layout_data.iter().map(|data| data.min).sum::<Coord>() + spacing_w;
let max = layout_data.iter().map(|data| data.max).fold(spacing_w, Saturating::add);
let preferred = layout_data.iter().map(|data| data.pref).sum::<Coord>() + spacing_w;
let stretch = layout_data.iter().map(|data| data.stretch).sum::<f32>();
LayoutInfo { min, max, min_percent: 0 as _, max_percent: 100 as _, preferred, stretch }
}
#[repr(C)]
#[derive(Debug)]
pub struct BoxLayoutData<'a> {
pub size: Coord,
pub spacing: Coord,
pub padding: Padding,
pub alignment: LayoutAlignment,
pub cells: Slice<'a, LayoutItemInfo>,
}
#[repr(C)]
#[derive(Debug)]
pub struct FlexboxLayoutData<'a> {
pub width: Coord,
pub height: Coord,
pub spacing_h: Coord,
pub spacing_v: Coord,
pub padding_h: Padding,
pub padding_v: Padding,
pub alignment: LayoutAlignment,
pub direction: FlexboxLayoutDirection,
pub align_content: FlexboxLayoutAlignContent,
pub align_items: FlexboxLayoutAlignItems,
pub flex_wrap: FlexboxLayoutWrap,
pub cells_h: Slice<'a, FlexboxLayoutItemInfo>,
pub cells_v: Slice<'a, FlexboxLayoutItemInfo>,
}
#[repr(C)]
#[derive(Debug, Clone, Default)]
pub struct LayoutItemInfo {
pub constraint: LayoutInfo,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct FlexboxLayoutItemInfo {
pub constraint: LayoutInfo,
pub flex_grow: f32,
pub flex_shrink: f32,
pub flex_basis: Coord,
pub flex_align_self: FlexboxLayoutAlignSelf,
pub flex_order: i32,
}
impl Default for FlexboxLayoutItemInfo {
fn default() -> Self {
Self {
constraint: LayoutInfo::default(),
flex_grow: 0.0,
flex_shrink: 1.0,
flex_basis: -1 as _,
flex_align_self: FlexboxLayoutAlignSelf::Auto,
flex_order: 0,
}
}
}
impl From<LayoutItemInfo> for FlexboxLayoutItemInfo {
fn from(info: LayoutItemInfo) -> Self {
Self { constraint: info.constraint, ..Default::default() }
}
}
pub fn solve_box_layout(data: &BoxLayoutData, repeater_indices: Slice<u32>) -> SharedVector<Coord> {
let mut result = SharedVector::<Coord>::default();
result.resize(data.cells.len() * 2 + repeater_indices.len(), 0 as _);
if data.cells.is_empty() {
return result;
}
let size_without_padding = data.size - data.padding.begin - data.padding.end;
let num_spacings = (data.cells.len() - 1) as Coord;
let spacings = data.spacing * num_spacings;
let content_size = size_without_padding - spacings; let mut layout_data: Vec<_> = data
.cells
.iter()
.map(|c| {
let min = c.constraint.min.max(c.constraint.min_percent * content_size / 100 as Coord);
let max = c.constraint.max.min(c.constraint.max_percent * content_size / 100 as Coord);
grid_internal::LayoutData {
min,
max,
pref: c.constraint.preferred.min(max).max(min),
stretch: c.constraint.stretch,
..Default::default()
}
})
.collect();
let pref_size: Coord = layout_data.iter().map(|it| it.pref).sum();
let align = match data.alignment {
LayoutAlignment::Stretch => {
grid_internal::layout_items(
&mut layout_data,
data.padding.begin,
size_without_padding,
data.spacing,
);
None
}
_ if size_without_padding <= pref_size + spacings => {
grid_internal::layout_items(
&mut layout_data,
data.padding.begin,
size_without_padding,
data.spacing,
);
None
}
LayoutAlignment::Center => Some((
data.padding.begin + (size_without_padding - pref_size - spacings) / 2 as Coord,
data.spacing,
)),
LayoutAlignment::Start => Some((data.padding.begin, data.spacing)),
LayoutAlignment::End => {
Some((data.padding.begin + (size_without_padding - pref_size - spacings), data.spacing))
}
LayoutAlignment::SpaceBetween => {
Some((data.padding.begin, (size_without_padding - pref_size) / num_spacings))
}
LayoutAlignment::SpaceAround => {
let spacing = (size_without_padding - pref_size) / (num_spacings + 1 as Coord);
Some((data.padding.begin + spacing / 2 as Coord, spacing))
}
LayoutAlignment::SpaceEvenly => {
let spacing = (size_without_padding - pref_size) / (num_spacings + 2 as Coord);
Some((data.padding.begin + spacing, spacing))
}
};
if let Some((mut pos, spacing)) = align {
for it in &mut layout_data {
it.pos = pos;
it.size = it.pref;
pos += spacing + it.size;
}
}
let mut generator = LayoutCacheGenerator::new(&repeater_indices, &mut result);
for layout in layout_data.iter() {
generator.add(layout.pos, layout.size);
}
result
}
pub fn box_layout_info(
cells: Slice<LayoutItemInfo>,
spacing: Coord,
padding: &Padding,
alignment: LayoutAlignment,
) -> LayoutInfo {
let count = cells.len();
let is_stretch = alignment == LayoutAlignment::Stretch;
if count < 1 {
let mut info = LayoutInfo::default();
info.min = padding.begin + padding.end;
info.preferred = info.min;
if is_stretch {
info.max = info.min;
}
return info;
};
let extra_w = padding.begin + padding.end + spacing * (count - 1) as Coord;
let min = cells.iter().map(|c| c.constraint.min).sum::<Coord>() + extra_w; let max = if is_stretch {
(cells.iter().map(|c| c.constraint.max).fold(extra_w, Saturating::add)).max(min)
} else {
Coord::MAX
}; let preferred = cells.iter().map(|c| c.constraint.preferred_bounded()).sum::<Coord>() + extra_w;
let stretch = cells.iter().map(|c| c.constraint.stretch).sum::<f32>();
LayoutInfo { min, max, min_percent: 0 as _, max_percent: 100 as _, preferred, stretch }
}
pub fn box_layout_info_ortho(cells: Slice<LayoutItemInfo>, padding: &Padding) -> LayoutInfo {
let extra_w = padding.begin + padding.end;
let mut fold =
cells.iter().fold(LayoutInfo { stretch: f32::MAX, ..Default::default() }, |a, b| {
a.merge(&b.constraint)
});
fold.max = fold.max.max(fold.min);
fold.preferred = fold.preferred.clamp(fold.min, fold.max);
fold.min += extra_w;
fold.max = Saturating::add(fold.max, extra_w);
fold.preferred += extra_w;
fold
}
mod flexbox_taffy {
use super::{
Coord, FlexboxLayoutAlignContent, FlexboxLayoutAlignItems, FlexboxLayoutAlignSelf,
FlexboxLayoutItemInfo, FlexboxLayoutWrap as SlintFlexboxLayoutWrap, LayoutAlignment,
Padding, Slice,
};
use alloc::vec::Vec;
pub use taffy::prelude::FlexDirection as TaffyFlexDirection;
use taffy::prelude::*;
pub struct FlexboxLayoutParams<'a> {
pub cells_h: &'a Slice<'a, FlexboxLayoutItemInfo>,
pub cells_v: &'a Slice<'a, FlexboxLayoutItemInfo>,
pub spacing_h: Coord,
pub spacing_v: Coord,
pub padding_h: &'a Padding,
pub padding_v: &'a Padding,
pub alignment: LayoutAlignment,
pub align_content: FlexboxLayoutAlignContent,
pub align_items: FlexboxLayoutAlignItems,
pub flex_wrap: SlintFlexboxLayoutWrap,
pub flex_direction: TaffyFlexDirection,
pub container_width: Option<Coord>,
pub container_height: Option<Coord>,
pub use_measure_for_cross_axis: bool,
}
pub struct FlexboxTaffyBuilder {
pub taffy: TaffyTree<usize>,
pub children: Vec<NodeId>,
pub container: NodeId,
pub order_map: Vec<usize>,
}
impl FlexboxTaffyBuilder {
pub fn new(params: FlexboxLayoutParams) -> Self {
let mut taffy = TaffyTree::<usize>::new();
let mut children: Vec<NodeId> = params
.cells_h
.iter()
.enumerate()
.map(|(idx, cell_h)| {
let cell_v = params.cells_v.get(idx);
let h_constraint = &cell_h.constraint;
let v_constraint = cell_v.map(|c| &c.constraint);
let preferred_width = h_constraint.preferred_bounded();
let preferred_height =
v_constraint.map(|vc| vc.preferred_bounded()).unwrap_or(0 as Coord);
let flex_basis = if cell_h.flex_basis >= 0 as Coord {
Dimension::length(cell_h.flex_basis as _)
} else {
match params.flex_direction {
TaffyFlexDirection::Row | TaffyFlexDirection::RowReverse => {
Dimension::length(preferred_width as _)
}
TaffyFlexDirection::Column | TaffyFlexDirection::ColumnReverse => {
Dimension::length(preferred_height as _)
}
}
};
taffy
.new_leaf_with_context(
Style {
flex_basis,
size: Size {
width: match params.flex_direction {
TaffyFlexDirection::Column
| TaffyFlexDirection::ColumnReverse => {
if let Some(cw) = params.container_width {
Dimension::length(cw as _)
} else if preferred_width > 0 as Coord {
Dimension::length(preferred_width as _)
} else {
Dimension::auto()
}
}
_ => Dimension::auto(),
},
height: match params.flex_direction {
TaffyFlexDirection::Row
| TaffyFlexDirection::RowReverse => {
if params.use_measure_for_cross_axis {
Dimension::auto()
} else if preferred_height > 0 as Coord {
Dimension::length(preferred_height as _)
} else {
Dimension::auto()
}
}
_ => Dimension::auto(),
},
},
min_size: Size {
width: Dimension::length(h_constraint.min as _),
height: Dimension::length(
v_constraint.map(|vc| vc.min as f32).unwrap_or(0.0),
),
},
max_size: Size {
width: if h_constraint.max < Coord::MAX {
Dimension::length(h_constraint.max as _)
} else {
Dimension::auto()
},
height: if let Some(vc) = v_constraint {
if vc.max < Coord::MAX {
Dimension::length(vc.max as _)
} else {
Dimension::auto()
}
} else {
Dimension::auto()
},
},
flex_grow: cell_h.flex_grow,
flex_shrink: cell_h.flex_shrink,
align_self: match cell_h.flex_align_self {
FlexboxLayoutAlignSelf::Auto => None,
FlexboxLayoutAlignSelf::Stretch => Some(AlignSelf::Stretch),
FlexboxLayoutAlignSelf::Start => Some(AlignSelf::FlexStart),
FlexboxLayoutAlignSelf::End => Some(AlignSelf::FlexEnd),
FlexboxLayoutAlignSelf::Center => Some(AlignSelf::Center),
},
..Default::default()
},
idx,
)
.unwrap() })
.collect();
let has_order = params.cells_h.iter().any(|c| c.flex_order != 0);
let order_map: Vec<usize> = if has_order {
let mut indices: Vec<usize> = (0..children.len()).collect();
indices.sort_by_key(|&i| params.cells_h.get(i).map_or(0, |c| c.flex_order));
let sorted_children: Vec<NodeId> = indices.iter().map(|&i| children[i]).collect();
children = sorted_children;
indices
} else {
Vec::new()
};
let container = taffy
.new_with_children(
Style {
display: Display::Flex,
flex_direction: params.flex_direction,
flex_wrap: match params.flex_wrap {
SlintFlexboxLayoutWrap::Wrap => FlexWrap::Wrap,
SlintFlexboxLayoutWrap::NoWrap => FlexWrap::NoWrap,
SlintFlexboxLayoutWrap::WrapReverse => FlexWrap::WrapReverse,
},
justify_content: Some(match params.alignment {
LayoutAlignment::Start => AlignContent::FlexStart,
LayoutAlignment::End => AlignContent::FlexEnd,
LayoutAlignment::Center => AlignContent::Center,
LayoutAlignment::Stretch => AlignContent::Stretch,
LayoutAlignment::SpaceBetween => AlignContent::SpaceBetween,
LayoutAlignment::SpaceAround => AlignContent::SpaceAround,
LayoutAlignment::SpaceEvenly => AlignContent::SpaceEvenly,
}),
align_items: Some(match params.align_items {
FlexboxLayoutAlignItems::Stretch => AlignItems::Stretch,
FlexboxLayoutAlignItems::Start => AlignItems::FlexStart,
FlexboxLayoutAlignItems::End => AlignItems::FlexEnd,
FlexboxLayoutAlignItems::Center => AlignItems::Center,
}),
align_content: Some(match params.align_content {
FlexboxLayoutAlignContent::Stretch => AlignContent::Stretch,
FlexboxLayoutAlignContent::Start => AlignContent::FlexStart,
FlexboxLayoutAlignContent::End => AlignContent::FlexEnd,
FlexboxLayoutAlignContent::Center => AlignContent::Center,
FlexboxLayoutAlignContent::SpaceBetween => AlignContent::SpaceBetween,
FlexboxLayoutAlignContent::SpaceAround => AlignContent::SpaceAround,
FlexboxLayoutAlignContent::SpaceEvenly => AlignContent::SpaceEvenly,
}),
gap: Size {
width: LengthPercentage::length(params.spacing_h as _),
height: LengthPercentage::length(params.spacing_v as _),
},
padding: Rect {
left: LengthPercentage::length(params.padding_h.begin as _),
right: LengthPercentage::length(params.padding_h.end as _),
top: LengthPercentage::length(params.padding_v.begin as _),
bottom: LengthPercentage::length(params.padding_v.end as _),
},
size: Size {
width: params
.container_width
.map(|w| Dimension::length(w as _))
.unwrap_or(Dimension::auto()),
height: params
.container_height
.map(|h| Dimension::length(h as _))
.unwrap_or(Dimension::auto()),
},
..Default::default()
},
&children,
)
.unwrap();
Self { taffy, children, container, order_map }
}
pub fn compute_layout(
&mut self,
available_width: Coord,
available_height: Coord,
mut measure: Option<
&mut dyn FnMut(usize, Option<Coord>, Option<Coord>) -> (Coord, Coord),
>,
) {
let available_space = taffy::prelude::Size {
width: if available_width < Coord::MAX {
AvailableSpace::Definite(available_width as _)
} else {
AvailableSpace::MaxContent
},
height: if available_height < Coord::MAX {
AvailableSpace::Definite(available_height as _)
} else {
AvailableSpace::MaxContent
},
};
self.taffy
.compute_layout_with_measure(
self.container,
available_space,
|known_dimensions, _available_space, _node_id, node_context, _style| {
if let (Some(measure), Some(&mut child_index)) =
(measure.as_deref_mut(), node_context)
{
let known_w = known_dimensions.width.map(|w| w as Coord);
let known_h = known_dimensions.height.map(|h| h as Coord);
let (w, h) = measure(child_index, known_w, known_h);
taffy::prelude::Size { width: w as f32, height: h as f32 }
} else {
taffy::prelude::Size::ZERO
}
},
)
.unwrap_or_else(|e| {
crate::debug_log!("FlexboxLayout computation error: {}", e);
});
}
pub fn container_size(&self) -> (Coord, Coord) {
let layout = self.taffy.layout(self.container).unwrap();
(layout.size.width as Coord, layout.size.height as Coord)
}
pub fn child_geometry(&self, idx: usize) -> (Coord, Coord, Coord, Coord) {
let layout = self.taffy.layout(self.children[idx]).unwrap();
(
layout.location.x as Coord,
layout.location.y as Coord,
layout.size.width as Coord,
layout.size.height as Coord,
)
}
pub fn original_index(&self, taffy_idx: usize) -> usize {
if self.order_map.is_empty() { taffy_idx } else { self.order_map[taffy_idx] }
}
}
}
struct FlexboxLayoutCacheGenerator<'a> {
repeater_indices: &'a [u32],
counter: usize,
repeat_offset: usize,
next_rep: usize,
current_offset: usize,
result: &'a mut SharedVector<Coord>,
}
impl<'a> FlexboxLayoutCacheGenerator<'a> {
fn new(repeater_indices: &'a [u32], result: &'a mut SharedVector<Coord>) -> Self {
let total_repeated_cells: usize = repeater_indices
.chunks(2)
.map(|chunk| chunk.get(1).copied().unwrap_or(0) as usize)
.sum();
assert!(result.len() >= total_repeated_cells * 4);
let repeat_offset = result.len() / 4 - total_repeated_cells;
Self { repeater_indices, counter: 0, repeat_offset, next_rep: 0, current_offset: 0, result }
}
fn add(&mut self, x: Coord, y: Coord, w: Coord, h: Coord) {
let res = self.result.make_mut_slice();
let o = loop {
if let Some(nr) = self.repeater_indices.get(self.next_rep * 2) {
let nr = *nr as usize;
if nr == self.counter {
res[self.current_offset * 4] = (self.repeat_offset * 4) as Coord;
res[self.current_offset * 4 + 1] = (self.repeat_offset * 4 + 1) as Coord;
res[self.current_offset * 4 + 2] = (self.repeat_offset * 4 + 2) as Coord;
res[self.current_offset * 4 + 3] = (self.repeat_offset * 4 + 3) as Coord;
self.current_offset += 1;
}
if self.counter >= nr {
let rep_count = self.repeater_indices[self.next_rep * 2 + 1] as usize;
if self.counter - nr == rep_count {
self.repeat_offset += rep_count;
self.next_rep += 1;
continue;
}
let cell_in_rep = self.counter - nr;
let offset = self.repeat_offset + cell_in_rep;
break offset;
}
}
self.current_offset += 1;
break self.current_offset - 1;
};
res[o * 4] = x;
res[o * 4 + 1] = y;
res[o * 4 + 2] = w;
res[o * 4 + 3] = h;
self.counter += 1;
}
}
pub type FlexboxMeasureFn<'a> =
Option<&'a mut dyn FnMut(usize, Option<Coord>, Option<Coord>) -> (Coord, Coord)>;
pub fn solve_flexbox_layout(
data: &FlexboxLayoutData,
repeater_indices: Slice<u32>,
) -> SharedVector<Coord> {
let mut measure = |child_index: usize,
known_w: Option<Coord>,
known_h: Option<Coord>|
-> (Coord, Coord) {
let w = known_w.unwrap_or_else(|| {
data.cells_h.get(child_index).map_or(0 as Coord, |c| c.constraint.preferred_bounded())
});
let h = known_h.unwrap_or_else(|| {
data.cells_v.get(child_index).map_or(0 as Coord, |c| c.constraint.preferred_bounded())
});
(w, h)
};
solve_flexbox_layout_with_measure(data, repeater_indices, Some(&mut measure))
}
pub fn solve_flexbox_layout_with_measure(
data: &FlexboxLayoutData,
repeater_indices: Slice<u32>,
measure: FlexboxMeasureFn<'_>,
) -> SharedVector<Coord> {
let mut result = SharedVector::<Coord>::default();
result.resize(data.cells_h.len() * 4 + repeater_indices.len() * 2, 0 as _);
if data.cells_h.is_empty() {
return result;
}
let taffy_direction = match data.direction {
FlexboxLayoutDirection::Row => flexbox_taffy::TaffyFlexDirection::Row,
FlexboxLayoutDirection::RowReverse => flexbox_taffy::TaffyFlexDirection::RowReverse,
FlexboxLayoutDirection::Column => flexbox_taffy::TaffyFlexDirection::Column,
FlexboxLayoutDirection::ColumnReverse => flexbox_taffy::TaffyFlexDirection::ColumnReverse,
};
let (container_width, container_height) = (
if data.width > 0 as Coord { Some(data.width) } else { None },
if data.height > 0 as Coord { Some(data.height) } else { None },
);
let use_measure = measure.is_some();
let mut builder = flexbox_taffy::FlexboxTaffyBuilder::new(flexbox_taffy::FlexboxLayoutParams {
cells_h: &data.cells_h,
cells_v: &data.cells_v,
spacing_h: data.spacing_h,
spacing_v: data.spacing_v,
padding_h: &data.padding_h,
padding_v: &data.padding_v,
alignment: data.alignment,
align_content: data.align_content,
align_items: data.align_items,
flex_wrap: data.flex_wrap,
flex_direction: taffy_direction,
container_width,
container_height,
use_measure_for_cross_axis: use_measure,
});
let (available_width, available_height) = match data.direction {
FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
(data.width, Coord::MAX)
}
FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
(Coord::MAX, data.height)
}
};
builder.compute_layout(available_width, available_height, measure);
if builder.order_map.is_empty() {
let mut generator = FlexboxLayoutCacheGenerator::new(&repeater_indices, &mut result);
for idx in 0..data.cells_h.len() {
let (x, y, w, h) = builder.child_geometry(idx);
generator.add(x, y, w, h);
}
} else {
let count = data.cells_h.len();
let mut geom = alloc::vec![(0 as Coord, 0 as Coord, 0 as Coord, 0 as Coord); count];
for taffy_idx in 0..count {
let orig_idx = builder.original_index(taffy_idx);
geom[orig_idx] = builder.child_geometry(taffy_idx);
}
let mut generator = FlexboxLayoutCacheGenerator::new(&repeater_indices, &mut result);
for (x, y, w, h) in geom {
generator.add(x, y, w, h);
}
}
result
}
pub fn flexbox_layout_info_main_axis(
cells: Slice<FlexboxLayoutItemInfo>,
spacing: Coord,
padding: &Padding,
flex_wrap: FlexboxLayoutWrap,
) -> LayoutInfo {
let extra_pad = padding.begin + padding.end;
if cells.is_empty() {
return LayoutInfo {
min: extra_pad,
preferred: extra_pad,
max: extra_pad,
..Default::default()
};
}
let num_spacings = cells.len().saturating_sub(1) as Coord;
let min = if matches!(flex_wrap, FlexboxLayoutWrap::NoWrap) {
cells.iter().map(|c| c.constraint.min).sum::<Coord>() + spacing * num_spacings + extra_pad
} else {
cells.iter().map(|c| c.constraint.min).fold(0.0 as Coord, |a, b| a.max(b)) + extra_pad
};
let preferred = if matches!(flex_wrap, FlexboxLayoutWrap::NoWrap) {
cells.iter().map(|c| c.constraint.preferred_bounded()).sum::<Coord>()
+ spacing * num_spacings
+ extra_pad
} else {
let total_area: Coord = cells
.iter()
.map(|c| {
let w = c.constraint.preferred_bounded();
w * w
})
.sum();
let count = cells.len();
Float::sqrt(total_area as f32) as Coord + spacing * (count - 1) as Coord + extra_pad
};
let stretch = cells.iter().map(|c| c.constraint.stretch).sum::<f32>();
LayoutInfo {
min,
max: Coord::MAX,
min_percent: 0 as _,
max_percent: 100 as _,
preferred,
stretch,
}
}
pub fn flexbox_layout_info_cross_axis(
cells_h: Slice<FlexboxLayoutItemInfo>,
cells_v: Slice<FlexboxLayoutItemInfo>,
spacing_h: Coord,
spacing_v: Coord,
padding_h: &Padding,
padding_v: &Padding,
direction: FlexboxLayoutDirection,
flex_wrap: FlexboxLayoutWrap,
constraint_size: Coord,
) -> LayoutInfo {
if cells_h.is_empty() {
assert!(cells_v.is_empty());
let orientation = match direction {
FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
Orientation::Vertical
}
FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
Orientation::Horizontal
}
};
let padding = match orientation {
Orientation::Horizontal => padding_h,
Orientation::Vertical => padding_v,
};
let pad = padding.begin + padding.end;
return LayoutInfo { min: pad, preferred: pad, max: pad, ..Default::default() };
}
let cross_cells = match direction {
FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => &cells_v,
FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => &cells_h,
};
let (main_cells, main_spacing, main_padding) = match direction {
FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
(&cells_h, spacing_h, padding_h)
}
FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
(&cells_v, spacing_v, padding_v)
}
};
let main_extra_pad = main_padding.begin + main_padding.end;
let main_axis_constraint = if constraint_size > 0 as Coord && constraint_size < Coord::MAX {
constraint_size
} else if matches!(flex_wrap, FlexboxLayoutWrap::NoWrap) {
Coord::MAX
} else {
let total_area: Coord = main_cells
.iter()
.zip(cross_cells.iter())
.map(|(m, c)| m.constraint.preferred_bounded() * c.constraint.preferred_bounded())
.sum();
let count = main_cells.len();
Float::sqrt(total_area as f32) as Coord
+ main_spacing * (count - 1) as Coord
+ main_extra_pad
};
let taffy_direction = match direction {
FlexboxLayoutDirection::Row => flexbox_taffy::TaffyFlexDirection::Row,
FlexboxLayoutDirection::RowReverse => flexbox_taffy::TaffyFlexDirection::RowReverse,
FlexboxLayoutDirection::Column => flexbox_taffy::TaffyFlexDirection::Column,
FlexboxLayoutDirection::ColumnReverse => flexbox_taffy::TaffyFlexDirection::ColumnReverse,
};
let (container_width, container_height) = match direction {
FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
(Some(main_axis_constraint), None)
}
FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
(None, Some(main_axis_constraint))
}
};
let mut builder = flexbox_taffy::FlexboxTaffyBuilder::new(flexbox_taffy::FlexboxLayoutParams {
cells_h: &cells_h,
cells_v: &cells_v,
spacing_h,
spacing_v,
padding_h,
padding_v,
alignment: LayoutAlignment::Start,
align_content: FlexboxLayoutAlignContent::Stretch,
align_items: FlexboxLayoutAlignItems::Stretch,
flex_wrap,
flex_direction: taffy_direction,
container_width,
container_height,
use_measure_for_cross_axis: false,
});
let (available_width, available_height) = match direction {
FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => {
(main_axis_constraint, Coord::MAX)
}
FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => {
(Coord::MAX, main_axis_constraint)
}
};
builder.compute_layout(available_width, available_height, None);
let (total_width, total_height) = builder.container_size();
let cross_size = match direction {
FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse => total_height,
FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse => total_width,
};
LayoutInfo {
min: cross_size,
max: Coord::MAX,
min_percent: 0 as _,
max_percent: 100 as _,
preferred: cross_size,
stretch: 0.0,
}
}
#[cfg(feature = "ffi")]
pub(crate) mod ffi {
#![allow(unsafe_code)]
use super::*;
#[unsafe(no_mangle)]
pub extern "C" fn slint_organize_grid_layout(
input_data: Slice<GridLayoutInputData>,
repeater_indices: Slice<u32>,
repeater_steps: Slice<u32>,
result: &mut GridLayoutOrganizedData,
) {
*result = super::organize_grid_layout(input_data, repeater_indices, repeater_steps);
}
#[unsafe(no_mangle)]
pub extern "C" fn slint_organize_dialog_button_layout(
input_data: Slice<GridLayoutInputData>,
dialog_button_roles: Slice<DialogButtonRole>,
result: &mut GridLayoutOrganizedData,
) {
*result = super::organize_dialog_button_layout(input_data, dialog_button_roles);
}
#[unsafe(no_mangle)]
pub extern "C" fn slint_solve_grid_layout(
data: &GridLayoutData,
constraints: Slice<LayoutItemInfo>,
orientation: Orientation,
repeater_indices: Slice<u32>,
repeater_steps: Slice<u32>,
result: &mut SharedVector<Coord>,
) {
*result = super::solve_grid_layout(
data,
constraints,
orientation,
repeater_indices,
repeater_steps,
)
}
#[unsafe(no_mangle)]
pub extern "C" fn slint_grid_layout_info(
organized_data: &GridLayoutOrganizedData,
constraints: Slice<LayoutItemInfo>,
repeater_indices: Slice<u32>,
repeater_steps: Slice<u32>,
spacing: Coord,
padding: &Padding,
orientation: Orientation,
) -> LayoutInfo {
super::grid_layout_info(
organized_data.clone(),
constraints,
repeater_indices,
repeater_steps,
spacing,
padding,
orientation,
)
}
#[unsafe(no_mangle)]
pub extern "C" fn slint_solve_box_layout(
data: &BoxLayoutData,
repeater_indices: Slice<u32>,
result: &mut SharedVector<Coord>,
) {
*result = super::solve_box_layout(data, repeater_indices)
}
#[unsafe(no_mangle)]
pub extern "C" fn slint_box_layout_info(
cells: Slice<LayoutItemInfo>,
spacing: Coord,
padding: &Padding,
alignment: LayoutAlignment,
) -> LayoutInfo {
super::box_layout_info(cells, spacing, padding, alignment)
}
#[unsafe(no_mangle)]
pub extern "C" fn slint_box_layout_info_ortho(
cells: Slice<LayoutItemInfo>,
padding: &Padding,
) -> LayoutInfo {
super::box_layout_info_ortho(cells, padding)
}
pub type FlexboxMeasureFnC = unsafe extern "C" fn(
user_data: *mut core::ffi::c_void,
child_index: usize,
known_width: Coord,
known_height: Coord,
out_width: *mut Coord,
out_height: *mut Coord,
);
#[unsafe(no_mangle)]
#[allow(unsafe_code)]
pub extern "C" fn slint_solve_flexbox_layout(
data: &FlexboxLayoutData,
repeater_indices: Slice<u32>,
result: &mut SharedVector<Coord>,
measure_fn: *const core::ffi::c_void,
measure_user_data: *mut core::ffi::c_void,
) {
const {
assert!(
core::mem::size_of::<*const core::ffi::c_void>()
== core::mem::size_of::<FlexboxMeasureFnC>()
);
}
let measure_fn: Option<FlexboxMeasureFnC> = if measure_fn.is_null() {
None
} else {
Some(unsafe {
core::mem::transmute::<*const core::ffi::c_void, FlexboxMeasureFnC>(measure_fn)
})
};
if let Some(c_measure) = measure_fn {
let mut measure = |child_index: usize,
known_w: Option<Coord>,
known_h: Option<Coord>|
-> (Coord, Coord) {
let mut out_w: Coord = 0 as _;
let mut out_h: Coord = 0 as _;
unsafe {
c_measure(
measure_user_data,
child_index,
known_w.unwrap_or(-1 as _),
known_h.unwrap_or(-1 as _),
&mut out_w,
&mut out_h,
);
}
(out_w, out_h)
};
*result = super::solve_flexbox_layout_with_measure(
data,
repeater_indices,
Some(&mut measure),
);
} else {
*result = super::solve_flexbox_layout(data, repeater_indices);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn slint_flexbox_layout_info_main_axis(
cells: Slice<FlexboxLayoutItemInfo>,
spacing: Coord,
padding: &Padding,
flex_wrap: FlexboxLayoutWrap,
) -> LayoutInfo {
super::flexbox_layout_info_main_axis(cells, spacing, padding, flex_wrap)
}
#[unsafe(no_mangle)]
pub extern "C" fn slint_flexbox_layout_info_cross_axis(
cells_h: Slice<FlexboxLayoutItemInfo>,
cells_v: Slice<FlexboxLayoutItemInfo>,
spacing_h: Coord,
spacing_v: Coord,
padding_h: &Padding,
padding_v: &Padding,
direction: FlexboxLayoutDirection,
flex_wrap: FlexboxLayoutWrap,
constraint_size: Coord,
) -> LayoutInfo {
super::flexbox_layout_info_cross_axis(
cells_h,
cells_v,
spacing_h,
spacing_v,
padding_h,
padding_v,
direction,
flex_wrap,
constraint_size,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn collect_from_organized_data(
organized_data: &GridLayoutOrganizedData,
num_cells: usize,
repeater_indices: Slice<u32>,
repeater_steps: Slice<u32>,
) -> Vec<(u16, u16, u16, u16)> {
let mut result = Vec::new();
for i in 0..num_cells {
let col_and_span = organized_data.col_or_row_and_span(
i,
Orientation::Horizontal,
&repeater_indices,
&repeater_steps,
);
let row_and_span = organized_data.col_or_row_and_span(
i,
Orientation::Vertical,
&repeater_indices,
&repeater_steps,
);
result.push((col_and_span.0, col_and_span.1, row_and_span.0, row_and_span.1));
}
result
}
#[test]
fn test_organized_data_generator_2_fixed_cells() {
let mut result = GridLayoutOrganizedData::default();
let num_cells = 2;
let mut generator = OrganizedDataGenerator::new(&[], &[], num_cells, 0, 0, &mut result);
generator.add(0, 1, 0, 1);
generator.add(1, 2, 0, 3);
assert_eq!(result.as_slice(), &[0, 1, 0, 1, 1, 2, 0, 3]);
let repeater_indices = Slice::from_slice(&[]);
let empty_steps = Slice::from_slice(&[]);
let collected_data =
collect_from_organized_data(&result, num_cells, repeater_indices, empty_steps);
assert_eq!(collected_data.as_slice(), &[(0, 1, 0, 1), (1, 2, 0, 3)]);
assert_eq!(
result.max_value(num_cells, Orientation::Horizontal, &repeater_indices, &empty_steps),
3
);
assert_eq!(
result.max_value(num_cells, Orientation::Vertical, &repeater_indices, &empty_steps),
3
);
}
#[test]
fn test_organized_data_generator_1_fixed_cell_1_repeater() {
let mut result = GridLayoutOrganizedData::default();
let num_cells = 4;
let repeater_indices = &[1u32, 3u32];
let mut generator =
OrganizedDataGenerator::new(repeater_indices, &[], 1, 1, 3, &mut result);
generator.add(0, 1, 0, 2); generator.add(1, 2, 1, 3); generator.add(1, 1, 2, 4);
generator.add(2, 2, 3, 5);
assert_eq!(
result.as_slice(),
&[
0, 1, 0, 2, 8, 4, 0, 0, 1, 2, 1, 3, 1, 1, 2, 4, 2, 2, 3, 5, ]
);
let repeater_indices = Slice::from_slice(repeater_indices);
let empty_steps = Slice::from_slice(&[]);
let collected_data =
collect_from_organized_data(&result, num_cells, repeater_indices, empty_steps);
assert_eq!(
collected_data.as_slice(),
&[(0, 1, 0, 2), (1, 2, 1, 3), (1, 1, 2, 4), (2, 2, 3, 5)]
);
assert_eq!(
result.max_value(num_cells, Orientation::Horizontal, &repeater_indices, &empty_steps),
4
);
assert_eq!(
result.max_value(num_cells, Orientation::Vertical, &repeater_indices, &empty_steps),
8
);
}
#[test]
fn test_organize_data_with_auto_and_spans() {
let auto = i_slint_common::ROW_COL_AUTO;
let input = std::vec![
GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 2., rowspan: -1. },
GridLayoutInputData { new_row: false, col: auto, row: auto, colspan: 1., rowspan: 2. },
GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 2., rowspan: 1. },
GridLayoutInputData { new_row: true, col: -2., row: 80000., colspan: 2., rowspan: 1. },
];
let repeater_indices = Slice::from_slice(&[]);
let (organized_data, errors) = organize_grid_layout_impl(
Slice::from_slice(&input),
repeater_indices,
Slice::from_slice(&[]),
);
assert_eq!(
organized_data.as_slice(),
&[
0, 2, 0, 0, 2, 1, 0, 2, 0, 2, 1, 1, 0, 2, 65535, 1, ]
);
assert_eq!(errors.len(), 3);
assert_eq!(errors[0], "cell rowspan -1 is negative, clamping to 0");
assert_eq!(errors[1], "cell row 80000 is too large, clamping to 65535");
assert_eq!(errors[2], "cell col -2 is negative, clamping to 0");
let empty_steps = Slice::from_slice(&[]);
let collected_data = collect_from_organized_data(
&organized_data,
input.len(),
repeater_indices,
empty_steps,
);
assert_eq!(
collected_data.as_slice(),
&[(0, 2, 0, 0), (2, 1, 0, 2), (0, 2, 1, 1), (0, 2, 65535, 1)]
);
assert_eq!(
organized_data.max_value(3, Orientation::Horizontal, &repeater_indices, &empty_steps),
3
);
assert_eq!(
organized_data.max_value(3, Orientation::Vertical, &repeater_indices, &empty_steps),
2
);
}
#[test]
fn test_organize_data_1_empty_repeater() {
let auto = i_slint_common::ROW_COL_AUTO;
let cell =
GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. };
let input = std::vec![cell];
let repeater_indices = Slice::from_slice(&[1u32, 0u32]);
let (organized_data, errors) = organize_grid_layout_impl(
Slice::from_slice(&input),
repeater_indices,
Slice::from_slice(&[]),
);
assert_eq!(
organized_data.as_slice(),
&[
0, 1, 0, 1, 0, 0, 0, 0
] );
assert_eq!(errors.len(), 0);
let empty_steps = Slice::from_slice(&[]);
let collected_data = collect_from_organized_data(
&organized_data,
input.len(),
repeater_indices,
empty_steps,
);
assert_eq!(collected_data.as_slice(), &[(0, 1, 0, 1)]);
assert_eq!(
organized_data.max_value(1, Orientation::Horizontal, &repeater_indices, &empty_steps),
1
);
}
#[test]
fn test_organize_data_4_repeaters() {
let auto = i_slint_common::ROW_COL_AUTO;
let mut cell =
GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. };
let mut input = std::vec![cell.clone()];
for _ in 0..8 {
cell.new_row = false;
input.push(cell.clone());
}
let repeater_indices = Slice::from_slice(&[0u32, 0u32, 1u32, 4u32, 6u32, 2u32, 8u32, 0u32]);
let (organized_data, errors) = organize_grid_layout_impl(
Slice::from_slice(&input),
repeater_indices,
Slice::from_slice(&[]),
);
assert_eq!(
organized_data.as_slice(),
&[
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, ]
);
assert_eq!(errors.len(), 0);
let empty_steps = Slice::from_slice(&[]);
let collected_data = collect_from_organized_data(
&organized_data,
input.len(),
repeater_indices,
empty_steps,
);
assert_eq!(
collected_data.as_slice(),
&[
(0, 1, 0, 1),
(1, 1, 0, 1),
(2, 1, 0, 1),
(3, 1, 0, 1),
(4, 1, 0, 1),
(5, 1, 0, 1),
(6, 1, 0, 1),
(7, 1, 0, 1),
(8, 1, 0, 1),
]
);
let empty_steps = Slice::from_slice(&[]);
assert_eq!(
organized_data.max_value(
input.len(),
Orientation::Horizontal,
&repeater_indices,
&empty_steps
),
9
);
}
#[test]
fn test_organize_data_repeated_rows() {
let auto = i_slint_common::ROW_COL_AUTO;
let mut input = Vec::new();
let num_rows: u32 = 3;
let num_columns: u32 = 2;
for _ in 0..num_rows {
let mut cell = GridLayoutInputData {
new_row: true,
col: auto,
row: auto,
colspan: 1.,
rowspan: 1.,
};
input.push(cell.clone());
cell.new_row = false;
input.push(cell.clone());
}
let repeater_indices_arr = [0_u32, num_rows];
let repeater_steps_arr = [num_columns];
let repeater_steps = Slice::from_slice(&repeater_steps_arr);
let repeater_indices = Slice::from_slice(&repeater_indices_arr);
let (organized_data, errors) =
organize_grid_layout_impl(Slice::from_slice(&input), repeater_indices, repeater_steps);
assert_eq!(
organized_data.as_slice(),
&[
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, ]
);
assert_eq!(errors.len(), 0);
let collected_data = collect_from_organized_data(
&organized_data,
input.len(),
repeater_indices,
repeater_steps,
);
assert_eq!(
collected_data.as_slice(),
&[(0, 1, 0, 1), (1, 1, 0, 1), (0, 1, 1, 1), (1, 1, 1, 1), (0, 1, 2, 1), (1, 1, 2, 1),]
);
assert_eq!(
organized_data.max_value(
input.len(),
Orientation::Horizontal,
&repeater_indices,
&repeater_steps
),
2
);
assert_eq!(
organized_data.max_value(
input.len(),
Orientation::Vertical,
&repeater_indices,
&repeater_steps
),
3
);
let mut layout_cache_v = SharedVector::<Coord>::default();
let mut generator = GridLayoutCacheGenerator::new(
repeater_indices.as_slice(),
repeater_steps.as_slice(),
0, 1, 6, &mut layout_cache_v,
);
generator.add(0., 50.);
generator.add(0., 50.);
generator.add(50., 50.);
generator.add(50., 50.);
generator.add(100., 50.);
generator.add(100., 50.);
assert_eq!(
layout_cache_v.as_slice(),
&[
2., 4., 0., 50., 0., 50., 50., 50., 50., 50., 100., 50., 100., 50., ]
);
let layout_cache_v_access = |jump_index: usize,
repeater_index: usize,
stride: usize,
child_offset: usize|
-> Coord {
let base = layout_cache_v[jump_index] as usize;
let data_idx = base + repeater_index * stride + child_offset;
layout_cache_v[data_idx]
};
assert_eq!(layout_cache_v_access(0, 0, 4, 0), 0.);
assert_eq!(layout_cache_v_access(0, 1, 4, 0), 50.);
assert_eq!(layout_cache_v_access(0, 2, 4, 0), 100.);
assert_eq!(layout_cache_v_access(0, 0, 4, 2), 0.);
assert_eq!(layout_cache_v_access(0, 1, 4, 2), 50.);
assert_eq!(layout_cache_v_access(0, 2, 4, 2), 100.);
}
#[test]
fn test_organize_data_repeated_rows_multiple_repeaters() {
let auto = i_slint_common::ROW_COL_AUTO;
let mut input = Vec::new();
let num_rows: u32 = 5;
let mut cell =
GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. };
for _ in 0..3 {
cell.new_row = true;
input.push(cell.clone());
cell.new_row = false;
input.push(cell.clone());
}
for _ in 0..2 {
cell.new_row = true;
input.push(cell.clone());
cell.new_row = false;
input.push(cell.clone());
cell.new_row = false;
input.push(cell.clone());
}
let repeater_indices_arr = [0_u32, 3, 6, 2];
let repeater_steps_arr = [2, 3];
let repeater_steps = Slice::from_slice(&repeater_steps_arr);
let repeater_indices = Slice::from_slice(&repeater_indices_arr);
let (organized_data, errors) =
organize_grid_layout_impl(Slice::from_slice(&input), repeater_indices, repeater_steps);
assert_eq!(
organized_data.as_slice(),
&[
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, ]
);
assert_eq!(errors.len(), 0);
let collected_data = collect_from_organized_data(
&organized_data,
input.len(),
repeater_indices,
repeater_steps,
);
assert_eq!(
collected_data.as_slice(),
&[
(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)
]
);
assert_eq!(
organized_data.max_value(
input.len(),
Orientation::Horizontal,
&repeater_indices,
&repeater_steps
),
3 );
assert_eq!(
organized_data.max_value(
input.len(),
Orientation::Vertical,
&repeater_indices,
&repeater_steps
),
num_rows as u16 );
let mut layout_cache_v = SharedVector::<Coord>::default();
let mut generator = GridLayoutCacheGenerator::new(
repeater_indices.as_slice(),
repeater_steps.as_slice(),
0, 2, 12, &mut layout_cache_v,
);
generator.add(0., 50.);
generator.add(0., 50.);
generator.add(50., 50.);
generator.add(50., 50.);
generator.add(100., 50.);
generator.add(100., 50.);
generator.add(150., 50.);
generator.add(150., 50.);
generator.add(150., 50.);
generator.add(200., 50.);
generator.add(200., 50.);
generator.add(200., 50.);
assert_eq!(
layout_cache_v.as_slice(),
&[
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., ]
);
let layout_cache_v_access = |jump_index: usize,
repeater_index: usize,
stride: usize,
child_offset: usize|
-> Coord {
let base = layout_cache_v[jump_index] as usize;
let data_idx = base + repeater_index * stride + child_offset;
layout_cache_v[data_idx]
};
assert_eq!(layout_cache_v_access(0, 0, 4, 0), 0.);
assert_eq!(layout_cache_v_access(0, 1, 4, 0), 50.);
assert_eq!(layout_cache_v_access(0, 2, 4, 0), 100.);
assert_eq!(layout_cache_v_access(0, 0, 4, 2), 0.);
assert_eq!(layout_cache_v_access(0, 1, 4, 2), 50.);
assert_eq!(layout_cache_v_access(0, 2, 4, 2), 100.);
assert_eq!(layout_cache_v_access(2, 0, 6, 0), 150.);
assert_eq!(layout_cache_v_access(2, 1, 6, 0), 200.);
assert_eq!(layout_cache_v_access(2, 0, 6, 4), 150.);
assert_eq!(layout_cache_v_access(2, 1, 6, 4), 200.);
}
#[test]
fn test_layout_cache_generator_2_fixed_cells() {
let mut result = SharedVector::<Coord>::default();
result.resize(2 * 2, 0 as _);
let mut generator = LayoutCacheGenerator::new(&[], &mut result);
generator.add(0., 50.); generator.add(80., 50.); assert_eq!(result.as_slice(), &[0., 50., 80., 50.]);
}
#[test]
fn test_layout_cache_generator_1_fixed_cell_1_repeater() {
let mut result = SharedVector::<Coord>::default();
let repeater_indices = &[1, 3];
result.resize(4 * 2 + repeater_indices.len(), 0 as _);
let mut generator = LayoutCacheGenerator::new(repeater_indices, &mut result);
generator.add(0., 50.); generator.add(80., 50.); generator.add(160., 50.);
generator.add(240., 50.);
assert_eq!(
result.as_slice(),
&[
0., 50., 4., 5., 80., 50., 160., 50., 240., 50. ]
);
}
#[test]
fn test_layout_cache_generator_4_repeaters() {
let mut result = SharedVector::<Coord>::default();
let repeater_indices = &[1, 0, 1, 4, 6, 2, 8, 0];
result.resize(8 * 2 + repeater_indices.len(), 0 as _);
let mut generator = LayoutCacheGenerator::new(repeater_indices, &mut result);
generator.add(0., 50.); generator.add(80., 10.); generator.add(160., 10.);
generator.add(240., 10.);
generator.add(320., 10.); generator.add(400., 80.); generator.add(500., 20.); generator.add(600., 20.); assert_eq!(
result.as_slice(),
&[
0., 50., 12., 13., 12., 13., 400., 80., 20., 21., 0., 0., 80., 10., 160., 10., 240., 10., 320., 10., 500., 20., 600., 20. ]
);
}
}