From 21a80f4562805b56dcf1cd4e76173684dd4937be Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Mon, 3 May 2021 16:09:27 +0200 Subject: [PATCH] WIP: Layout refactoring Instead of using a solve_layout function in the component, use property to hold a layout cache. This commit only implement the GridLayout and only the interpreter part --- Cargo.toml | 68 +-- sixtyfps_compiler/expression_tree.rs | 34 +- sixtyfps_compiler/langtype.rs | 14 +- sixtyfps_compiler/layout.rs | 185 +++---- sixtyfps_compiler/object_tree.rs | 15 +- sixtyfps_compiler/passes/inlining.rs | 1 + sixtyfps_compiler/passes/lower_layout.rs | 414 ++++++++-------- sixtyfps_compiler/passes/lower_popups.rs | 2 - sixtyfps_compiler/passes/move_declarations.rs | 2 +- .../passes/repeater_component.rs | 4 +- sixtyfps_runtime/corelib/layout.rs | 43 +- sixtyfps_runtime/interpreter/api.rs | 10 + .../interpreter/dynamic_component.rs | 451 +----------------- sixtyfps_runtime/interpreter/eval.rs | 15 +- sixtyfps_runtime/interpreter/eval_layout.rs | 175 +++++++ .../interpreter/global_component.rs | 1 + sixtyfps_runtime/interpreter/lib.rs | 1 + tests/cases/layout/grid.60 | 2 +- 18 files changed, 584 insertions(+), 853 deletions(-) create mode 100644 sixtyfps_runtime/interpreter/eval_layout.rs diff --git a/Cargo.toml b/Cargo.toml index f83431089..1d25d305c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,34 +9,34 @@ members = [ 'sixtyfps_runtime/rendering_backends/testing', 'sixtyfps_compiler', 'sixtyfps_compiler/parser_test_macro', - 'api/sixtyfps-rs', - 'api/sixtyfps-rs/sixtyfps-macros', - 'api/sixtyfps-rs/sixtyfps-build', - 'api/sixtyfps-cpp', - 'api/sixtyfps-node/native', + # 'api/sixtyfps-rs', + # 'api/sixtyfps-rs/sixtyfps-macros', + # 'api/sixtyfps-rs/sixtyfps-build', + # 'api/sixtyfps-cpp', + # 'api/sixtyfps-node/native', 'api/sixtyfps-wasm-interpreter', - 'tools/compiler', + # 'tools/compiler', 'tools/fmt', 'tools/lsp', 'tools/syntax_updater', 'tools/viewer', - 'examples/gallery', - 'examples/printerdemo/rust', - 'examples/printerdemo_old/rust', - 'examples/todo/rust', - 'examples/slide_puzzle', - 'examples/7gui', - 'examples/memory', + # 'examples/gallery', + # 'examples/printerdemo/rust', + # 'examples/printerdemo_old/rust', + # 'examples/todo/rust', + # 'examples/slide_puzzle', + # 'examples/7gui', + # 'examples/memory', 'helper_crates/const-field-offset', 'helper_crates/vtable', 'helper_crates/vtable/macro', 'xtask', - 'tests/doctests', - 'tests/driver/driverlib', - 'tests/driver/rust', - 'tests/driver/cpp', - 'tests/driver/nodejs', - 'tests/driver/interpreter', + # 'tests/doctests', + # 'tests/driver/driverlib', + # 'tests/driver/rust', + # 'tests/driver/cpp', + # 'tests/driver/nodejs', + # 'tests/driver/interpreter', ] default-members = [ @@ -45,25 +45,25 @@ default-members = [ 'sixtyfps_runtime/rendering_backends/gl', 'sixtyfps_runtime/rendering_backends/qt', 'sixtyfps_runtime/rendering_backends/default', - 'sixtyfps_compiler', - 'api/sixtyfps-rs', - 'api/sixtyfps-rs/sixtyfps-build', - 'api/sixtyfps-node/native', - 'tools/compiler', + # 'sixtyfps_compiler', + # 'api/sixtyfps-rs', + # 'api/sixtyfps-rs/sixtyfps-build', + # 'api/sixtyfps-node/native', + # 'tools/compiler', 'tools/fmt', 'tools/lsp', 'tools/syntax_updater', 'tools/viewer', - 'examples/gallery', - 'examples/printerdemo/rust', - 'examples/printerdemo_old/rust', - 'examples/todo/rust', - 'examples/slide_puzzle', - 'examples/memory', - 'tests/doctests', - 'tests/driver/rust', - 'tests/driver/nodejs', - 'tests/driver/interpreter', + # 'examples/gallery', + # 'examples/printerdemo/rust', + # 'examples/printerdemo_old/rust', + # 'examples/todo/rust', + # 'examples/slide_puzzle', + # 'examples/memory', + # 'tests/doctests', + # 'tests/driver/rust', + # 'tests/driver/nodejs', + # 'tests/driver/interpreter', ] resolver = "2" diff --git a/sixtyfps_compiler/expression_tree.rs b/sixtyfps_compiler/expression_tree.rs index 7796636c4..d522035db 100644 --- a/sixtyfps_compiler/expression_tree.rs +++ b/sixtyfps_compiler/expression_tree.rs @@ -398,6 +398,13 @@ pub enum Expression { EnumerationValue(EnumerationValue), ReturnStatement(Option>), + + LayoutCacheAccess { + layout_cache_prop: NamedReference, + index: usize, + }, + ComputeLayoutInfo(crate::layout::Layout), + SolveLayout(crate::layout::Layout), } impl Default for Expression { @@ -530,6 +537,9 @@ impl Expression { Expression::EnumerationValue(value) => Type::Enumeration(value.enumeration.clone()), // invalid because the expression is unreachable Expression::ReturnStatement(_) => Type::Invalid, + Expression::LayoutCacheAccess { .. } => Type::LogicalLength, + Expression::ComputeLayoutInfo(_) => crate::layout::layout_info_type(), + Expression::SolveLayout(_) => Type::LayoutCache, } } @@ -613,6 +623,9 @@ impl Expression { Expression::ReturnStatement(expr) => { expr.as_deref().map(|expr| visitor(expr)); } + Expression::LayoutCacheAccess { .. } => {} + Expression::ComputeLayoutInfo(_) => {} + Expression::SolveLayout(_) => {} } } @@ -695,6 +708,9 @@ impl Expression { Expression::ReturnStatement(expr) => { expr.as_deref_mut().map(|expr| visitor(expr)); } + Expression::LayoutCacheAccess { .. } => {} + Expression::ComputeLayoutInfo(_) => {} + Expression::SolveLayout(_) => {} } } @@ -751,6 +767,10 @@ impl Expression { Expression::ReturnStatement(expr) => { expr.as_ref().map_or(true, |expr| expr.is_constant()) } + + Expression::LayoutCacheAccess { .. } => false, + Expression::ComputeLayoutInfo(_) => false, + Expression::SolveLayout(_) => false, } } @@ -915,7 +935,8 @@ impl Expression { | Type::Callback { .. } | Type::Function { .. } | Type::Void - | Type::ElementReference => Expression::Invalid, + | Type::ElementReference + | Type::LayoutCache => Expression::Invalid, Type::Float32 => Expression::NumberLiteral(0., Unit::None), Type::Int32 => Expression::NumberLiteral(0., Unit::None), Type::String => Expression::StringLiteral(String::new()), @@ -970,7 +991,7 @@ impl Expression { } /// The expression in the Element::binding hash table -#[derive(Default, Debug, Clone, derive_more::Deref, derive_more::DerefMut)] +#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut)] pub struct BindingExpression { #[deref] #[deref_mut] @@ -979,6 +1000,8 @@ pub struct BindingExpression { pub span: Option, /// How deep is this binding declared in the hierarchy. When two binding are conflicting /// for the same priority (because of two way binding), the lower priority wins. + /// The priority starts at 1, and each level of inlining adds one to the priority. + /// 0 means the expression was added by some passes and it is not explicit in the source code pub priority: i32, } @@ -993,7 +1016,7 @@ impl BindingExpression { Self { expression: Expression::Uncompiled(node.clone()), span: Some(node.to_source_location()), - priority: 0, + priority: 1, } } } @@ -1176,6 +1199,11 @@ pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std write!(f, "return ")?; e.as_ref().map(|e| pretty_print(f, e)).unwrap_or(Ok(())) } + Expression::LayoutCacheAccess { layout_cache_prop, index } => { + write!(f, "{:?}[{}]", layout_cache_prop, index) + } + Expression::ComputeLayoutInfo(l) => write!(f, "info({:?})", l), + Expression::SolveLayout(l) => write!(f, "solve({:?})", l), } } diff --git a/sixtyfps_compiler/langtype.rs b/sixtyfps_compiler/langtype.rs index e1f4d79ac..ba57a2efc 100644 --- a/sixtyfps_compiler/langtype.rs +++ b/sixtyfps_compiler/langtype.rs @@ -54,7 +54,7 @@ pub enum Type { PathElements, Easing, Brush, - + /// This is usually a model Array(Box), Struct { fields: BTreeMap, @@ -69,6 +69,9 @@ pub enum Type { UnitProduct(Vec<(Unit, i8)>), ElementReference, + + /// This is a `SharedArray` + LayoutCache, } impl core::cmp::PartialEq for Type { @@ -107,6 +110,7 @@ impl core::cmp::PartialEq for Type { Type::Enumeration(lhs) => matches!(other, Type::Enumeration(rhs) if lhs == rhs), Type::UnitProduct(a) => matches!(other, Type::UnitProduct(b) if a == b), Type::ElementReference => matches!(other, Type::ElementReference), + Type::LayoutCache => matches!(other, Type::LayoutCache), } } } @@ -189,6 +193,7 @@ impl Display for Type { write!(f, "({})", x.join("×")) } Type::ElementReference => write!(f, "element ref"), + Type::LayoutCache => write!(f, "layout cache"), } } } @@ -469,6 +474,7 @@ impl Type { Type::Enumeration(_) => None, Type::UnitProduct(_) => None, Type::ElementReference => None, + Type::LayoutCache => None, } } @@ -682,6 +688,12 @@ pub struct PropertyLookupResult<'a> { pub property_type: Type, } +impl<'a> PropertyLookupResult<'a> { + pub fn is_valid(&self) -> bool { + self.property_type != Type::Invalid + } +} + #[derive(Debug, Clone)] pub struct Enumeration { pub name: String, diff --git a/sixtyfps_compiler/layout.rs b/sixtyfps_compiler/layout.rs index 7d74ab83f..8402aefb7 100644 --- a/sixtyfps_compiler/layout.rs +++ b/sixtyfps_compiler/layout.rs @@ -9,16 +9,14 @@ LICENSE END */ //! Datastructures used to represent layouts in the compiler -use crate::langtype::Type; -use crate::object_tree::{ElementRc, PropertyDeclaration}; -use crate::{diagnostics::BuildDiagnostics, langtype::PropertyLookupResult}; -use crate::{ - expression_tree::{Expression, NamedReference, Path}, - object_tree::Component, -}; -use std::{borrow::Cow, rc::Rc}; +use crate::diagnostics::BuildDiagnostics; +use crate::expression_tree::{Expression, NamedReference, Path}; +use crate::langtype::{PropertyLookupResult, Type}; +use crate::object_tree::{Component, ElementRc}; -#[derive(Debug, derive_more::From)] +use std::rc::Rc; + +#[derive(Clone, Debug, derive_more::From)] pub enum Layout { GridLayout(GridLayout), PathLayout(PathLayout), @@ -37,7 +35,7 @@ impl Layout { impl Layout { /// Call the visitor for each NamedReference stored in the layout - fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) { + pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) { match self { Layout::GridLayout(grid) => grid.visit_named_references(visitor), Layout::BoxLayout(l) => l.visit_named_references(visitor), @@ -46,65 +44,29 @@ impl Layout { } } -/// Holds a list of all layout in the component -#[derive(derive_more::Deref, derive_more::DerefMut, Default, Debug)] -pub struct LayoutVec { - #[deref] - #[deref_mut] - pub layouts: Vec, - /// The index within the vector of the layout which applies to the root item, if any - pub main_layout: Option, - /// The constraints that applies to the root item - pub root_constraints: LayoutConstraints, -} - -impl LayoutVec { - /// Call the visitor for each NamedReference stored in the layout - pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) { - self.root_constraints.visit_named_references(visitor); - for sub in &mut self.layouts { - sub.visit_named_references(visitor); - } - } -} - /// An Item in the layout tree -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct LayoutItem { - pub element: Option, - pub layout: Option, + pub element: ElementRc, pub constraints: LayoutConstraints, } impl LayoutItem { - pub fn rect(&self) -> Cow { - if let Some(e) = &self.element { - let p = |unresolved_name: &str| { - let PropertyLookupResult { resolved_name, property_type } = - e.borrow().lookup_property(unresolved_name); - if property_type == Type::LogicalLength { - Some(NamedReference::new(e, resolved_name.as_ref())) - } else { - None - } - }; - Cow::Owned(LayoutRect { - x_reference: p("x"), - y_reference: p("y"), - width_reference: if !self.constraints.fixed_width { p("width") } else { None }, - height_reference: if !self.constraints.fixed_height { p("height") } else { None }, - }) - } else if let Some(l) = &self.layout { - let mut r = Cow::Borrowed(l.rect()); - if r.width_reference.is_some() && self.constraints.fixed_width { - r.to_mut().width_reference = None; + pub fn rect(&self) -> LayoutRect { + let p = |unresolved_name: &str| { + let PropertyLookupResult { resolved_name, property_type } = + self.element.borrow().lookup_property(unresolved_name); + if property_type == Type::LogicalLength { + Some(NamedReference::new(&self.element, resolved_name.as_ref())) + } else { + None } - if r.height_reference.is_some() && self.constraints.fixed_height { - r.to_mut().height_reference = None; - } - r - } else { - Cow::Owned(LayoutRect::default()) + }; + LayoutRect { + x_reference: p("x"), + y_reference: p("y"), + width_reference: if !self.constraints.fixed_width { p("width") } else { None }, + height_reference: if !self.constraints.fixed_height { p("height") } else { None }, } } } @@ -119,17 +81,7 @@ pub struct LayoutRect { impl LayoutRect { pub fn install_on_element(element: &ElementRc) -> Self { - let install_prop = |name: &str| { - element.borrow_mut().property_declarations.insert( - name.to_string(), - PropertyDeclaration { - property_type: Type::LogicalLength, - node: None, - ..Default::default() - }, - ); - Some(NamedReference::new(element, name)) - }; + let install_prop = |name: &str| Some(NamedReference::new(element, name)); Self { x_reference: install_prop("x"), @@ -147,7 +99,7 @@ impl LayoutRect { } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct LayoutConstraints { pub minimum_width: Option, pub maximum_width: Option, @@ -251,7 +203,7 @@ impl LayoutConstraints { .chain(self.vertical_stretch.as_ref().map(|x| (x, "vertical_stretch"))) } - fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) { + pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) { self.maximum_width.as_mut().map(|e| visitor(&mut *e)); self.minimum_width.as_mut().map(|e| visitor(&mut *e)); self.maximum_height.as_mut().map(|e| visitor(&mut *e)); @@ -264,7 +216,7 @@ impl LayoutConstraints { } /// An element in a GridLayout -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct GridLayoutElement { pub col: u16, pub row: u16, @@ -273,7 +225,7 @@ pub struct GridLayoutElement { pub item: LayoutItem, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Padding { pub left: Option, pub right: Option, @@ -290,15 +242,12 @@ impl Padding { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct LayoutGeometry { pub rect: LayoutRect, pub spacing: Option, pub alignment: Option, pub padding: Padding, - /// This contains the reference of properties of the materialized properties who - /// don't have explicit binding and must therefore need to be set - pub materialized_constraints: LayoutConstraints, } impl LayoutGeometry { @@ -307,10 +256,10 @@ impl LayoutGeometry { self.spacing.as_mut().map(|e| visitor(&mut *e)); self.alignment.as_mut().map(|e| visitor(&mut *e)); self.padding.visit_named_references(visitor); - self.materialized_constraints.visit_named_references(visitor); } } +/// Return a named reference to a property if a binding is set on that property fn binding_reference(element: &ElementRc, name: &str) -> Option { element.borrow().bindings.contains_key(name).then(|| NamedReference::new(element, name)) } @@ -337,11 +286,7 @@ fn init_fake_property( } impl LayoutGeometry { - pub fn new( - rect: LayoutRect, - layout_element: &ElementRc, - style_metrics: &Option>, - ) -> Self { + pub fn new(layout_element: &ElementRc, style_metrics: &Option>) -> Self { let style_metrics_element = style_metrics.as_ref().map(|comp| comp.root_element.clone()); let padding = || { @@ -358,10 +303,6 @@ impl LayoutGeometry { }); let alignment = binding_reference(layout_element, "alignment"); - init_fake_property(layout_element, "width", || rect.width_reference.clone()); - init_fake_property(layout_element, "height", || rect.height_reference.clone()); - init_fake_property(layout_element, "x", || rect.x_reference.clone()); - init_fake_property(layout_element, "y", || rect.y_reference.clone()); init_fake_property(layout_element, "padding_left", padding); init_fake_property(layout_element, "padding_right", padding); init_fake_property(layout_element, "padding_top", padding); @@ -374,31 +315,14 @@ impl LayoutGeometry { bottom: binding_reference(layout_element, "padding_bottom").or_else(padding), }; - // that's kind of the opposite of binding reference - let fake_property_ref = |name| { - (layout_element.borrow().property_declarations.contains_key(name) - && !layout_element.borrow().bindings.contains_key(name)) - .then(|| NamedReference::new(layout_element, name)) - }; - let materialized_constraints = LayoutConstraints { - minimum_width: fake_property_ref("minimum_width"), - maximum_width: fake_property_ref("maximum_width"), - minimum_height: fake_property_ref("minimum_height"), - maximum_height: fake_property_ref("maximum_height"), - preferred_width: fake_property_ref("preferred_width"), - preferred_height: fake_property_ref("preferred_height"), - horizontal_stretch: fake_property_ref("horizontal_stretch"), - vertical_stretch: fake_property_ref("vertical_stretch"), - // these two have booleans have no meeing in this case - fixed_width: false, - fixed_height: false, - }; - Self { rect, spacing, padding, alignment, materialized_constraints } + let rect = LayoutRect::install_on_element(layout_element); + + Self { rect, spacing, padding, alignment } } } /// Internal representation of a grid layout -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct GridLayout { /// All the elements will be layout within that element. pub elems: Vec, @@ -409,7 +333,6 @@ pub struct GridLayout { impl GridLayout { fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) { for cell in &mut self.elems { - cell.item.layout.as_mut().map(|x| x.visit_named_references(visitor)); cell.item.constraints.visit_named_references(visitor); } self.geometry.visit_named_references(visitor); @@ -417,7 +340,7 @@ impl GridLayout { } /// Internal representation of a BoxLayout -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct BoxLayout { /// When true, this is a HorizonalLayout, otherwise a VerticalLayout pub is_horizontal: bool, @@ -428,7 +351,6 @@ pub struct BoxLayout { impl BoxLayout { fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) { for cell in &mut self.elems { - cell.layout.as_mut().map(|x| x.visit_named_references(visitor)); cell.constraints.visit_named_references(visitor); } self.geometry.visit_named_references(visitor); @@ -436,7 +358,7 @@ impl BoxLayout { } /// Internal representation of a path layout -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct PathLayout { pub path: Path, pub elements: Vec, @@ -555,3 +477,34 @@ pub mod gen { layout_tree.last().unwrap() } } + +/// The [`Type`] for a runtime LayoutInfo structure +pub fn layout_info_type() -> Type { + Type::Struct { + fields: [ + "min_width", + "min_height", + "max_width", + "max_height", + "preferred_width", + "preferred_height", + ] + .iter() + .map(|s| (s.to_string(), Type::LogicalLength)) + .chain( + [ + "min_width_percent", + "min_height_percent", + "max_width_percent", + "max_height_percent", + "horizontal_stretch", + "vertical_stretch", + ] + .iter() + .map(|s| (s.to_string(), Type::Float32)), + ) + .collect(), + name: Some("LayoutInfo".into()), + node: None, + } +} diff --git a/sixtyfps_compiler/object_tree.rs b/sixtyfps_compiler/object_tree.rs index e26409234..50a713b8e 100644 --- a/sixtyfps_compiler/object_tree.rs +++ b/sixtyfps_compiler/object_tree.rs @@ -17,6 +17,7 @@ use crate::diagnostics::{BuildDiagnostics, Spanned}; use crate::expression_tree::{self, BindingExpression, Expression, Unit}; use crate::langtype::PropertyLookupResult; use crate::langtype::{BuiltinElement, NativeClass, Type}; +use crate::layout::LayoutConstraints; use crate::namedreference::NamedReference; use crate::parser::{identifier_text, syntax_nodes, SyntaxKind, SyntaxNode}; use crate::typeloader::ImportedTypes; @@ -188,8 +189,8 @@ pub struct Component { /// generator for symbol generation. pub embedded_file_resources: RefCell>, - /// All layouts in this component - pub layouts: RefCell, + /// The layout constraints of the root item + pub root_constraints: RefCell, /// When creating this component and inserting "children", append them to the children of /// the element pointer to by this field. @@ -332,6 +333,8 @@ pub struct Element { /// true when this item's geometry is handled by a layout pub child_of_layout: bool, + /// The property pointing to the layout info + pub layout_info_prop: Option, /// true if this Element is the fake Flickable viewport pub is_flickable_viewport: bool, @@ -1233,6 +1236,9 @@ pub fn visit_all_named_references_in_element( match expr { Expression::PropertyReference(r) | Expression::CallbackReference(r) => vis(r), Expression::TwoWayBinding(r, _) => vis(r), + Expression::LayoutCacheAccess { layout_cache_prop, .. } => vis(layout_cache_prop), + Expression::SolveLayout(l) => l.visit_named_references(vis), + Expression::ComputeLayoutInfo(l) => l.visit_named_references(vis), // This is not really a named reference, but the result is the same, it need to be updated // FIXME: this should probably be lowered into a PropertyReference Expression::RepeaterModelReference { element } @@ -1272,6 +1278,9 @@ pub fn visit_all_named_references_in_element( } } elem.borrow_mut().repeated = repeated; + let mut layout_info_prop = std::mem::take(&mut elem.borrow_mut().layout_info_prop); + layout_info_prop.as_mut().map(vis); + elem.borrow_mut().layout_info_prop = layout_info_prop; } /// Visit all named reference in this component and sub component @@ -1287,7 +1296,7 @@ pub fn visit_all_named_references( let compo = elem.borrow().enclosing_component.clone(); if !Weak::ptr_eq(parent_compo, &compo) { let compo = compo.upgrade().unwrap(); - compo.layouts.borrow_mut().visit_named_references(vis); + compo.root_constraints.borrow_mut().visit_named_references(vis); compo.popup_windows.borrow_mut().iter_mut().for_each(|p| { vis(&mut p.x); vis(&mut p.y); diff --git a/sixtyfps_compiler/passes/inlining.rs b/sixtyfps_compiler/passes/inlining.rs index 1e3d00cfd..e15540068 100644 --- a/sixtyfps_compiler/passes/inlining.rs +++ b/sixtyfps_compiler/passes/inlining.rs @@ -182,6 +182,7 @@ fn duplicate_element_with_mapping( .map(|t| duplicate_transition(t, mapping, root_component)) .collect(), child_of_layout: elem.child_of_layout, + layout_info_prop: elem.layout_info_prop.clone(), named_references: Default::default(), item_index: Default::default(), // Not determined yet is_flickable_viewport: elem.is_flickable_viewport, diff --git a/sixtyfps_compiler/passes/lower_layout.rs b/sixtyfps_compiler/passes/lower_layout.rs index e5964a105..3a3e8c90c 100644 --- a/sixtyfps_compiler/passes/lower_layout.rs +++ b/sixtyfps_compiler/passes/lower_layout.rs @@ -15,30 +15,139 @@ use crate::layout::*; use crate::object_tree::*; use crate::typeregister::TypeRegister; use crate::{diagnostics::BuildDiagnostics, typeloader::TypeLoader}; -use std::cell::RefCell; use std::rc::Rc; -fn lower_grid_layout( +pub async fn lower_layouts<'a>( + component: &Rc, + type_loader: &mut TypeLoader<'a>, + diag: &mut BuildDiagnostics, +) { + // Ignore import errors + let mut build_diags_to_ignore = crate::diagnostics::BuildDiagnostics::default(); + let style_metrics = type_loader + .import_type("sixtyfps_widgets.60", "StyleMetrics", &mut build_diags_to_ignore) + .await; + let style_metrics = + style_metrics.and_then(|sm| if let Type::Component(c) = sm { Some(c) } else { None }); + lower_layouts_impl(component, &type_loader.global_type_registry.borrow(), &style_metrics, diag); +} + +fn lower_layouts_impl( component: &Rc, - rect: LayoutRect, - grid_layout_element: &ElementRc, - collected_children: &mut Vec, type_register: &TypeRegister, style_metrics: &Option>, diag: &mut BuildDiagnostics, -) -> Option { +) { + // FIXME: one should enable minimum_width and minimum_height on the window, but not height and width + //component.layouts.borrow_mut().root_constraints = + // LayoutConstraints::new(&component.root_element, diag); + + recurse_elem_including_sub_components(&component, &(), &mut |elem, _| { + let component = elem.borrow().enclosing_component.upgrade().unwrap(); + lower_element_layout(&component, elem, type_register, style_metrics, diag); + check_no_layout_properties(elem, diag); + }); +} + +fn lower_element_layout( + component: &Rc, + elem: &ElementRc, + type_register: &TypeRegister, + style_metrics: &Option>, + diag: &mut BuildDiagnostics, +) { + //let base_type = + // if let Type::Builtin(be) = &elem.borrow().base_type { be.clone() } else { return }; + + let base_type = if let Type::Builtin(base_type) = &elem.borrow().base_type { + base_type.clone() + } else { + return; + }; + match base_type.name.as_str() { + "Row" => panic!("Error caught at element lookup time"), + "GridLayout" => lower_grid_layout(component, elem, style_metrics, diag), + "HorizontalLayout" => lower_grid_layout(component, elem, style_metrics, diag), + "VerticalLayout" => lower_grid_layout(component, elem, style_metrics, diag), + /*"HorizontalLayout" => &lower_box_layout, + "VerticalLayout" => &lower_box_layout, + "PathLayout" => &lower_path_layout,*/ + _ => return, + }; + + { + let mut elem = elem.borrow_mut(); + let elem = &mut *elem; + let prev_base = std::mem::replace(&mut elem.base_type, type_register.lookup("Rectangle")); + // Create fake properties for the layout properties + for p in elem.bindings.keys() { + if !elem.base_type.lookup_property(p).is_valid() + && !elem.property_declarations.contains_key(p) + { + let ty = prev_base.lookup_property(p).property_type; + if ty != Type::Invalid { + elem.property_declarations.insert(p.into(), ty.into()); + } + } + } + } + + /* + let merge_min_max = |prop, op| { + let mut elem = elem.borrow_mut(); + if let Some(old) = elem.bindings.get_mut(prop) { + if old.priority == 0 { + old.expression = expression_tree::min_max_expression( + std::mem::take(&mut old.expression), + Expression::PropertyReference(NamedReference::new(&child, prop)), + op, + ); + } + } else { + elem.bindings.insert( + prop.to_owned(), + Expression::PropertyReference(NamedReference::new(&child, prop)).into(), + ); + } + }; + // Instead of forwarding each property like that, one could imagine having only one LayoutInfo property that we merge + // instead of merging all the component separately + merge_min_max("minimum_width", '>'); + merge_min_max("minimum_height", '>'); + merge_min_max("maximum_width", '<'); + merge_min_max("maximum_height", '<'); + // Ideally this should be the same as "merge" + merge_min_max("preferred_width", '>'); + merge_min_max("preferred_height", '>'); + // TODO: handle the implicit constraints + */ +} + +fn lower_grid_layout( + component: &Rc, + grid_layout_element: &ElementRc, + style_metrics: &Option>, + diag: &mut BuildDiagnostics, +) { let mut grid = GridLayout { elems: Default::default(), - geometry: LayoutGeometry::new(rect, &grid_layout_element, style_metrics), + geometry: LayoutGeometry::new(&grid_layout_element, style_metrics), }; + // FIXME: remove + let is_vertical = grid_layout_element.borrow().base_type.to_string() == "VerticalLayout"; + + let layout_cache_prop = create_new_prop(grid_layout_element, "layout_cache", Type::LayoutCache); + let layout_info_prop = create_new_prop(grid_layout_element, "layout_info", layout_info_type()); + 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(); for layout_child in layout_children { let is_row = if let Type::Builtin(be) = &layout_child.borrow().base_type { - be.native_class.class_name == "Row" + be.name == "Row" } else { false }; @@ -49,49 +158,39 @@ fn lower_grid_layout( } 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), - diag, - type_register, - style_metrics, - &component, - collected_children, - ); + grid.add_element(&x, (&mut row, &mut col), diag, &layout_cache_prop); col += 1; + collected_children.push(x); } if col > 0 { row += 1; col = 0; } - component.optimized_elements.borrow_mut().push(layout_child.clone()); + component.optimized_elements.borrow_mut().push(layout_child); } else { - grid.add_element( - layout_child, - (&mut row, &mut col), - diag, - type_register, - style_metrics, - &component, - collected_children, - ); - col += 1; + grid.add_element(&layout_child, (&mut row, &mut col), diag, &layout_cache_prop); + if is_vertical { + row += 1 + } else { + col += 1 + }; + collected_children.push(layout_child); } } - component.optimized_elements.borrow_mut().push(grid_layout_element.clone()); - if !grid.elems.is_empty() { - Some(grid.into()) - } else { - None - } + grid_layout_element.borrow_mut().children = collected_children; + layout_cache_prop.element().borrow_mut().bindings.insert( + layout_cache_prop.name().into(), + Expression::SolveLayout(Layout::GridLayout(grid.clone())).into(), + ); + layout_info_prop.element().borrow_mut().bindings.insert( + layout_info_prop.name().into(), + Expression::ComputeLayoutInfo(Layout::GridLayout(grid)).into(), + ); } fn lower_box_layout( component: &Rc, - rect: LayoutRect, layout_element: &ElementRc, - collected_children: &mut Vec, - type_register: &TypeRegister, style_metrics: &Option>, diag: &mut BuildDiagnostics, ) -> Option { @@ -99,18 +198,11 @@ fn lower_box_layout( let mut layout = BoxLayout { is_horizontal, elems: Default::default(), - geometry: LayoutGeometry::new(rect, &layout_element, style_metrics), + geometry: LayoutGeometry::new(&layout_element, style_metrics), }; let layout_children = std::mem::take(&mut layout_element.borrow_mut().children); for layout_child in layout_children { - if let Some(item) = create_layout_item( - &layout_child, - component, - collected_children, - type_register, - style_metrics, - diag, - ) { + if let Some(item) = create_layout_item(&layout_child, diag) { layout.elems.push(item) } } @@ -166,131 +258,8 @@ fn lower_path_layout( ) } -type LayoutParseFunction = dyn Fn( - &Rc, - LayoutRect, - &ElementRc, - &mut Vec, - &TypeRegister, - &Option>, - &mut BuildDiagnostics, -) -> Option; - -fn layout_parse_function( - layout_element_candidate: &ElementRc, -) -> Option<&'static LayoutParseFunction> { - if let Type::Builtin(be) = &layout_element_candidate.borrow().base_type { - match be.native_class.class_name.as_str() { - "Row" => panic!("Error caught at element lookup time"), - "GridLayout" => Some(&lower_grid_layout), - "HorizontalLayout" => Some(&lower_box_layout), - "VerticalLayout" => Some(&lower_box_layout), - "PathLayout" => Some(&lower_path_layout), - _ => None, - } - } else { - None - } -} - -fn lower_element_layout( - component: &Rc, - elem: &ElementRc, - type_register: &TypeRegister, - style_metrics: &Option>, - diag: &mut BuildDiagnostics, -) -> LayoutVec { - let old_children = { - let mut elem = elem.borrow_mut(); - let new_children = Vec::with_capacity(elem.children.len()); - std::mem::replace(&mut elem.children, new_children) - }; - - // lay out within the current element's boundaries. - let rect_to_layout = LayoutRect { - x_reference: None, - y_reference: None, - width_reference: Some(NamedReference::new(elem, "width")), - height_reference: Some(NamedReference::new(elem, "height")), - }; - - let mut found_layouts = LayoutVec::default(); - - for child in old_children { - if let Some(layout_parser) = layout_parse_function(&child) { - let mut children = std::mem::take(&mut elem.borrow_mut().children); - if let Some(layout) = layout_parser( - component, - rect_to_layout.clone(), - &child, - &mut children, - type_register, - style_metrics, - diag, - ) { - if Rc::ptr_eq(elem, &component.root_element) { - found_layouts.main_layout = Some(found_layouts.len()); - } - found_layouts.push(layout); - } - elem.borrow_mut().children = children; - continue; - } else { - elem.borrow_mut().children.push(child); - } - } - - found_layouts -} - -/// Currently this just removes the layout from the tree -pub async fn lower_layouts<'a>( - component: &Rc, - type_loader: &mut TypeLoader<'a>, - diag: &mut BuildDiagnostics, -) { - // Ignore import errors - let mut build_diags_to_ignore = crate::diagnostics::BuildDiagnostics::default(); - let style_metrics = type_loader - .import_type("sixtyfps_widgets.60", "StyleMetrics", &mut build_diags_to_ignore) - .await; - let style_metrics = - style_metrics.and_then(|sm| if let Type::Component(c) = sm { Some(c) } else { None }); - lower_layouts_impl(component, &type_loader.global_type_registry.borrow(), &style_metrics, diag); -} - -fn lower_layouts_impl( - component: &Rc, - type_register: &TypeRegister, - style_metrics: &Option>, - diag: &mut BuildDiagnostics, -) { - // FIXME: one should enable minimum_width and minimum_height on the window, but not height and width - //component.layouts.borrow_mut().root_constraints = - // LayoutConstraints::new(&component.root_element, diag); - - recurse_elem_including_sub_components(&component, &(), &mut |elem, _| { - let component = elem.borrow().enclosing_component.upgrade().unwrap(); - let mut layouts = - lower_element_layout(&component, elem, type_register, style_metrics, diag); - let mut component_layouts = component.layouts.borrow_mut(); - component_layouts.main_layout = component_layouts - .main_layout - .or_else(|| layouts.main_layout.map(|x| x + component_layouts.len())); - component_layouts.append(&mut layouts); - check_no_layout_properties(elem, diag); - }); -} - /// Create a LayoutItem for the given `item_element` returns None is the layout is empty -fn create_layout_item( - item_element: &ElementRc, - component: &Rc, - collected_children: &mut Vec, - type_register: &TypeRegister, - style_metrics: &Option>, - diag: &mut BuildDiagnostics, -) -> Option { +fn create_layout_item(item_element: &ElementRc, diag: &mut BuildDiagnostics) -> Option { let fix_explicit_percent = |prop: &str, item: &ElementRc| { if !item.borrow().bindings.get(prop).map_or(false, |b| b.ty() == Type::Percent) { return; @@ -315,68 +284,25 @@ fn create_layout_item( let constraints = LayoutConstraints::new(item_element, diag); item_element.borrow_mut().child_of_layout = true; - if item_element.borrow().repeated.is_some() { let rep_comp = item_element.borrow().base_type.as_component().clone(); fix_explicit_percent("width", &rep_comp.root_element); fix_explicit_percent("height", &rep_comp.root_element); - rep_comp.layouts.borrow_mut().root_constraints = + *rep_comp.root_constraints.borrow_mut() = LayoutConstraints::new(&rep_comp.root_element, diag); rep_comp.root_element.borrow_mut().child_of_layout = true; - collected_children.push(item_element.clone()); - - if layout_parse_function(&rep_comp.root_element).is_some() { - let new_root = ElementRc::new(RefCell::new(Element { - id: format!("{}_rootrect", item_element.borrow().id), - base_type: type_register.lookup_element("Rectangle").unwrap(), - enclosing_component: Rc::downgrade(&rep_comp), - ..Default::default() - })); - drop(rep_comp); - crate::object_tree::inject_element_as_repeated_element(item_element, new_root); - } - - Some(LayoutItem { element: Some(item_element.clone()), layout: None, constraints }) - } else if let Some(nested_layout_parser) = layout_parse_function(item_element) { - let layout_rect = LayoutRect::install_on_element(&item_element); - - nested_layout_parser( - component, - layout_rect, - &item_element, - collected_children, - type_register, - style_metrics, - diag, - ) - .map(|x| LayoutItem { layout: Some(x), constraints, element: None }) - } else { - collected_children.push(item_element.clone()); - let element = item_element.clone(); - let layout = { - let mut layouts = - lower_element_layout(component, &element, type_register, style_metrics, diag); - if layouts.is_empty() { - None - } else { - Some(layouts.remove(0)) - } - }; - Some(LayoutItem { element: Some(element), layout, constraints }) - } + }; + Some(LayoutItem { element: item_element.clone(), constraints }) } impl GridLayout { fn add_element( &mut self, - item_element: ElementRc, + item_element: &ElementRc, (row, col): (&mut u16, &mut u16), diag: &mut BuildDiagnostics, - type_register: &TypeRegister, - style_metrics: &Option>, - component: &Rc, - collected_children: &mut Vec, + layout_cache_prop: &NamedReference, ) { let mut get_const_value = |name: &str| { item_element @@ -395,14 +321,28 @@ impl GridLayout { *col = c; } - if let Some(layout_item) = create_layout_item( - &item_element, - component, - collected_children, - type_register, - style_metrics, - diag, - ) { + if let Some(layout_item) = create_layout_item(&item_element, diag) { + let index = self.elems.len(); + //FIXME: report errors if there is already bindings on x or y + let set_prop_from_cache = |prop: &str, offset: usize| { + item_element.borrow_mut().bindings.insert( + prop.into(), + Expression::LayoutCacheAccess { + layout_cache_prop: layout_cache_prop.clone(), + index: index * 4 + offset, + } + .into(), + ); + }; + set_prop_from_cache("x", 0); + set_prop_from_cache("y", 1); + if !layout_item.constraints.fixed_width { + set_prop_from_cache("width", 2); + } + if !layout_item.constraints.fixed_height { + set_prop_from_cache("height", 3); + } + self.elems.push(GridLayoutElement { col: *col, row: *row, @@ -438,6 +378,28 @@ fn eval_const_expr( } } +/// Create a new property based on the name. (it might get a different name if that property exist) +fn create_new_prop(elem: &ElementRc, tentative_name: &str, ty: Type) -> NamedReference { + let mut e = elem.borrow_mut(); + if !e.lookup_property(tentative_name).is_valid() { + e.property_declarations.insert(tentative_name.into(), ty.into()); + drop(e); + NamedReference::new(elem, tentative_name) + } else { + let mut counter = 0; + loop { + counter += 1; + let name = format!("{}{}", tentative_name, counter); + if !e.lookup_property(&name).is_valid() { + e.property_declarations.insert(name.clone(), ty.into()); + drop(e); + return NamedReference::new(elem, &name); + } + } + } +} + +/// Checks that there is grid-layout specific properties left fn check_no_layout_properties(item: &ElementRc, diag: &mut BuildDiagnostics) { for (prop, expr) in item.borrow().bindings.iter() { if matches!(prop.as_ref(), "col" | "row" | "colspan" | "rowspan") { diff --git a/sixtyfps_compiler/passes/lower_popups.rs b/sixtyfps_compiler/passes/lower_popups.rs index 906a02320..6f4f26f25 100644 --- a/sixtyfps_compiler/passes/lower_popups.rs +++ b/sixtyfps_compiler/passes/lower_popups.rs @@ -60,8 +60,6 @@ fn lower_popup_window( }; let parent_component = parent_element.borrow().enclosing_component.upgrade().unwrap(); - // Because layout constraint which are supposed to be in the popup will not be lowered, the layout lowering should be done after - debug_assert!(parent_component.layouts.borrow().is_empty()); // Remove the popup_window_element from its parent parent_element.borrow_mut().children.retain(|child| !Rc::ptr_eq(child, popup_window_element)); diff --git a/sixtyfps_compiler/passes/move_declarations.rs b/sixtyfps_compiler/passes/move_declarations.rs index bac789960..77fc9ad82 100644 --- a/sixtyfps_compiler/passes/move_declarations.rs +++ b/sixtyfps_compiler/passes/move_declarations.rs @@ -93,7 +93,7 @@ pub fn move_declarations(component: &Rc, diag: &mut BuildDiagnostics) component.optimized_elements.borrow().iter().for_each(|e| move_bindings_and_animations(e)); - component.layouts.borrow_mut().visit_named_references(&mut |e| fixup_reference(e)); + component.root_constraints.borrow_mut().visit_named_references(&mut |e| fixup_reference(e)); component.popup_windows.borrow_mut().iter_mut().for_each(|p| { fixup_reference(&mut p.x); fixup_reference(&mut p.y); diff --git a/sixtyfps_compiler/passes/repeater_component.rs b/sixtyfps_compiler/passes/repeater_component.rs index 6ce3469c1..b1bed92d8 100644 --- a/sixtyfps_compiler/passes/repeater_component.rs +++ b/sixtyfps_compiler/passes/repeater_component.rs @@ -23,9 +23,6 @@ pub fn process_repeater_components(component: &Rc) { } fn create_repeater_components(component: &Rc) { - // Because layout constraint which are supposed to be in the repeater will not be lowered - debug_assert!(component.layouts.borrow().is_empty()); - recurse_elem(&component.root_element, &(), &mut |elem, _| { if elem.borrow().repeated.is_none() { return; @@ -48,6 +45,7 @@ fn create_repeater_components(component: &Rc) { states: std::mem::take(&mut elem.states), transitions: std::mem::take(&mut elem.transitions), child_of_layout: elem.child_of_layout, + layout_info_prop: elem.layout_info_prop.take(), is_flickable_viewport: elem.is_flickable_viewport, item_index: Default::default(), // Not determined yet })), diff --git a/sixtyfps_runtime/corelib/layout.rs b/sixtyfps_runtime/corelib/layout.rs index a1d3dbee2..d37ebae53 100644 --- a/sixtyfps_runtime/corelib/layout.rs +++ b/sixtyfps_runtime/corelib/layout.rs @@ -11,7 +11,7 @@ LICENSE END */ //! //! Currently this is a very basic implementation -use crate::{slice::Slice, Property}; +use crate::{slice::Slice, Property, SharedVector}; type Coord = f32; @@ -257,24 +257,21 @@ pub struct GridLayoutData<'a> { pub y: Coord, pub spacing: Coord, pub padding: &'a Padding, - pub cells: Slice<'a, GridLayoutCellData<'a>>, + pub cells: Slice<'a, GridLayoutCellData>, } #[repr(C)] #[derive(Default, Debug)] -pub struct GridLayoutCellData<'a> { +pub struct GridLayoutCellData { pub col: u16, pub row: u16, pub colspan: u16, pub rowspan: u16, pub constraint: LayoutInfo, - pub x: Option<&'a Property>, - pub y: Option<&'a Property>, - pub width: Option<&'a Property>, - pub height: Option<&'a Property>, } -pub fn solve_grid_layout(data: &GridLayoutData) { +/// return, an array which is of siz `data.cells.len() * 4` which for each cell we give the x, y, width, height +pub fn solve_grid_layout(data: &GridLayoutData) -> SharedVector { let (mut num_col, mut num_row) = (0, 0); for cell in data.cells.iter() { num_row = num_row.max(cell.row + cell.rowspan); @@ -282,7 +279,7 @@ pub fn solve_grid_layout(data: &GridLayoutData) { } if num_col < 1 || num_row < 1 { - return; + return Default::default(); } let mut row_layout_data = vec![grid_internal::LayoutData::default(); num_row as usize]; @@ -345,30 +342,28 @@ pub fn solve_grid_layout(data: &GridLayoutData) { data.width - (data.padding.left + data.padding.right), data.spacing, ); + let mut result = SharedVector::with_capacity(4 * data.cells.len()); for cell in data.cells.iter() { let rdata = &row_layout_data[cell.row as usize]; let cdata = &col_layout_data[cell.col as usize]; - cell.x.map(|p| p.set(cdata.pos)); - cell.width.map(|p| { - p.set({ - let first_cell = &col_layout_data[cell.col as usize]; - let last_cell = &col_layout_data[cell.col as usize + cell.colspan as usize - 1]; - last_cell.pos + last_cell.size - first_cell.pos - }) + result.push(cdata.pos); + result.push(rdata.pos); + result.push({ + let first_cell = &col_layout_data[cell.col as usize]; + let last_cell = &col_layout_data[cell.col as usize + cell.colspan as usize - 1]; + last_cell.pos + last_cell.size - first_cell.pos }); - cell.y.map(|p| p.set(rdata.pos)); - cell.height.map(|p| { - p.set({ - let first_cell = &row_layout_data[cell.row as usize]; - let last_cell = &row_layout_data[cell.row as usize + cell.rowspan as usize - 1]; - last_cell.pos + last_cell.size - first_cell.pos - }) + result.push({ + let first_cell = &row_layout_data[cell.row as usize]; + let last_cell = &row_layout_data[cell.row as usize + cell.rowspan as usize - 1]; + last_cell.pos + last_cell.size - first_cell.pos }); } + result } pub fn grid_layout_info<'a>( - cells: &Slice<'a, GridLayoutCellData<'a>>, + cells: &Slice<'a, GridLayoutCellData>, spacing: Coord, padding: &Padding, ) -> LayoutInfo { diff --git a/sixtyfps_runtime/interpreter/api.rs b/sixtyfps_runtime/interpreter/api.rs index c95315b19..4fcddaaa9 100644 --- a/sixtyfps_runtime/interpreter/api.rs +++ b/sixtyfps_runtime/interpreter/api.rs @@ -111,6 +111,8 @@ pub enum Value { /// An enumation, like `TextHorizontalAlignment::align_center`, represented by `("TextHorizontalAlignment", "align_center")`. /// FIXME: consider representing that with a number? EnumerationValue(String, String), + #[doc(hidden)] + LayoutCache(SharedVector), } impl Value { @@ -153,6 +155,7 @@ impl PartialEq for Value { Value::EnumerationValue(lhs_name, lhs_value) => { matches!(other, Value::EnumerationValue(rhs_name, rhs_value) if lhs_name == rhs_name && lhs_value == rhs_value) } + Value::LayoutCache(lhs) => matches!(other, Value::LayoutCache(rhs) if lhs == rhs), } } } @@ -172,6 +175,7 @@ impl std::fmt::Debug for Value { Value::PathElements(e) => write!(f, "Value::PathElements({:?})", e), Value::EasingCurve(c) => write!(f, "Value::EasingCurve({:?})", c), Value::EnumerationValue(n, v) => write!(f, "Value::EnumerationValue({:?}, {:?})", n, v), + Value::LayoutCache(v) => write!(f, "Value::LayoutCache({:?})", v), } } } @@ -213,6 +217,7 @@ declare_value_conversion!(Struct => [Struct] ); declare_value_conversion!(Brush => [Brush] ); declare_value_conversion!(PathElements => [PathData]); declare_value_conversion!(EasingCurve => [sixtyfps_corelib::animations::EasingCurve]); +declare_value_conversion!(LayoutCache => [SharedVector] ); /// Implement From / TryInto for Value that convert a `struct` to/from `Value::Object` macro_rules! declare_value_struct_conversion { @@ -245,6 +250,11 @@ declare_value_struct_conversion!(struct sixtyfps_corelib::model::StandardListVie declare_value_struct_conversion!(struct sixtyfps_corelib::properties::StateInfo { current_state, previous_state, change_time }); declare_value_struct_conversion!(struct sixtyfps_corelib::input::KeyboardModifiers { control, alt, shift, meta }); declare_value_struct_conversion!(struct sixtyfps_corelib::input::KeyEvent { event_type, text, modifiers }); +declare_value_struct_conversion!(struct sixtyfps_corelib::layout::LayoutInfo { + min_width, max_width, min_height, max_height, + min_width_percent, max_width_percent, min_height_percent, max_height_percent, + preferred_width, preferred_height, horizontal_stretch, vertical_stretch, +}); /// Implement From / TryInto for Value that convert an `enum` to/from `Value::EnumerationValue` /// diff --git a/sixtyfps_runtime/interpreter/dynamic_component.rs b/sixtyfps_runtime/interpreter/dynamic_component.rs index 392aea84e..cdcc209ac 100644 --- a/sixtyfps_runtime/interpreter/dynamic_component.rs +++ b/sixtyfps_runtime/interpreter/dynamic_component.rs @@ -15,9 +15,6 @@ use dynamic_type::{Instance, InstanceBox}; use expression_tree::NamedReference; use object_tree::{Element, ElementRc}; use sixtyfps_compilerlib::langtype::Type; -use sixtyfps_compilerlib::layout::{ - Layout, LayoutConstraints, LayoutGeometry, LayoutItem, PathLayout, -}; use sixtyfps_compilerlib::*; use sixtyfps_compilerlib::{diagnostics::BuildDiagnostics, object_tree::PropertyDeclaration}; use sixtyfps_compilerlib::{expression_tree::Expression, langtype::PropertyLookupResult}; @@ -29,14 +26,14 @@ use sixtyfps_corelib::item_tree::{ use sixtyfps_corelib::items::{ Flickable, ItemRc, ItemRef, ItemVTable, ItemWeak, PropertyAnimation, }; -use sixtyfps_corelib::layout::{LayoutInfo, Padding}; +use sixtyfps_corelib::layout::{BoxLayoutCellData, LayoutInfo}; use sixtyfps_corelib::model::RepeatedComponent; use sixtyfps_corelib::model::Repeater; use sixtyfps_corelib::properties::InterpolatedPropertyValue; use sixtyfps_corelib::rtti::{self, AnimatedBindingKind, FieldOffset, PropertyInfo}; use sixtyfps_corelib::slice::Slice; use sixtyfps_corelib::window::ComponentWindow; -use sixtyfps_corelib::{Brush, Color, Property, SharedString}; +use sixtyfps_corelib::{Brush, Color, Property, SharedString, SharedVector}; use std::collections::HashMap; use std::{pin::Pin, rc::Rc}; @@ -184,7 +181,7 @@ impl RepeatedComponent for ErasedComponentBox { } }; - let root_c = &s.component_type.original.layouts.borrow().root_constraints; + let root_c = &s.component_type.original.root_constraints.borrow(); BoxLayoutCellData { constraint: self.borrow().as_ref().layout_info(), x: get_prop("x"), @@ -774,7 +771,8 @@ fn generate_component<'id>( "FillRule" => property_info::(), _ => panic!("unkown enum"), }, - _ => panic!("bad type"), + Type::LayoutCache => property_info::>(), + _ => panic!("bad type {:?}", &decl.property_type), }; custom_properties.insert( name.clone(), @@ -1147,7 +1145,7 @@ pub fn instantiate<'id>( comp_rc } -fn get_property_ptr(nr: &NamedReference, instance: InstanceRef) -> *const () { +pub(crate) fn get_property_ptr(nr: &NamedReference, instance: InstanceRef) -> *const () { let element = nr.element(); generativity::make_guard!(guard); let enclosing_component = eval::enclosing_component_for_element(&element, instance, guard); @@ -1167,15 +1165,6 @@ fn get_property_ptr(nr: &NamedReference, instance: InstanceRef) -> *const () { unsafe { item.as_ptr().add(item_info.rtti.properties.get(nr.name()).unwrap().offset()).cast() } } -use sixtyfps_corelib::layout::*; - -struct LayoutWithCells<'a, C> { - geometry: &'a sixtyfps_compilerlib::layout::LayoutGeometry, - cells: Vec, - spacing: f32, - padding: Padding, -} - pub struct ErasedComponentBox(ComponentBox<'static>); impl ErasedComponentBox { pub fn unerase<'a, 'id>( @@ -1221,399 +1210,6 @@ impl<'id> From> for ErasedComponentBox { } } -type RepeatedComponentRc = vtable::VRc; -enum BoxLayoutCellTmpData<'a> { - Item(BoxLayoutCellData<'a>), - Repeater(Vec), -} - -impl<'a> BoxLayoutCellTmpData<'a> { - fn into_cells(cells: &'a [Self]) -> Vec> { - let mut c = Vec::with_capacity(cells.len()); - for x in cells.iter() { - match x { - BoxLayoutCellTmpData::Item(cell) => { - c.push((*cell).clone()); - } - BoxLayoutCellTmpData::Repeater(vec) => { - c.extend(vec.iter().map(|x| x.as_pin_ref().box_layout_data())) - } - } - } - c - } -} - -#[derive(derive_more::From)] -enum LayoutTreeItem<'a> { - GridLayout(LayoutWithCells<'a, GridLayoutCellData<'a>>), - BoxLayout( - LayoutWithCells<'a, BoxLayoutCellTmpData<'a>>, - bool, - sixtyfps_corelib::layout::LayoutAlignment, - ), - PathLayout(&'a PathLayout), -} - -impl<'a> LayoutTreeItem<'a> { - fn layout_info(&self) -> LayoutInfo { - match self { - LayoutTreeItem::GridLayout(grid_layout) => grid_layout_info( - &Slice::from(grid_layout.cells.as_slice()), - grid_layout.spacing, - &grid_layout.padding, - ), - LayoutTreeItem::BoxLayout(box_layout, is_horizontal, alignment) => { - let cells = BoxLayoutCellTmpData::into_cells(&box_layout.cells); - box_layout_info( - &Slice::from(cells.as_slice()), - box_layout.spacing, - &box_layout.padding, - *alignment, - *is_horizontal, - ) - } - LayoutTreeItem::PathLayout(_) => todo!(), - } - } - - fn geometry(&self) -> Option<&LayoutGeometry> { - match self { - Self::GridLayout(LayoutWithCells { geometry, .. }) - | Self::BoxLayout(LayoutWithCells { geometry, .. }, _, _) => Some(geometry), - _ => None, - } - } -} - -fn get_layout_info<'a, 'b>( - item: &'a LayoutItem, - component: InstanceRef<'a, '_>, - layout_tree: &'b mut Vec>, - window: &ComponentWindow, -) -> LayoutInfo { - let layout_info = item.layout.as_ref().map(|l| { - let layout_tree_item = collect_layouts_recursively(layout_tree, l, component, window); - layout_tree_item.layout_info() - }); - let elem_info = item.element.as_ref().map(|elem| { - let item = &component - .component_type - .items - .get(elem.borrow().id.as_str()) - .unwrap_or_else(|| panic!("Internal error: Item {} not found", elem.borrow().id)); - unsafe { item.item_from_component(component.as_ptr()).as_ref().layouting_info(window) } - }); - - match (layout_info, elem_info) { - (None, None) => Default::default(), - (None, Some(x)) => x, - (Some(x), None) => x, - (Some(layout_info), Some(elem_info)) => layout_info.merge(&elem_info), - } -} - -fn fill_layout_info_constraints( - layout_info: &mut LayoutInfo, - constraints: &LayoutConstraints, - expr_eval: &impl Fn(&NamedReference) -> f32, -) { - let is_percent = - |nr: &NamedReference| Expression::PropertyReference(nr.clone()).ty() == Type::Percent; - constraints.minimum_width.as_ref().map(|e| { - if !is_percent(e) { - layout_info.min_width = expr_eval(e) - } else { - layout_info.min_width_percent = expr_eval(e) - } - }); - constraints.maximum_width.as_ref().map(|e| { - if !is_percent(e) { - layout_info.max_width = expr_eval(e) - } else { - layout_info.max_width_percent = expr_eval(e) - } - }); - constraints.minimum_height.as_ref().map(|e| { - if !is_percent(e) { - layout_info.min_height = expr_eval(e) - } else { - layout_info.min_height_percent = expr_eval(e) - } - }); - constraints.maximum_height.as_ref().map(|e| { - if !is_percent(e) { - layout_info.max_height = expr_eval(e) - } else { - layout_info.max_height_percent = expr_eval(e) - } - }); - constraints.preferred_width.as_ref().map(|e| { - layout_info.preferred_width = expr_eval(e); - }); - constraints.preferred_height.as_ref().map(|e| { - layout_info.preferred_height = expr_eval(e); - }); - constraints.horizontal_stretch.as_ref().map(|e| layout_info.horizontal_stretch = expr_eval(e)); - constraints.vertical_stretch.as_ref().map(|e| layout_info.vertical_stretch = expr_eval(e)); -} - -fn collect_layouts_recursively<'a, 'b>( - layout_tree: &'b mut Vec>, - layout: &'a Layout, - component: InstanceRef<'a, '_>, - window: &ComponentWindow, -) -> &'b LayoutTreeItem<'a> { - let assume_property_f32 = |nr: &Option| { - nr.as_ref().map(|nr| { - let p = get_property_ptr(nr, component); - unsafe { &*(p as *const Property) } - }) - }; - let expr_eval = |nr: &NamedReference| -> f32 { - eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap() - }; - - match layout { - Layout::GridLayout(grid_layout) => { - let cells = grid_layout - .elems - .iter() - .map(|cell| { - let mut layout_info = - get_layout_info(&cell.item, component, layout_tree, window); - fill_layout_info_constraints( - &mut layout_info, - &cell.item.constraints, - &expr_eval, - ); - let rect = cell.item.rect(); - - GridLayoutCellData { - x: assume_property_f32(&rect.x_reference), - y: assume_property_f32(&rect.y_reference), - width: assume_property_f32(&rect.width_reference), - height: assume_property_f32(&rect.height_reference), - col: cell.col, - row: cell.row, - colspan: cell.colspan, - rowspan: cell.rowspan, - constraint: layout_info, - } - }) - .collect(); - let spacing = grid_layout.geometry.spacing.as_ref().map_or(0., expr_eval); - let padding = Padding { - left: grid_layout.geometry.padding.left.as_ref().map_or(0., expr_eval), - right: grid_layout.geometry.padding.right.as_ref().map_or(0., expr_eval), - top: grid_layout.geometry.padding.top.as_ref().map_or(0., expr_eval), - bottom: grid_layout.geometry.padding.bottom.as_ref().map_or(0., expr_eval), - }; - layout_tree.push( - LayoutWithCells { geometry: &grid_layout.geometry, cells, spacing, padding }.into(), - ); - } - Layout::BoxLayout(box_layout) => { - let mut make_box_layout_cell_data = |cell| { - let mut layout_info = get_layout_info(cell, component, layout_tree, window); - fill_layout_info_constraints(&mut layout_info, &cell.constraints, &expr_eval); - let rect = cell.rect(); - - BoxLayoutCellData { - x: assume_property_f32(&rect.x_reference), - y: assume_property_f32(&rect.y_reference), - width: assume_property_f32(&rect.width_reference), - height: assume_property_f32(&rect.height_reference), - constraint: layout_info, - } - }; - - let cells = box_layout - .elems - .iter() - .map(|item| match &item.element { - Some(elem) if elem.borrow().repeated.is_some() => { - generativity::make_guard!(guard); - let rep = get_repeater_by_name(component, elem.borrow().id.as_str(), guard); - rep.0.as_ref().ensure_updated(|| { - let window = component - .component_type - .window_offset - .apply(component.as_ref()) - .as_ref() - .unwrap() - .clone(); - let instance = - instantiate(rep.1.clone(), Some(component.borrow()), window); - instance.run_setup_code(); - instance - }); - - BoxLayoutCellTmpData::Repeater( - rep.0.as_ref().components_vec().into_iter().collect(), - ) - } - _ => BoxLayoutCellTmpData::Item(make_box_layout_cell_data(item)), - }) - .collect::>(); - - let spacing = box_layout.geometry.spacing.as_ref().map_or(0., expr_eval); - let padding = Padding { - left: box_layout.geometry.padding.left.as_ref().map_or(0., expr_eval), - right: box_layout.geometry.padding.right.as_ref().map_or(0., expr_eval), - top: box_layout.geometry.padding.top.as_ref().map_or(0., expr_eval), - bottom: box_layout.geometry.padding.bottom.as_ref().map_or(0., expr_eval), - }; - let alignment = box_layout - .geometry - .alignment - .as_ref() - .map(|nr| { - eval::load_property(component, &nr.element(), nr.name()) - .unwrap() - .try_into() - .unwrap_or_default() - }) - .unwrap_or_default(); - layout_tree.push(LayoutTreeItem::BoxLayout( - LayoutWithCells { geometry: &box_layout.geometry, cells, spacing, padding }, - box_layout.is_horizontal, - alignment, - )); - } - Layout::PathLayout(layout) => layout_tree.push(layout.into()), - } - layout_tree.last().unwrap() -} - -impl<'a> LayoutTreeItem<'a> { - fn solve(&self, instance_ref: InstanceRef) { - let resolve_prop_ref = |prop_ref: &Option| { - prop_ref.as_ref().map_or(0., |nr| { - eval::load_property(instance_ref, &nr.element(), nr.name()) - .unwrap() - .try_into() - .unwrap_or(0.) - }) - }; - - if let Some(geometry) = self.geometry() { - // Set the properties that depends on the constraints - if geometry.materialized_constraints.has_explicit_restrictions() { - let info = self.layout_info(); - let apply_materialized_constraint = |nr: &Option, c: f32| { - if let Some(nr) = nr { - let p = get_property_ptr(nr, instance_ref); - let p_ref = unsafe { &*(p as *const Property) }; - p_ref.set(c); - }; - }; - let c = &geometry.materialized_constraints; - apply_materialized_constraint(&c.minimum_width, info.min_width); - apply_materialized_constraint(&c.minimum_height, info.min_height); - apply_materialized_constraint(&c.maximum_width, info.max_width); - apply_materialized_constraint(&c.maximum_height, info.max_height); - apply_materialized_constraint(&c.vertical_stretch, info.vertical_stretch); - apply_materialized_constraint(&c.horizontal_stretch, info.horizontal_stretch); - } - } - - match self { - Self::GridLayout(grid_layout) => { - solve_grid_layout(&GridLayoutData { - width: resolve_prop_ref(&grid_layout.geometry.rect.width_reference), - height: resolve_prop_ref(&grid_layout.geometry.rect.height_reference), - x: resolve_prop_ref(&grid_layout.geometry.rect.x_reference), - y: resolve_prop_ref(&grid_layout.geometry.rect.y_reference), - spacing: grid_layout.spacing, - padding: &grid_layout.padding, - cells: Slice::from(grid_layout.cells.as_slice()), - }); - } - Self::BoxLayout(box_layout, is_horizontal, alignment) => { - let cells = BoxLayoutCellTmpData::into_cells(&box_layout.cells); - solve_box_layout( - &BoxLayoutData { - width: resolve_prop_ref(&box_layout.geometry.rect.width_reference), - height: resolve_prop_ref(&box_layout.geometry.rect.height_reference), - x: resolve_prop_ref(&box_layout.geometry.rect.x_reference), - y: resolve_prop_ref(&box_layout.geometry.rect.y_reference), - spacing: box_layout.spacing, - padding: &box_layout.padding, - cells: Slice::from(cells.as_slice()), - alignment: *alignment, - }, - *is_horizontal, - ); - } - Self::PathLayout(path_layout) => { - use sixtyfps_corelib::layout::*; - - let mut items = vec![]; - for elem in &path_layout.elements { - let mut push_layout_data = |elem: &ElementRc, instance_ref: InstanceRef| { - let item_info = - &instance_ref.component_type.items[elem.borrow().id.as_str()]; - let get_prop = |name| { - item_info.rtti.properties.get(name).map(|p| unsafe { - &*(instance_ref.as_ptr().add(item_info.offset).add(p.offset()) - as *const Property) - }) - }; - - let item = unsafe { item_info.item_from_component(instance_ref.as_ptr()) }; - let get_prop_value = |name| { - item_info - .rtti - .properties - .get(name) - .map(|p| p.get(item)) - .unwrap_or_default() - }; - items.push(PathLayoutItemData { - x: get_prop("x"), - y: get_prop("y"), - width: get_prop_value("width").try_into().unwrap_or_default(), - height: get_prop_value("height").try_into().unwrap_or_default(), - }); - }; - - if elem.borrow().repeated.is_none() { - push_layout_data(elem, instance_ref) - } else { - generativity::make_guard!(guard); - let repeater = - get_repeater_by_name(instance_ref, elem.borrow().id.as_str(), guard); - let vec = repeater.0.components_vec(); - for sub_comp in vec.iter() { - generativity::make_guard!(guard); - push_layout_data( - &elem.borrow().base_type.as_component().root_element, - sub_comp.unerase(guard).borrow_instance(), - ) - } - } - } - - let path_elements = eval::convert_path( - &path_layout.path, - &mut eval::EvalLocalContext::from_component_instance(instance_ref), - ); - - solve_path_layout(&PathLayoutData { - items: Slice::from(items.as_slice()), - elements: &path_elements, - x: resolve_prop_ref(&path_layout.rect.x_reference), - y: resolve_prop_ref(&path_layout.rect.y_reference), - width: resolve_prop_ref(&path_layout.rect.width_reference), - height: resolve_prop_ref(&path_layout.rect.height_reference), - offset: resolve_prop_ref(&Some(path_layout.offset_reference.clone())), - }); - } - } - } -} - pub fn get_repeater_by_name<'a, 'id>( instance_ref: InstanceRef<'a, '_>, name: &str, @@ -1629,24 +1225,16 @@ extern "C" fn layout_info(component: ComponentRefPin) -> LayoutInfo { // This is fine since we can only be called with a component that with our vtable which is a ComponentDescription let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) }; - let component_layouts = instance_ref.component_type.original.layouts.borrow(); - let result = if let Some(idx) = component_layouts.main_layout { - let mut inverse_layout_tree = Default::default(); - collect_layouts_recursively( - &mut inverse_layout_tree, - &component_layouts[idx], - instance_ref, - &eval::window_ref(instance_ref).unwrap(), - ) - .layout_info() - } else { - instance_ref.root_item().as_ref().layouting_info(&eval::window_ref(instance_ref).unwrap()) - }; - if component_layouts.root_constraints.has_explicit_restrictions() { + let constraints = instance_ref.component_type.original.root_constraints.borrow(); + + let result = + instance_ref.root_item().as_ref().layouting_info(&eval::window_ref(instance_ref).unwrap()); + + if constraints.has_explicit_restrictions() { let mut info = LayoutInfo::default(); - fill_layout_info_constraints( + crate::eval_layout::fill_layout_info_constraints( &mut info, - &component_layouts.root_constraints, + &constraints, &|nr: &NamedReference| { eval::load_property(instance_ref, &nr.element(), nr.name()) .unwrap() @@ -1664,17 +1252,6 @@ extern "C" fn apply_layout(component: ComponentRefPin, _r: sixtyfps_corelib::gra generativity::make_guard!(guard); // This is fine since we can only be called with a component that with our vtable which is a ComponentDescription let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) }; - let window = eval::window_ref(instance_ref).unwrap(); - - instance_ref.component_type.original.layouts.borrow().iter().for_each(|layout| { - let mut inverse_layout_tree = Vec::new(); - - collect_layouts_recursively(&mut inverse_layout_tree, &layout, instance_ref, &window); - - inverse_layout_tree.iter().rev().for_each(|layout| { - layout.solve(instance_ref); - }); - }); for rep_in_comp in &instance_ref.component_type.repeater { generativity::make_guard!(g); diff --git a/sixtyfps_runtime/interpreter/eval.rs b/sixtyfps_runtime/interpreter/eval.rs index 8690c1ba1..50df09296 100644 --- a/sixtyfps_runtime/interpreter/eval.rs +++ b/sixtyfps_runtime/interpreter/eval.rs @@ -89,7 +89,7 @@ impl> ErasedCallbackIn impl corelib::rtti::ValueType for Value {} #[derive(Copy, Clone)] -enum ComponentInstance<'a, 'id> { +pub(crate) enum ComponentInstance<'a, 'id> { InstanceRef(InstanceRef<'a, 'id>), GlobalComponent(&'a Pin>), } @@ -98,7 +98,7 @@ enum ComponentInstance<'a, 'id> { pub struct EvalLocalContext<'a, 'id> { local_variables: HashMap, function_arguments: Vec, - component_instance: ComponentInstance<'a, 'id>, + pub(crate) component_instance: ComponentInstance<'a, 'id>, /// When Some, a return statement was executed and one must stop evaluating return_value: Option, } @@ -539,6 +539,17 @@ pub fn eval_expression(e: &Expression, local_context: &mut EvalLocalContext) -> } local_context.return_value.clone().unwrap() } + + Expression::LayoutCacheAccess { layout_cache_prop, index } => { + let cache = load_property_helper(local_context.component_instance, &layout_cache_prop.element(), layout_cache_prop.name()).unwrap(); + if let Value::LayoutCache(cache) = cache { + Value::Number(cache[*index].into()) + } else { + panic!("invalid layout cache") + } + } + Expression::ComputeLayoutInfo(lay) => crate::eval_layout::compute_layout_info(lay, local_context), + Expression::SolveLayout(lay) => crate::eval_layout::solve_layout(lay, local_context), } } diff --git a/sixtyfps_runtime/interpreter/eval_layout.rs b/sixtyfps_runtime/interpreter/eval_layout.rs new file mode 100644 index 000000000..2704b2444 --- /dev/null +++ b/sixtyfps_runtime/interpreter/eval_layout.rs @@ -0,0 +1,175 @@ +/* LICENSE BEGIN + This file is part of the SixtyFPS Project -- https://sixtyfps.io + Copyright (c) 2020 Olivier Goffart + Copyright (c) 2020 Simon Hausmann + + SPDX-License-Identifier: GPL-3.0-only + This file is also available under commercial licensing terms. + Please contact info@sixtyfps.io for more information. +LICENSE END */ + +use crate::dynamic_component::InstanceRef; +use crate::eval::{self, ComponentInstance, EvalLocalContext}; +use crate::Value; +use sixtyfps_compilerlib::expression_tree::Expression; +use sixtyfps_compilerlib::langtype::Type; +use sixtyfps_compilerlib::layout::{Layout, LayoutConstraints, LayoutItem}; +use sixtyfps_compilerlib::namedreference::NamedReference; +use sixtyfps_corelib::layout as core_layout; +use sixtyfps_corelib::slice::Slice; +use sixtyfps_corelib::window::ComponentWindow; +use std::convert::TryInto; + +pub(crate) fn compute_layout_info(lay: &Layout, local_context: &mut EvalLocalContext) -> Value { + let component = match local_context.component_instance { + ComponentInstance::InstanceRef(c) => c, + ComponentInstance::GlobalComponent(_) => panic!("Cannot compute layout from a Global"), + }; + let expr_eval = |nr: &NamedReference| -> f32 { + eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap() + }; + match lay { + Layout::GridLayout(grid_layout) => { + let (cells, padding, spacing) = grid_layout_data(grid_layout, component, &expr_eval); + core_layout::grid_layout_info(&Slice::from(cells.as_slice()), spacing, &padding).into() + } + _ => todo!(), + } +} + +pub(crate) fn solve_layout(lay: &Layout, local_context: &mut EvalLocalContext) -> Value { + let component = match local_context.component_instance { + ComponentInstance::InstanceRef(c) => c, + ComponentInstance::GlobalComponent(_) => panic!("Cannot compute layout from a Global"), + }; + let expr_eval = |nr: &NamedReference| -> f32 { + eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap() + }; + + match lay { + Layout::GridLayout(grid_layout) => { + let (cells, padding, spacing) = grid_layout_data(grid_layout, component, &expr_eval); + + Value::LayoutCache(core_layout::solve_grid_layout(&core_layout::GridLayoutData { + width: grid_layout + .geometry + .rect + .width_reference + .as_ref() + .map(expr_eval) + .unwrap_or(0.), + height: grid_layout + .geometry + .rect + .height_reference + .as_ref() + .map(expr_eval) + .unwrap_or(0.), + x: 0., + y: 0., + spacing, + padding: &padding, + cells: Slice::from(cells.as_slice()), + })) + } + _ => todo!(), + } +} + +/// return the celldata, the padding, and the spacing of a grid layout +fn grid_layout_data( + grid_layout: &sixtyfps_compilerlib::layout::GridLayout, + component: InstanceRef, + expr_eval: &impl Fn(&NamedReference) -> f32, +) -> (Vec, core_layout::Padding, f32) { + let cells = grid_layout + .elems + .iter() + .map(|cell| { + let mut layout_info = + get_layout_info(&cell.item, component, &eval::window_ref(component).unwrap()); + fill_layout_info_constraints(&mut layout_info, &cell.item.constraints, &expr_eval); + core_layout::GridLayoutCellData { + col: cell.col, + row: cell.row, + colspan: cell.colspan, + rowspan: cell.rowspan, + constraint: layout_info, + } + }) + .collect::>(); + let spacing = grid_layout.geometry.spacing.as_ref().map_or(0., expr_eval); + let padding = core_layout::Padding { + left: grid_layout.geometry.padding.left.as_ref().map_or(0., expr_eval), + right: grid_layout.geometry.padding.right.as_ref().map_or(0., expr_eval), + top: grid_layout.geometry.padding.top.as_ref().map_or(0., expr_eval), + bottom: grid_layout.geometry.padding.bottom.as_ref().map_or(0., expr_eval), + }; + (cells, padding, spacing) +} + +pub(crate) fn fill_layout_info_constraints( + layout_info: &mut core_layout::LayoutInfo, + constraints: &LayoutConstraints, + expr_eval: &impl Fn(&NamedReference) -> f32, +) { + let is_percent = + |nr: &NamedReference| Expression::PropertyReference(nr.clone()).ty() == Type::Percent; + constraints.minimum_width.as_ref().map(|e| { + if !is_percent(e) { + layout_info.min_width = expr_eval(e) + } else { + layout_info.min_width_percent = expr_eval(e) + } + }); + constraints.maximum_width.as_ref().map(|e| { + if !is_percent(e) { + layout_info.max_width = expr_eval(e) + } else { + layout_info.max_width_percent = expr_eval(e) + } + }); + constraints.minimum_height.as_ref().map(|e| { + if !is_percent(e) { + layout_info.min_height = expr_eval(e) + } else { + layout_info.min_height_percent = expr_eval(e) + } + }); + constraints.maximum_height.as_ref().map(|e| { + if !is_percent(e) { + layout_info.max_height = expr_eval(e) + } else { + layout_info.max_height_percent = expr_eval(e) + } + }); + constraints.preferred_width.as_ref().map(|e| { + layout_info.preferred_width = expr_eval(e); + }); + constraints.preferred_height.as_ref().map(|e| { + layout_info.preferred_height = expr_eval(e); + }); + constraints.horizontal_stretch.as_ref().map(|e| layout_info.horizontal_stretch = expr_eval(e)); + constraints.vertical_stretch.as_ref().map(|e| layout_info.vertical_stretch = expr_eval(e)); +} + +fn get_layout_info<'a, 'b>( + item: &'a LayoutItem, + component: InstanceRef<'a, '_>, + window: &ComponentWindow, +) -> core_layout::LayoutInfo { + let elem = item.element.borrow(); + let item = &component + .component_type + .items + .get(elem.id.as_str()) + .unwrap_or_else(|| panic!("Internal error: Item {} not found", elem.id)); + let r = unsafe { item.item_from_component(component.as_ptr()).as_ref().layouting_info(window) }; + if let Some(nr) = &elem.layout_info_prop { + r.merge( + &eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap(), + ) + } else { + r + } +} diff --git a/sixtyfps_runtime/interpreter/global_component.rs b/sixtyfps_runtime/interpreter/global_component.rs index 9439870cd..d3ab57d35 100644 --- a/sixtyfps_runtime/interpreter/global_component.rs +++ b/sixtyfps_runtime/interpreter/global_component.rs @@ -154,6 +154,7 @@ fn default_value_for_type(ty: &Type) -> Value { Type::Model => Value::Void, Type::UnitProduct(_) => Value::Number(0.), Type::PathElements => Value::PathElements(Default::default()), + Type::LayoutCache => Value::LayoutCache(Default::default()), Type::ElementReference | Type::Builtin(_) | Type::Component(_) diff --git a/sixtyfps_runtime/interpreter/lib.rs b/sixtyfps_runtime/interpreter/lib.rs index f1f95e8bd..748fab72f 100644 --- a/sixtyfps_runtime/interpreter/lib.rs +++ b/sixtyfps_runtime/interpreter/lib.rs @@ -78,6 +78,7 @@ mod api; mod dynamic_component; mod dynamic_type; mod eval; +mod eval_layout; mod global_component; mod value_model; diff --git a/tests/cases/layout/grid.60 b/tests/cases/layout/grid.60 index 7960696c6..f4947fdb4 100644 --- a/tests/cases/layout/grid.60 +++ b/tests/cases/layout/grid.60 @@ -20,7 +20,7 @@ TestCase := Rectangle { rowspan: 1; } rect2 := Rectangle { - background: red; + background: blue; } }