diff --git a/api/cpp/include/slint.h b/api/cpp/include/slint.h index 9e9277b3163..142edcf1690 100644 --- a/api/cpp/include/slint.h +++ b/api/cpp/include/slint.h @@ -123,18 +123,19 @@ inline SharedVector solve_box_layout(const cbindgen_private::BoxLayoutDat return result; } -inline SharedVector solve_grid_layout(const cbindgen_private::GridLayoutData &data) +inline SharedVector solve_grid_layout(const cbindgen_private::GridLayoutData &data, + cbindgen_private::Orientation orientation) { SharedVector result; - cbindgen_private::slint_solve_grid_layout(&data, &result); + cbindgen_private::slint_solve_grid_layout(&data, orientation, &result); return result; } inline cbindgen_private::LayoutInfo grid_layout_info(cbindgen_private::Slice cells, float spacing, - const cbindgen_private::Padding &padding) + const cbindgen_private::Padding &padding, cbindgen_private::Orientation o) { - return cbindgen_private::slint_grid_layout_info(cells, spacing, &padding); + return cbindgen_private::slint_grid_layout_info(cells, spacing, &padding, o); } inline cbindgen_private::LayoutInfo diff --git a/internal/compiler/layout.rs b/internal/compiler/layout.rs index 7bc481e0626..44fb4f0e913 100644 --- a/internal/compiler/layout.rs +++ b/internal/compiler/layout.rs @@ -278,18 +278,19 @@ impl LayoutConstraints { /// An element in a GridLayout #[derive(Debug, Clone)] pub struct GridLayoutElement { - pub col: u16, - pub row: u16, - pub colspan: u16, - pub rowspan: u16, + pub new_row: bool, + pub col_expr: Option, + pub row_expr: Option, + pub colspan_expr: Option, + pub rowspan_expr: Option, pub item: LayoutItem, } impl GridLayoutElement { - pub fn col_or_row_and_span(&self, orientation: Orientation) -> (u16, u16) { + pub fn span(&self, orientation: Orientation) -> &Option { match orientation { - Orientation::Horizontal => (self.col, self.colspan), - Orientation::Vertical => (self.row, self.rowspan), + Orientation::Horizontal => &self.colspan_expr, + Orientation::Vertical => &self.rowspan_expr, } } } diff --git a/internal/compiler/llr/expression.rs b/internal/compiler/llr/expression.rs index 627c2da1e71..66755ce6e98 100644 --- a/internal/compiler/llr/expression.rs +++ b/internal/compiler/llr/expression.rs @@ -730,7 +730,10 @@ impl TypeResolutionContext for EvaluationContext<'_, T> { &self.compilation_unit.sub_components[sub_component.sub_components[*i].ty]; } - sub_component.items[*item_index].ty.lookup_property(prop_name).unwrap() + sub_component.items[*item_index] + .ty + .lookup_property(prop_name) + .unwrap_or_else(|| panic!("property not found: {}", prop_name)) } PropertyReference::InParent { level, parent_reference } => { let mut ctx = self; diff --git a/internal/compiler/llr/lower_expression.rs b/internal/compiler/llr/lower_expression.rs index 0c058ffe930..7f50e09aace 100644 --- a/internal/compiler/llr/lower_expression.rs +++ b/internal/compiler/llr/lower_expression.rs @@ -597,9 +597,13 @@ fn compute_layout_info( crate::layout::Layout::GridLayout(layout) => { let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx); let cells = grid_layout_cell_data(layout, o, ctx); + let orientation_literal = llr_Expression::EnumerationValue(EnumerationValue { + value: o as _, + enumeration: crate::typeregister::BUILTIN.with(|b| b.enums.Orientation.clone()), + }); llr_Expression::ExtraBuiltinFunctionCall { function: "grid_layout_info".into(), - arguments: vec![cells, spacing, padding], + arguments: vec![cells, spacing, padding, orientation_literal], return_ty: crate::typeregister::layout_info_type().into(), } } @@ -643,6 +647,10 @@ fn solve_layout( let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx); let cells = grid_layout_cell_data(layout, o, ctx); let size = layout_geometry_size(&layout.geometry.rect, o, ctx); + let orientation_expr = llr_Expression::EnumerationValue(EnumerationValue { + value: o as _, + enumeration: crate::typeregister::BUILTIN.with(|b| b.enums.Orientation.clone()), + }); if let (Some(button_roles), Orientation::Horizontal) = (&layout.dialog_button_roles, o) { let cells_ty = cells.ty(ctx); @@ -669,37 +677,43 @@ fn solve_layout( }, llr_Expression::ExtraBuiltinFunctionCall { function: "solve_grid_layout".into(), - arguments: vec![make_struct( - "GridLayoutData", - [ - ("size", Type::Float32, size), - ("spacing", Type::Float32, spacing), - ("padding", padding.ty(ctx), padding), - ( - "cells", - cells_ty.clone(), - llr_Expression::ReadLocalVariable { - name: "cells".into(), - ty: cells_ty, - }, - ), - ], - )], + arguments: vec![ + make_struct( + "GridLayoutData", + [ + ("size", Type::Float32, size), + ("spacing", Type::Float32, spacing), + ("padding", padding.ty(ctx), padding), + ( + "cells", + cells_ty.clone(), + llr_Expression::ReadLocalVariable { + name: "cells".into(), + ty: cells_ty, + }, + ), + ], + ), + orientation_expr, + ], return_ty: Type::LayoutCache, }, ]) } else { llr_Expression::ExtraBuiltinFunctionCall { function: "solve_grid_layout".into(), - arguments: vec![make_struct( - "GridLayoutData", - [ - ("size", Type::Float32, size), - ("spacing", Type::Float32, spacing), - ("padding", padding.ty(ctx), padding), - ("cells", cells.ty(ctx), cells), - ], - )], + arguments: vec![ + make_struct( + "GridLayoutData", + [ + ("size", Type::Float32, size), + ("spacing", Type::Float32, spacing), + ("padding", padding.ty(ctx), padding), + ("cells", cells.ty(ctx), cells), + ], + ), + orientation_expr, + ], return_ty: Type::LayoutCache, } } @@ -846,16 +860,36 @@ fn grid_layout_cell_data( .elems .iter() .map(|c| { - let (col_or_row, span) = c.col_or_row_and_span(orientation); let layout_info = get_layout_info(&c.item.element, ctx, &c.item.constraints, orientation); + let mut lower_expr_or_default = + |expr: &Option, default: u16| { + expr.as_ref().map_or_else( + || llr_Expression::NumberLiteral(default.into()), + |e| lower_expression(e, ctx), + ) + }; + // MAX means "auto", see to_layout_data() + let row_expr = lower_expr_or_default(&c.row_expr, u16::MAX); + let col_expr = lower_expr_or_default(&c.col_expr, u16::MAX); + let span_expr = lower_expr_or_default(&c.span(orientation), 1); + make_struct( "GridLayoutCellData", [ ("constraint", crate::typeregister::layout_info_type().into(), layout_info), - ("col_or_row", Type::Int32, llr_Expression::NumberLiteral(col_or_row as _)), - ("span", Type::Int32, llr_Expression::NumberLiteral(span as _)), + ("new_row", Type::Bool, llr_Expression::BoolLiteral(c.new_row)), + ( + "col_or_row", + Type::Int32, + match orientation { + Orientation::Horizontal => col_expr, + Orientation::Vertical => row_expr.clone(), + }, + ), + ("row", Type::Int32, row_expr), + ("span", Type::Int32, span_expr), ], ) }) @@ -867,6 +901,7 @@ fn grid_layout_cell_data( pub(super) fn grid_layout_cell_data_ty() -> Type { Type::Struct(Rc::new(Struct { fields: IntoIterator::into_iter([ + (SmolStr::new_static("new_row"), Type::Bool), (SmolStr::new_static("col_or_row"), Type::Int32), (SmolStr::new_static("span"), Type::Int32), (SmolStr::new_static("constraint"), crate::typeregister::layout_info_type().into()), diff --git a/internal/compiler/passes/lower_layout.rs b/internal/compiler/passes/lower_layout.rs index b1351140151..d63f43c9658 100644 --- a/internal/compiler/passes/lower_layout.rs +++ b/internal/compiler/passes/lower_layout.rs @@ -54,9 +54,9 @@ pub fn lower_layouts( elem, &type_loader.global_type_registry.borrow(), style_metrics, + parent_layout_type, diag, ); - check_no_layout_properties(elem, &layout_type, &parent_layout_type, diag); layout_type }, ); @@ -93,14 +93,22 @@ fn lower_element_layout( elem: &ElementRc, type_register: &TypeRegister, style_metrics: &Rc, + parent_layout_type: &Option, diag: &mut BuildDiagnostics, ) -> Option { - let base_type = if let ElementType::Builtin(base_type) = &elem.borrow().base_type { - base_type.clone() + let layout_type = if let ElementType::Builtin(base_type) = &elem.borrow().base_type { + Some(base_type.name.clone()) } else { - return None; + None }; - match base_type.name.as_str() { + + check_no_layout_properties(elem, &layout_type, &parent_layout_type, diag); + + if layout_type.is_none() { + return None; + } + + match layout_type.as_ref().unwrap().as_str() { "Row" => { // We shouldn't lower layout if we have a Row in there. Unless the Row is the root of a repeated item, // in which case another error has been reported @@ -121,7 +129,7 @@ fn lower_element_layout( "Dialog" => { lower_dialog_layout(elem, style_metrics, diag); // return now, the Dialog stays in the tree as a Dialog - return Some(base_type.name.clone()); + return layout_type; } _ => return None, }; @@ -140,7 +148,7 @@ fn lower_element_layout( } } - Some(base_type.name.clone()) + layout_type } fn lower_grid_layout( @@ -176,11 +184,9 @@ fn lower_grid_layout( layout_info_type().into(), ); - let mut row = 0; - let mut col = 0; - let layout_children = std::mem::take(&mut grid_layout_element.borrow_mut().children); let mut collected_children = Vec::new(); + let mut new_row = false; // true until the first child of a Row, or the first item after an empty Row for layout_child in layout_children { let is_row = if let ElementType::Builtin(be) = &layout_child.borrow().base_type { be.name == "Row" @@ -188,26 +194,14 @@ fn lower_grid_layout( false }; if is_row { - if col > 0 { - row += 1; - col = 0; - } + new_row = true; let row_children = std::mem::take(&mut layout_child.borrow_mut().children); for x in row_children { - grid.add_element( - &x, - (&mut row, &mut col), - &layout_cache_prop_h, - &layout_cache_prop_v, - diag, - ); - col += 1; + grid.add_element(&x, new_row, &layout_cache_prop_h, &layout_cache_prop_v, diag); collected_children.push(x); + new_row = false; } - if col > 0 { - row += 1; - col = 0; - } + new_row = true; // the end of a Row means the next item is the first of a new row if layout_child.borrow().has_popup_child { // We need to keep that element otherwise the popup will malfunction layout_child.borrow_mut().base_type = type_register.empty_type(); @@ -218,13 +212,13 @@ fn lower_grid_layout( } else { grid.add_element( &layout_child, - (&mut row, &mut col), + new_row, &layout_cache_prop_h, &layout_cache_prop_v, diag, ); - col += 1; collected_children.push(layout_child); + new_row = false; } } grid_layout_element.borrow_mut().children = collected_children; @@ -275,41 +269,33 @@ impl GridLayout { fn add_element( &mut self, item_element: &ElementRc, - (row, col): (&mut u16, &mut u16), + new_row: bool, layout_cache_prop_h: &NamedReference, layout_cache_prop_v: &NamedReference, diag: &mut BuildDiagnostics, ) { - let mut get_const_value = |name: &str| { - item_element - .borrow_mut() - .bindings - .get(name) - .and_then(|e| eval_const_expr(&e.borrow().expression, name, &*e.borrow(), diag)) + let mut get_expr = |name: &str| { + item_element.borrow_mut().bindings.get(name).map(|e| { + let expr = &e.borrow().expression; + check_number_literal_is_positive_integer(expr, name, &*e.borrow(), diag); + expr.clone() + }) }; - let colspan = get_const_value("colspan").unwrap_or(1); - let rowspan = get_const_value("rowspan").unwrap_or(1); - if let Some(r) = get_const_value("row") { - *row = r; - *col = 0; - } - if let Some(c) = get_const_value("col") { - *col = c; - } - let result = self.add_element_with_coord( + let row_expr = get_expr("row"); + let col_expr = get_expr("col"); + let rowspan_expr = get_expr("rowspan"); + let colspan_expr = get_expr("colspan"); + + self.add_element_with_coord_as_expr( item_element, - (*row, *col), - (rowspan, colspan), + new_row, + (&row_expr, &col_expr), + (&rowspan_expr, &colspan_expr), layout_cache_prop_h, layout_cache_prop_v, diag, ); - if let Some(layout_item) = result { - let e = &layout_item.elem; - insert_fake_property(e, "row", Expression::NumberLiteral(*row as f64, Unit::None)); - insert_fake_property(e, "col", Expression::NumberLiteral(*col as f64, Unit::None)); - } } fn add_element_with_coord( @@ -320,7 +306,34 @@ impl GridLayout { layout_cache_prop_h: &NamedReference, layout_cache_prop_v: &NamedReference, diag: &mut BuildDiagnostics, - ) -> Option { + ) { + self.add_element_with_coord_as_expr( + item_element, + false, // new_row + ( + &Some(Expression::NumberLiteral(row as _, Unit::None)), + &Some(Expression::NumberLiteral(col as _, Unit::None)), + ), + ( + &Some(Expression::NumberLiteral(rowspan as _, Unit::None)), + &Some(Expression::NumberLiteral(colspan as _, Unit::None)), + ), + layout_cache_prop_h, + layout_cache_prop_v, + diag, + ) + } + + fn add_element_with_coord_as_expr( + &mut self, + item_element: &ElementRc, + new_row: bool, + (row_expr, col_expr): (&Option, &Option), + (rowspan_expr, colspan_expr): (&Option, &Option), + layout_cache_prop_h: &NamedReference, + layout_cache_prop_v: &NamedReference, + diag: &mut BuildDiagnostics, + ) { let index = self.elems.len(); let result = create_layout_item(item_element, diag); if let Some(ref layout_item) = result { @@ -330,28 +343,35 @@ impl GridLayout { .to_string(), &*item_element.borrow(), ); - return None; + return; } let e = &layout_item.elem; - set_prop_from_cache(e, "x", layout_cache_prop_h, index * 2, &None, diag); + set_prop_from_cache(e, "x", layout_cache_prop_h, index * 3, &None, diag); if !layout_item.item.constraints.fixed_width { - set_prop_from_cache(e, "width", layout_cache_prop_h, index * 2 + 1, &None, diag); + set_prop_from_cache(e, "width", layout_cache_prop_h, index * 3 + 1, &None, diag); + } + if col_expr.is_none() { + set_prop_from_cache(e, "col", layout_cache_prop_h, index * 3 + 2, &None, diag); } - set_prop_from_cache(e, "y", layout_cache_prop_v, index * 2, &None, diag); + + set_prop_from_cache(e, "y", layout_cache_prop_v, index * 3, &None, diag); if !layout_item.item.constraints.fixed_height { - set_prop_from_cache(e, "height", layout_cache_prop_v, index * 2 + 1, &None, diag); + set_prop_from_cache(e, "height", layout_cache_prop_v, index * 3 + 1, &None, diag); + } + if row_expr.is_none() { + set_prop_from_cache(e, "row", layout_cache_prop_v, index * 3 + 2, &None, diag); } self.elems.push(GridLayoutElement { - col, - row, - colspan, - rowspan, + new_row, + col_expr: col_expr.clone(), + row_expr: row_expr.clone(), + colspan_expr: colspan_expr.clone(), + rowspan_expr: rowspan_expr.clone(), item: layout_item.item.clone(), }); } - result } } @@ -785,15 +805,6 @@ fn create_layout_item( }) } -fn insert_fake_property(elem: &ElementRc, prop: &str, expr: Expression) { - let mut elem_mut = elem.borrow_mut(); - let span = elem_mut.to_source_location(); - if let std::collections::btree_map::Entry::Vacant(e) = elem_mut.bindings.entry(prop.into()) { - let binding = BindingExpression::new_with_span(expr, span); - e.insert(binding.into()); - } -} - fn set_prop_from_cache( elem: &ElementRc, prop: &str, @@ -822,30 +833,37 @@ fn set_prop_from_cache( } } -fn eval_const_expr( +// If it's a number literal, it must be a positive integer +// But also allow any other kind of expression +fn check_number_literal_is_positive_integer( expression: &Expression, name: &str, span: &dyn crate::diagnostics::Spanned, diag: &mut BuildDiagnostics, -) -> Option { +) { match super::ignore_debug_hooks(expression) { Expression::NumberLiteral(v, Unit::None) => { - if *v < 0. || *v > u16::MAX as f64 || !v.trunc().approx_eq(v) { + if *v > u16::MAX as f64 || !v.trunc().approx_eq(v) { diag.push_error(format!("'{name}' must be a positive integer"), span); - None - } else { - Some(*v as u16) } } - Expression::Cast { from, .. } => eval_const_expr(from, name, span, diag), - _ => { - diag.push_error(format!("'{name}' must be an integer literal"), span); - None + Expression::UnaryOp { op: '-', sub } => { + if let Expression::NumberLiteral(_, Unit::None) = super::ignore_debug_hooks(sub) { + diag.push_error(format!("'{name}' must be a positive integer"), span); + } + } + Expression::Cast { from, .. } => { + check_number_literal_is_positive_integer(from, name, span, diag) } + _ => {} } } -/// Checks that there is grid-layout specific properties left +fn recognized_layout_types() -> &'static [&'static str] { + &["Row", "GridLayout", "HorizontalLayout", "VerticalLayout", "Dialog"] +} + +/// Checks that there are no grid-layout specific properties used wrongly fn check_no_layout_properties( item: &ElementRc, layout_type: &Option, @@ -867,7 +885,8 @@ fn check_no_layout_properties( &*expr.borrow(), ); } - if layout_type.is_none() + if (layout_type.is_none() + || !recognized_layout_types().contains(&layout_type.as_ref().unwrap().as_str())) && matches!( prop.as_ref(), "padding" | "padding-left" | "padding-right" | "padding-top" | "padding-bottom" diff --git a/internal/compiler/tests/syntax/basic/layout2.slint b/internal/compiler/tests/syntax/basic/layout2.slint index 63bcc5179b6..dda6bd49dc5 100644 --- a/internal/compiler/tests/syntax/basic/layout2.slint +++ b/internal/compiler/tests/syntax/basic/layout2.slint @@ -6,20 +6,23 @@ export X := Rectangle { lay := GridLayout { property foo: "hello"; + property last_row: 4; + property negative: -5; Row { Text { text: lay.foo + parent.width; // ^error{Element 'Row' does not have a property 'width'} colspan: 1 + 1; -// ^error{'colspan' must be an integer literal} rowspan: 2; } Text { row: 3; col: -2; -// ^error{'col' must be an integer literal} +// ^error{'col' must be a positive integer} rowspan: 2.2; // ^error{'rowspan' must be a positive integer} + colspan: -1; +// ^error{'colspan' must be a positive integer} y: 0; // ^error{The property 'y' cannot be set for elements placed in this layout, because the layout is already setting it} @@ -31,6 +34,10 @@ export X := Rectangle { // ^error{The property must be known at compile time and cannot be changed at runtime} } } + Text { + row: lay.last_row; + col: -lay.negative; // check that a unary minus isn't necessary an error + } } Row { Text { diff --git a/internal/core/layout.rs b/internal/core/layout.rs index b078bf0b590..3d5425b6ecc 100644 --- a/internal/core/layout.rs +++ b/internal/core/layout.rs @@ -305,23 +305,28 @@ mod grid_internal { assert_eq!(my_items[2].size, 100.); } - /// Create a vector of LayoutData for an array of GridLayoutCellData + /// Create a vector of LayoutData (e.g. one per row if Vertical) for an array of GridLayoutCellData (one per cell) + /// Used by both solve_grid_layout() and grid_layout_info() (each with their own copy of the GridLayoutCellData slice) pub fn to_layout_data( data: &[GridLayoutCellData], spacing: Coord, size: Option, - ) -> Vec { + orientation: Orientation, + ) -> (Vec, Vec) { + let mut cells: Vec = data.to_vec(); + determine_row_col_numbers(&mut cells, orientation); + let mut num = 0usize; - for cell in data { + for cell in cells.iter() { num = num.max(cell.col_or_row as usize + cell.span.max(1) as usize); } if num < 1 { - return Default::default(); + return (Default::default(), cells); } let mut layout_data = alloc::vec![grid_internal::LayoutData { stretch: 1., ..Default::default() }; num]; let mut has_spans = false; - for cell in data { + for cell in cells.iter() { let constraint = &cell.constraint; let mut max = constraint.max; if let Some(size) = size { @@ -347,7 +352,7 @@ mod grid_internal { } if has_spans { // Adjust minimum sizes - for cell in data.iter().filter(|cell| cell.span > 1) { + for cell in cells.iter().filter(|cell| cell.span > 1) { let span_data = &mut layout_data [(cell.col_or_row as usize)..(cell.col_or_row + cell.span) as usize]; let mut min = cell.constraint.min; @@ -362,7 +367,7 @@ mod grid_internal { } } // Adjust maximum sizes - for cell in data.iter().filter(|cell| cell.span > 1) { + for cell in cells.iter().filter(|cell| cell.span > 1) { let span_data = &mut layout_data [(cell.col_or_row as usize)..(cell.col_or_row + cell.span) as usize]; let mut max = cell.constraint.max; @@ -377,7 +382,7 @@ mod grid_internal { } } // Adjust preferred sizes - for cell in data.iter().filter(|cell| cell.span > 1) { + for cell in cells.iter().filter(|cell| cell.span > 1) { let span_data = &mut layout_data [(cell.col_or_row as usize)..(cell.col_or_row + cell.span) as usize]; grid_internal::layout_items(span_data, 0 as _, cell.constraint.preferred, spacing); @@ -386,7 +391,7 @@ mod grid_internal { } } // Adjust stretches - for cell in data.iter().filter(|cell| cell.span > 1) { + for cell in cells.iter().filter(|cell| cell.span > 1) { let span_data = &mut layout_data [(cell.col_or_row as usize)..(cell.col_or_row + cell.span) as usize]; let total_stretch: f32 = span_data.iter().map(|c| c.stretch).sum(); @@ -397,7 +402,7 @@ mod grid_internal { } } } - layout_data + (layout_data, cells) } } @@ -429,20 +434,67 @@ pub struct GridLayoutData<'a> { pub cells: Slice<'a, GridLayoutCellData>, } +/// The horizontal or vertical data for a cell of a GridLayout #[repr(C)] -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] pub struct GridLayoutCellData { - /// col, or row. + /// whether this cell is the first one in a Row element + pub new_row: bool, + /// col or row (u16::MAX means auto). pub col_or_row: u16, + /// row number, needed to detect row changes (and set col to 0). + /// This duplicates col_or_row in case of vertical data, but col_or_row allows more common code. + pub row: u16, /// colspan or rowspan pub span: u16, pub constraint: LayoutInfo, } -/// return, an array which is of size `data.cells.len() * 2` which for each cell we give the pos, size -pub fn solve_grid_layout(data: &GridLayoutData) -> SharedVector { - let mut layout_data = - grid_internal::to_layout_data(data.cells.as_slice(), data.spacing, Some(data.size)); +// Implement "auto" behavior for row/col numbers (unless specified in the slint file). +// Shared code between to_layout_data() (when compiled) and eval_layout.rs (when interpreted) +pub fn determine_row_col_numbers(data: &mut [GridLayoutCellData], orientation: Orientation) { + let mut row = 0; + let mut col = 0; + let mut first = true; + let auto = u16::MAX; + for cell in data.iter_mut() { + if cell.new_row && !first { + row += 1; + col = 0; + } + first = false; + if cell.row == auto { + cell.row = row; + } else if row != cell.row { + row = cell.row; + col = 0; + } + if orientation == Orientation::Horizontal { + if cell.col_or_row == auto { + cell.col_or_row = col; + } else { + col = cell.col_or_row; + } + } else { + if cell.col_or_row == auto { + cell.col_or_row = row; + } else { + row = cell.col_or_row; + } + } + col += 1; + } +} + +/// return, an array which is of size `data.cells.len() * 3` which for each cell stores: +/// pos (x or y), size (width or height), row or column number +pub fn solve_grid_layout(data: &GridLayoutData, orientation: Orientation) -> SharedVector { + let (mut layout_data, cells) = grid_internal::to_layout_data( + data.cells.as_slice(), + data.spacing, + Some(data.size), + orientation, + ); if layout_data.is_empty() { return Default::default(); @@ -455,8 +507,8 @@ pub fn solve_grid_layout(data: &GridLayoutData) -> SharedVector { data.spacing, ); - let mut result = SharedVector::with_capacity(4 * data.cells.len()); - for cell in data.cells.iter() { + let mut result = SharedVector::with_capacity(4 * cells.len()); + for cell in cells.iter() { let cdata = &layout_data[cell.col_or_row as usize]; result.push(cdata.pos); result.push(if cell.span > 0 { @@ -466,6 +518,7 @@ pub fn solve_grid_layout(data: &GridLayoutData) -> SharedVector { } else { 0 as Coord }); + result.push(cell.col_or_row as _); } result } @@ -474,8 +527,10 @@ pub fn grid_layout_info( cells: Slice, spacing: Coord, padding: &Padding, + orientation: Orientation, ) -> LayoutInfo { - let layout_data = grid_internal::to_layout_data(cells.as_slice(), spacing, None); + let (layout_data, _) = + grid_internal::to_layout_data(cells.as_slice(), spacing, None, orientation); if layout_data.is_empty() { return Default::default(); } @@ -742,9 +797,10 @@ pub(crate) mod ffi { #[unsafe(no_mangle)] pub extern "C" fn slint_solve_grid_layout( data: &GridLayoutData, + orientation: Orientation, result: &mut SharedVector, ) { - *result = super::solve_grid_layout(data) + *result = super::solve_grid_layout(data, orientation) } #[unsafe(no_mangle)] @@ -752,8 +808,9 @@ pub(crate) mod ffi { cells: Slice, spacing: Coord, padding: &Padding, + orientation: Orientation, ) -> LayoutInfo { - super::grid_layout_info(cells, spacing, padding) + super::grid_layout_info(cells, spacing, padding, orientation) } #[unsafe(no_mangle)] diff --git a/internal/core/slice.rs b/internal/core/slice.rs index cff5aa39dff..0d16eeb6a2a 100644 --- a/internal/core/slice.rs +++ b/internal/core/slice.rs @@ -60,7 +60,7 @@ impl Clone for Slice<'_, T> { impl<'a, T> Slice<'a, T> { /// Return a slice pub fn as_slice(self) -> &'a [T] { - // Safety: it ptr is supposed to be a valid slice of given length + // Safety: ptr is supposed to be a valid slice of given length unsafe { core::slice::from_raw_parts(self.ptr.as_ptr(), self.len) } } diff --git a/internal/interpreter/eval_layout.rs b/internal/interpreter/eval_layout.rs index 1beb430115c..e4c3401f498 100644 --- a/internal/interpreter/eval_layout.rs +++ b/internal/interpreter/eval_layout.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use crate::dynamic_item_tree::InstanceRef; -use crate::eval::{self, EvalLocalContext}; +use crate::eval::{self, eval_expression, EvalLocalContext}; use crate::Value; use i_slint_compiler::expression_tree::Expression; use i_slint_compiler::langtype::Type; @@ -42,10 +42,17 @@ pub(crate) fn compute_layout_info( }; match lay { Layout::GridLayout(grid_layout) => { - let cells = grid_layout_data(grid_layout, orientation, component, &expr_eval); + let cells = + grid_layout_data(grid_layout, orientation, component, &expr_eval, local_context); let (padding, spacing) = padding_and_spacing(&grid_layout.geometry, orientation, &expr_eval); - core_layout::grid_layout_info(Slice::from(cells.as_slice()), spacing, &padding).into() + core_layout::grid_layout_info( + Slice::from(cells.as_slice()), + spacing, + &padding, + to_runtime(orientation), + ) + .into() } Layout::BoxLayout(box_layout) => { let (cells, alignment) = @@ -79,7 +86,8 @@ pub(crate) fn solve_layout( match lay { Layout::GridLayout(grid_layout) => { - let mut cells = grid_layout_data(grid_layout, orientation, component, &expr_eval); + let mut cells = + grid_layout_data(grid_layout, orientation, component, &expr_eval, local_context); if let (Some(buttons_roles), Orientation::Horizontal) = (&grid_layout.dialog_button_roles, orientation) { @@ -94,12 +102,15 @@ pub(crate) fn solve_layout( padding_and_spacing(&grid_layout.geometry, orientation, &expr_eval); let size_ref = grid_layout.geometry.rect.size_reference(orientation); - core_layout::solve_grid_layout(&core_layout::GridLayoutData { - size: size_ref.map(expr_eval).unwrap_or(0.), - spacing, - padding, - cells: Slice::from(cells.as_slice()), - }) + core_layout::solve_grid_layout( + &core_layout::GridLayoutData { + size: size_ref.map(expr_eval).unwrap_or(0.), + spacing, + padding, + cells: Slice::from(cells.as_slice()), + }, + to_runtime(orientation), + ) .into() } Layout::BoxLayout(box_layout) => { @@ -150,8 +161,9 @@ fn grid_layout_data( orientation: Orientation, component: InstanceRef, expr_eval: &impl Fn(&NamedReference) -> f32, + local_context: &mut EvalLocalContext, ) -> Vec { - let cells = grid_layout + let mut cells = grid_layout .elems .iter() .map(|cell| { @@ -167,10 +179,36 @@ fn grid_layout_data( orientation, &expr_eval, ); - let (col_or_row, span) = cell.col_or_row_and_span(orientation); - core_layout::GridLayoutCellData { col_or_row, span, constraint: layout_info } + let span = cell.span(orientation); + let mut eval_or_default = |expr: &Option, default: u16| match expr { + None => default, + Some(e) => { + let value = eval_expression(e, local_context); + match value { + Value::Number(n) if n >= 0.0 && n <= u16::MAX as f64 => n as u16, + _ => panic!( + "Expected a positive integer, but got {:?} while evaluating {:?}", + value, e + ), + } + } + }; + let row = eval_or_default(&cell.row_expr, u16::MAX); + let col = eval_or_default(&cell.col_expr, u16::MAX); + let span = eval_or_default(&span, 1); + core_layout::GridLayoutCellData { + new_row: cell.new_row, + col_or_row: match orientation { + Orientation::Horizontal => col, + Orientation::Vertical => row, + }, + row, + span, + constraint: layout_info, + } }) .collect::>(); + core_layout::determine_row_col_numbers(&mut cells, to_runtime(orientation)); cells } diff --git a/tests/cases/layout/grid_variable_row_col.slint b/tests/cases/layout/grid_variable_row_col.slint new file mode 100644 index 00000000000..6155ecdec4b --- /dev/null +++ b/tests/cases/layout/grid_variable_row_col.slint @@ -0,0 +1,84 @@ +// Copyright © Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +export component TestCase inherits Window { + property blue_row: 1; + property blue_colspan: 2; + property green_col: 1; + property red_row: 0; + property red_col: 0; + width: 400phx; + height: 400phx; + + GridLayout { + spacing: 10phx; + padding: 50phx; + + rb := Rectangle { + background: blue; + row: blue_row; + col: 0; + colspan: blue_colspan; + height: 40phx; + } + rg := Rectangle { + background: green; + row: 0; + col: green_col; + colspan: 1; + width: 40phx; + height: 40phx; + } + rr := Rectangle { + background: red; + row: red_row; + col: red_col; + colspan: 1; + width: 40phx; + height: 40phx; + } + } + out property initial_grid_ok: { + // red is at (0, 0), green is at (row=0, col=1), blue is at (row=1, col=0) with colspan 2 + rr.x == 50phx && rr.y == 50phx && rr.colspan == 1 && + rg.x == 100phx && rg.y == 50phx && rg.colspan == 1 && + rb.x == 50phx && rb.y == 100phx && rb.width == 90phx && rb.colspan == 2 + } + + public function change_grid() { + green_col = 0; + red_row = 1; + blue_row = 2; + blue_colspan = 1; + } + + out property final_grid_ok: { + // now they are all at col 0, and in the order green, red, blue + rr.x == 50phx && rr.y == 100phx && rr.width == 40phx && rr.colspan == 1 && + rg.x == 50phx && rg.y == 50phx && rg.width == 40phx && rg.colspan == 1 && + rb.x == 50phx && rb.y == 150phx && rb.width == 40phx && rb.colspan == 1 + } + + init => { + if !initial_grid_ok { root.error = "!initial_grid_ok"; } + change_grid() + } + out property error; + out property test: error == "" && final_grid_ok; +} + +/* + +```cpp +auto handle = TestCase::create(); +const TestCase &instance = *handle; +assert(instance.get_test()); +``` + + +```rust +let instance = TestCase::new().unwrap(); +assert!(instance.get_test()); +``` + +*/