From d395e92f3f554ec31758b4edd65e37a07fa097bb Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Wed, 15 Dec 2021 14:30:05 +0100 Subject: [PATCH] More work on LLR generate the actual tree and support a bunch more of expressions --- sixtyfps_compiler/expression_tree.rs | 2 +- sixtyfps_compiler/llr/expression.rs | 146 +++-- sixtyfps_compiler/llr/item_tree.rs | 50 +- sixtyfps_compiler/llr/lower_expression.rs | 685 ++++++++++++++++++-- sixtyfps_compiler/llr/lower_to_item_tree.rs | 153 ++++- sixtyfps_compiler/typeregister.rs | 26 +- 6 files changed, 907 insertions(+), 155 deletions(-) diff --git a/sixtyfps_compiler/expression_tree.rs b/sixtyfps_compiler/expression_tree.rs index 730fac6e5..e7f613d44 100644 --- a/sixtyfps_compiler/expression_tree.rs +++ b/sixtyfps_compiler/expression_tree.rs @@ -14,7 +14,7 @@ use std::rc::{Rc, Weak}; // FIXME remove the pub pub use crate::namedreference::NamedReference; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] /// A function built into the run-time pub enum BuiltinFunction { GetWindowScaleFactor, diff --git a/sixtyfps_compiler/llr/expression.rs b/sixtyfps_compiler/llr/expression.rs index 46c3a46a7..f3dc71826 100644 --- a/sixtyfps_compiler/llr/expression.rs +++ b/sixtyfps_compiler/llr/expression.rs @@ -1,11 +1,11 @@ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: (GPL-3.0-only OR LicenseRef-SixtyFPS-commercial) -use std::collections::HashMap; - -use crate::langtype::Type; - use super::PropertyReference; +use crate::expression_tree::{BuiltinFunction, OperatorClass}; +use crate::langtype::Type; +use itertools::Either; +use std::collections::HashMap; #[derive(Debug, Clone)] pub enum Expression { @@ -64,17 +64,32 @@ pub enum Expression { CodeBlock(Vec), /// A function call - FunctionCall { - function: Box, + BuiltinFunctionCall { + function: BuiltinFunction, + arguments: Vec, + }, + CallBackCall { + callback: PropertyReference, arguments: Vec, }, - /// A SelfAssignment or an Assignment. When op is '=' this is a signal assignment. - SelfAssignment { - lhs: Box, - rhs: Box, - /// '+', '-', '/', '*', or '=' - op: char, + /// A BuiltinFunctionCall, but the function is not yet in the `BuiltinFunction` enum + /// TODO: merge in BuiltinFunctionCall + ExtraBuiltinFunctionCall { + function: String, + arguments: Vec, + }, + + /// An assignment of a value to a property + PropertyAssignment { + property: PropertyReference, + value: Box, + }, + /// an assignment of a value to the model data + ModelDataAssignment { + // how deep in the parent hierarchy we go + level: usize, + value: Box, }, BinaryExpression { @@ -109,9 +124,7 @@ pub enum Expression { values: HashMap, }, - PathElements { - elements: crate::expression_tree::PathEvents, - }, + PathEvents(crate::expression_tree::PathEvents), EasingCurve(crate::expression_tree::EasingCurve), @@ -132,55 +145,15 @@ pub enum Expression { /// So this looks like `layout_cache_prop[layout_cache_prop[index] + repeater_index]` repeater_index: Option>, }, - //-/ Compute the LayoutInfo for the given layout. - //TODO - //ComputeLayoutInfo(Layout), - //SolveLayout(Layout), -} - -/* -#[derive(Debug, Default, Clone)] -pub struct LayoutConstraints { - pub min: Option, - pub max: Option, - pub preferred: Option, - pub stretch: Option, - pub fixed: bool, -} - -#[derive(Debug, Clone)] -pub struct LayoutItem { - pos: Option, - size: Option, -} - -pub struct LayoutGeometry {} - -/// An element in a GridLayout -#[derive(Debug, Clone)] -pub struct GridLayoutElement { - pub col: u16, - pub row: u16, - pub colspan: u16, - pub rowspan: u16, - pub item_horiz: LayoutItem, - pub item_vert: LayoutItem, -} - -#[derive(Debug, Clone)] -pub enum Layout { - Grid { - /// All the elements will be layout within that element. - elems: Vec, - - geometry: LayoutGeometry, - - /// When this GridLyout is actually the layout of a Dialog, then the cells start with all the buttons, - /// and this variable contains their roles. The string is actually one of the values from the sixtyfps_corelib::layout::DialogButtonRole - dialog_button_roles: Option>, + /// Generate the array of BoxLayoutCellData form elements + BoxLayoutCellDataArray { + /// Either an expression of type BoxLayoutCellData, or an index to the repeater + elements: Vec>, + /// The name for the local variable that stores the repeater indices + /// In other word, this expression has side effect and change that + repeater_indices: Option, }, } -*/ impl Expression { pub fn default_value_for_type(ty: &Type) -> Option { @@ -213,7 +186,7 @@ impl Expression { }, Type::Bool => Expression::BoolLiteral(false), Type::Model => return None, - Type::PathElements => Expression::PathElements { elements: Default::default() }, + Type::PathElements => Expression::PathEvents(Default::default()), Type::Array(element_ty) => { Expression::Array { element_ty: (**element_ty).clone(), values: vec![] } } @@ -234,4 +207,51 @@ impl Expression { } }) } + + pub fn ty(&self) -> Type { + match self { + Self::StringLiteral(_) => Type::String, + Self::NumberLiteral(_) => Type::Float32, + Self::BoolLiteral(_) => Type::Bool, + Self::PropertyReference(_) => todo!(), + Self::FunctionParameterReference { .. } => todo!(), + Self::StoreLocalVariable { .. } => Type::Void, + Self::ReadLocalVariable { ty, .. } => ty.clone(), + Self::StructFieldAccess { base, name } => match base.ty() { + Type::Struct { fields, .. } => fields[name].clone(), + _ => unreachable!(), + }, + Self::Cast { to, .. } => to.clone(), + Self::CodeBlock(sub) => sub.last().map_or(Type::Void, |e| e.ty()), + Self::BuiltinFunctionCall { function, .. } => match function.ty() { + Type::Function { return_type, .. } => *return_type, + _ => unreachable!(), + }, + Self::CallBackCall { .. } => todo!(), + Self::ExtraBuiltinFunctionCall { .. } => todo!(), + Self::PropertyAssignment { .. } => Type::Void, + Self::ModelDataAssignment { .. } => Type::Void, + Self::BinaryExpression { lhs, rhs: _, op } => { + if crate::expression_tree::operator_class(*op) != OperatorClass::ArithmeticOp { + Type::Bool + } else { + lhs.ty() + } + } + Self::UnaryOp { sub, .. } => sub.ty(), + Self::ImageReference { .. } => Type::Image, + Self::Condition { true_expr, .. } => true_expr.ty(), + Self::Array { element_ty, .. } => Type::Array(element_ty.clone().into()), + Self::Struct { ty, .. } => ty.clone(), + Self::PathEvents { .. } => todo!(), + Self::EasingCurve(_) => Type::Easing, + Self::LinearGradient { .. } => Type::Brush, + Self::EnumerationValue(e) => Type::Enumeration(e.enumeration.clone()), + Self::ReturnStatement(_) => Type::Invalid, + Self::LayoutCacheAccess { .. } => crate::layout::layout_info_type(), + Self::BoxLayoutCellDataArray { .. } => { + Type::Array(Box::new(crate::layout::layout_info_type())) + } + } + } } diff --git a/sixtyfps_compiler/llr/item_tree.rs b/sixtyfps_compiler/llr/item_tree.rs index ead240b60..4d0bc2301 100644 --- a/sixtyfps_compiler/llr/item_tree.rs +++ b/sixtyfps_compiler/llr/item_tree.rs @@ -1,31 +1,34 @@ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: (GPL-3.0-only OR LicenseRef-SixtyFPS-commercial) -use std::collections::HashMap; +use super::Expression; +use crate::langtype::{NativeClass, Type}; +use std::collections::BTreeMap; use std::num::NonZeroUsize; use std::rc::Rc; -use crate::langtype::Type; - -use super::Expression; - // Index in the `SubComponent::properties` pub type PropertyIndex = usize; +#[derive(Debug)] pub enum Animation { /// The expression is a Struct with the animation fields Static(Expression), Transition(Expression), } + +#[derive(Debug)] pub struct BindingExpression { pub expression: Expression, pub animation: Option, } +#[derive(Debug)] pub struct GlobalComponent { pub name: String, pub properties: Vec, pub init_values: Vec>, + pub const_properties: Vec, } /// a Reference to a property, in the context of a SubComponent @@ -41,12 +44,14 @@ pub enum PropertyReference { Global { global_index: usize, property_index: usize }, } +#[derive(Debug)] pub struct Property { pub name: String, pub ty: Type, //pub binding: Option, } +#[derive(Debug)] pub struct RepeatedElement { pub model: Expression, /// Within the sub_tree's root component @@ -56,41 +61,52 @@ pub struct RepeatedElement { pub sub_tree: ItemTree, } -pub struct ItemType { - // cpp_name: String, -// rust_name: String, -// cpp_init_function: String, -// mouse_function: String, -// extra_data_type: String, -} - +#[derive(Debug)] pub struct Item { - pub ty: Rc, + pub ty: Rc, + pub name: String, + /// When this is true, this item does not need to be created because it is + /// already in the flickable. + /// The Item::name is the same as the flickable, and ty is Rectangle + pub is_flickable_viewport: bool, } +#[derive(Debug)] pub struct TreeNode { pub sub_component_path: Vec, pub item_index: usize, + pub repeated: bool, pub children: Vec, } +#[derive(Debug)] pub struct SubComponent { pub name: String, pub properties: Vec, pub items: Vec, pub repeated: Vec, + pub popup_windows: Vec, pub sub_components: Vec, pub property_init: Vec<(PropertyReference, BindingExpression)>, pub two_way_bindings: Vec<(PropertyReference, PropertyReference)>, + pub const_properties: Vec, } pub struct SubComponentInstance { pub ty: Rc, + pub name: String, //pub property_values: Vec<(PropertyReference, BindingExpression)>, } +impl std::fmt::Debug for SubComponentInstance { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.ty.name) + } +} + +#[derive(Debug)] pub struct ItemTree { - pub root: SubComponentInstance, + pub root: SubComponent, pub tree: TreeNode, /// This tree has a parent. e.g: it is a Repeater or a PopupMenu whose property can access /// the parent ItemTree. @@ -98,8 +114,10 @@ pub struct ItemTree { pub parent_context: Option, } +#[derive(Debug)] pub struct PublicComponent { + pub public_properties: BTreeMap, pub item_tree: ItemTree, - pub sub_components: HashMap>, + pub sub_components: BTreeMap>, pub globals: Vec, } diff --git a/sixtyfps_compiler/llr/lower_expression.rs b/sixtyfps_compiler/llr/lower_expression.rs index 7bb1948e3..51ad5d234 100644 --- a/sixtyfps_compiler/llr/lower_expression.rs +++ b/sixtyfps_compiler/llr/lower_expression.rs @@ -2,13 +2,17 @@ // SPDX-License-Identifier: (GPL-3.0-only OR LicenseRef-SixtyFPS-commercial) use std::cell::RefCell; +use std::collections::{BTreeMap, HashMap}; use std::num::NonZeroUsize; use std::rc::{Rc, Weak}; -use super::lower_to_item_tree::{LoweredSubComponentMapping, LoweringState}; +use itertools::Either; + +use super::lower_to_item_tree::{LoweredElement, LoweredSubComponentMapping, LoweringState}; use super::{Animation, PropertyReference}; -use crate::expression_tree::Expression as tree_Expression; -use crate::langtype::Type; +use crate::expression_tree::{BuiltinFunction, Expression as tree_Expression}; +use crate::langtype::{EnumerationValue, Type}; +use crate::layout::Orientation; use crate::llr::Expression as llr_Expression; use crate::namedreference::NamedReference; use crate::object_tree::{Element, ElementRc, PropertyAnimation}; @@ -21,11 +25,7 @@ pub struct ExpressionContext<'a> { } impl ExpressionContext<'_> { - pub fn map_property_reference( - &self, - from: &NamedReference, - state: &LoweringState, - ) -> Option { + pub fn map_property_reference(&self, from: &NamedReference) -> Option { let element = from.element(); let enclosing = &element.borrow().enclosing_component.upgrade().unwrap(); if !enclosing.is_global() { @@ -36,13 +36,15 @@ impl ExpressionContext<'_> { level += 1; } if let Some(level) = NonZeroUsize::new(level) { - PropertyReference::InParent { + return Some(PropertyReference::InParent { level, - parent_reference: Box::new(map.mapping.map_property_reference(from, state)?), - }; + parent_reference: Box::new( + map.mapping.map_property_reference(from, self.state)?, + ), + }); } } - self.mapping.map_property_reference(from, state) + self.mapping.map_property_reference(from, self.state) } } @@ -52,20 +54,34 @@ pub fn lower_expression( ) -> Option { match expression { tree_Expression::Invalid => None, - tree_Expression::Uncompiled(_) => None, + tree_Expression::Uncompiled(_) => panic!(), tree_Expression::StringLiteral(s) => Some(llr_Expression::StringLiteral(s.clone())), - tree_Expression::NumberLiteral(n, _) => Some(llr_Expression::NumberLiteral(*n)), + tree_Expression::NumberLiteral(n, unit) => { + Some(llr_Expression::NumberLiteral(unit.normalize(*n))) + } tree_Expression::BoolLiteral(b) => Some(llr_Expression::BoolLiteral(*b)), - tree_Expression::CallbackReference(nr) => Some(llr_Expression::PropertyReference( - ctx.mapping.map_property_reference(nr, ctx.state)?, - )), - tree_Expression::PropertyReference(nr) => Some(llr_Expression::PropertyReference( - ctx.mapping.map_property_reference(nr, ctx.state)?, - )), - tree_Expression::BuiltinFunctionReference(_, _) => todo!(), - tree_Expression::MemberFunction { .. } => None, - tree_Expression::BuiltinMacroReference(_, _) => None, - tree_Expression::ElementReference(_) => todo!(), + tree_Expression::CallbackReference(nr) => { + Some(llr_Expression::PropertyReference(ctx.map_property_reference(nr)?)) + } + tree_Expression::PropertyReference(nr) => { + Some(llr_Expression::PropertyReference(ctx.map_property_reference(nr)?)) + } + tree_Expression::BuiltinFunctionReference(_, _) => panic!(), + tree_Expression::MemberFunction { .. } => panic!(), + tree_Expression::BuiltinMacroReference(_, _) => panic!(), + tree_Expression::ElementReference(e) => { + // We map an element reference to a reference to the property "" inside that native item + Some(llr_Expression::PropertyReference( + ctx.mapping + .map_property_reference( + &NamedReference::new(&e.upgrade().unwrap(), ""), + ctx.state, + ) + .expect( + "this should be a reference to a native item and it should always exist", + ), + )) + } tree_Expression::RepeaterIndexReference { element } => { repeater_special_property(element, ctx.component, 1) } @@ -97,20 +113,26 @@ pub fn lower_expression( tree_Expression::CodeBlock(expr) => Some(llr_Expression::CodeBlock( expr.iter().map(|e| lower_expression(e, ctx)).collect::>()?, )), - tree_Expression::FunctionCall { function, arguments, .. } => { - Some(llr_Expression::FunctionCall { - function: Box::new(lower_expression(function, ctx)?), - arguments: arguments - .iter() - .map(|e| lower_expression(e, ctx)) - .collect::>()?, - }) - } - tree_Expression::SelfAssignment { lhs, rhs, op } => Some(llr_Expression::SelfAssignment { - lhs: Box::new(lower_expression(lhs, ctx)?), - rhs: Box::new(lower_expression(rhs, ctx)?), - op: *op, - }), + tree_Expression::FunctionCall { function, arguments, .. } => match &**function { + tree_Expression::BuiltinFunctionReference(BuiltinFunction::ShowPopupWindow, _) => { + lower_show_popup(arguments, ctx) + } + tree_Expression::BuiltinFunctionReference(f, _) => { + let arguments = + arguments.iter().map(|e| lower_expression(e, ctx)).collect::>()?; + Some(llr_Expression::BuiltinFunctionCall { function: *f, arguments }) + } + tree_Expression::CallbackReference(nr) => { + let arguments = + arguments.iter().map(|e| lower_expression(e, ctx)).collect::>()?; + Some(llr_Expression::CallBackCall { + callback: ctx.map_property_reference(nr)?, + arguments, + }) + } + _ => panic!("not calling a function"), + }, + tree_Expression::SelfAssignment { lhs, rhs, op } => lower_assignment(lhs, rhs, *op, ctx), tree_Expression::BinaryExpression { lhs, rhs, op } => { Some(llr_Expression::BinaryExpression { lhs: Box::new(lower_expression(lhs, ctx)?), @@ -142,10 +164,7 @@ pub fn lower_expression( .map(|(s, e)| Some((s.clone(), lower_expression(e, ctx)?))) .collect::>()?, }), - tree_Expression::PathElements { elements } => match elements { - crate::expression_tree::Path::Elements(_) => todo!(), - crate::expression_tree::Path::Events(_) => todo!(), - }, + tree_Expression::PathElements { elements } => compile_path(elements, ctx), tree_Expression::EasingCurve(x) => Some(llr_Expression::EasingCurve(x.clone())), tree_Expression::LinearGradient { angle, stops } => Some(llr_Expression::LinearGradient { angle: Box::new(lower_expression(angle, ctx)?), @@ -170,8 +189,98 @@ pub fn lower_expression( .map(Box::new), }) } - tree_Expression::ComputeLayoutInfo(_, _) => todo!(), - tree_Expression::SolveLayout(_, _) => todo!(), + tree_Expression::ComputeLayoutInfo(l, o) => compute_layout_info(l, *o, ctx), + tree_Expression::SolveLayout(l, o) => solve_layout(l, *o, ctx), + } +} + +fn lower_assignment( + lhs: &tree_Expression, + rhs: &tree_Expression, + op: char, + ctx: &ExpressionContext, +) -> Option { + match lhs { + tree_Expression::PropertyReference(nr) => { + let rhs = lower_expression(rhs, ctx)?; + let property = ctx.map_property_reference(nr)?; + let value = if op == '=' { + rhs + } else { + llr_Expression::BinaryExpression { + lhs: llr_Expression::PropertyReference(property.clone()).into(), + rhs: rhs.into(), + op, + } + } + .into(); + Some(llr_Expression::PropertyAssignment { property, value }) + } + tree_Expression::StructFieldAccess { base, name } => { + let ty = base.ty(); + + static COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); + let unique_name = format!( + "struct_assignment{}", + COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + ); + let s = tree_Expression::StoreLocalVariable { + name: unique_name.clone(), + value: base.clone(), + }; + let lower_base = + tree_Expression::ReadLocalVariable { name: unique_name, ty: ty.clone() }; + let mut values = HashMap::new(); + match &ty { + Type::Struct { fields, .. } => { + for (field, _) in fields { + let e = if field != name { + tree_Expression::StructFieldAccess { + base: lower_base.clone().into(), + name: field.clone(), + } + } else if op == '=' { + rhs.clone() + } else { + tree_Expression::BinaryExpression { + lhs: tree_Expression::StructFieldAccess { + base: lower_base.clone().into(), + name: field.clone(), + } + .into(), + rhs: Box::new(rhs.clone()), + op, + } + }; + values.insert(field.clone(), e); + } + } + _ => unreachable!(), + } + let new_value = + tree_Expression::CodeBlock(vec![s, tree_Expression::Struct { ty, values }]); + lower_assignment(base, &new_value, '=', ctx) + } + tree_Expression::RepeaterModelReference { element } => { + let rhs = lower_expression(rhs, ctx)?; + let prop = repeater_special_property(element, ctx.component, 0)?; + + let level = match &prop { + llr_Expression::PropertyReference(PropertyReference::InParent { + level, .. + }) => (*level).into(), + _ => 0, + }; + + let value = Box::new(if op == '=' { + rhs + } else { + llr_Expression::BinaryExpression { lhs: prop.into(), rhs: rhs.into(), op } + }); + + Some(llr_Expression::ModelDataAssignment { level, value }) + } + _ => panic!("not a rvalue"), } } @@ -201,6 +310,39 @@ fn repeater_special_property( Some(llr_Expression::PropertyReference(r)) } +fn lower_show_popup(args: &[tree_Expression], ctx: &ExpressionContext) -> Option { + if let [tree_Expression::ElementReference(e)] = args { + let popup_window = e.upgrade().unwrap(); + let pop_comp = popup_window.borrow().enclosing_component.upgrade().unwrap(); + let parent_component = pop_comp + .parent_element + .upgrade() + .unwrap() + .borrow() + .enclosing_component + .upgrade() + .unwrap(); + let popup_list = parent_component.popup_windows.borrow(); + let (popup_index, popup) = popup_list + .iter() + .enumerate() + .find(|(_, p)| Rc::ptr_eq(&p.component, &pop_comp)) + .unwrap(); + let x = llr_Expression::PropertyReference(ctx.map_property_reference(&popup.x)?); + let y = llr_Expression::PropertyReference(ctx.map_property_reference(&popup.y)?); + let item_ref = lower_expression( + &tree_Expression::ElementReference(Rc::downgrade(&popup.parent_element)), + ctx, + )?; + Some(llr_Expression::BuiltinFunctionCall { + function: BuiltinFunction::ShowPopupWindow, + arguments: vec![llr_Expression::NumberLiteral(popup_index as _), x, y, item_ref], + }) + } else { + panic!("invalid arguments to ShowPopupWindow"); + } +} + pub fn lower_animation(a: &PropertyAnimation, ctx: &ExpressionContext<'_>) -> Option { fn lower_animation_element( a: &ElementRc, @@ -256,3 +398,458 @@ pub fn lower_animation(a: &PropertyAnimation, ctx: &ExpressionContext<'_>) -> Op } } } + +fn compute_layout_info( + l: &crate::layout::Layout, + o: Orientation, + ctx: &ExpressionContext, +) -> Option { + match l { + 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)?; + Some(llr_Expression::ExtraBuiltinFunctionCall { + function: "grid_layout_info".into(), + arguments: vec![cells, spacing, padding], + }) + } + crate::layout::Layout::BoxLayout(layout) => { + let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx)?; + let (cells, alignment) = box_layout_data(layout, o, ctx, None)?; + if o == layout.orientation { + Some(llr_Expression::ExtraBuiltinFunctionCall { + function: "box_layout_info".into(), + arguments: vec![cells, spacing, padding, alignment], + }) + } else { + Some(llr_Expression::ExtraBuiltinFunctionCall { + function: "box_layout_info_ortho".into(), + arguments: vec![cells, padding], + }) + } + } + crate::layout::Layout::PathLayout(_) => unimplemented!(), + } +} + +fn solve_layout( + l: &crate::layout::Layout, + o: Orientation, + ctx: &ExpressionContext, +) -> Option { + match l { + 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 size = layout_geometry_size(&layout.geometry.rect, o, ctx)?; + if let (Some(button_roles), Orientation::Horizontal) = (&layout.dialog_button_roles, o) + { + let cells_ty = cells.ty(); + let e = crate::typeregister::DIALOG_BUTTON_ROLE_ENUM.with(|e| e.clone()); + let roles = button_roles + .iter() + .map(|r| { + llr_Expression::EnumerationValue(EnumerationValue { + value: e.values.iter().position(|x| x == r).unwrap() as _, + enumeration: e.clone(), + }) + }) + .collect(); + Some(llr_Expression::CodeBlock(vec![ + llr_Expression::StoreLocalVariable { + name: "cells".into(), + value: Box::new(cells), + }, + llr_Expression::ExtraBuiltinFunctionCall { + function: "reorder_dialog_button_layout".into(), + arguments: vec![ + llr_Expression::ReadLocalVariable { + name: "cells".into(), + ty: cells_ty.clone(), + }, + llr_Expression::Array { + element_ty: Type::Enumeration(e), + values: roles, + }, + ], + }, + llr_Expression::ExtraBuiltinFunctionCall { + function: "solve_grid_layout".into(), + arguments: vec![make_struct( + "GridLayoutData".into(), + [ + ("size", Type::Float32, size), + ("spacing", Type::Float32, spacing), + ("padding", padding.ty(), padding), + ( + "cells", + cells_ty.clone(), + llr_Expression::ReadLocalVariable { + name: "cells".into(), + ty: cells_ty, + }, + ), + ], + )], + }, + ])) + } else { + Some(llr_Expression::ExtraBuiltinFunctionCall { + function: "solve_grid_layout".into(), + arguments: vec![make_struct( + "GridLayoutData".into(), + [ + ("size", Type::Float32, size), + ("spacing", Type::Float32, spacing), + ("padding", padding.ty(), padding), + ("cells", cells.ty(), cells), + ], + )], + }) + } + } + crate::layout::Layout::BoxLayout(layout) => { + let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx)?; + let mut repeated_indices = String::new(); + let (cells, alignment) = box_layout_data(layout, o, ctx, Some(&mut repeated_indices))?; + let size = layout_geometry_size(&layout.geometry.rect, o, ctx)?; + let data = make_struct( + "BoxLayoutData".into(), + [ + ("size", Type::Float32, size), + ("spacing", Type::Float32, spacing), + ("padding", padding.ty(), padding), + ( + "alignment", + crate::typeregister::LAYOUT_ALIGNMENT_ENUM + .with(|e| Type::Enumeration(e.clone())), + alignment, + ), + ("cells", cells.ty(), cells), + ], + ); + if repeated_indices.is_empty() { + Some(llr_Expression::ExtraBuiltinFunctionCall { + function: "solve_grid_layout".into(), + arguments: vec![ + data, + llr_Expression::Array { element_ty: Type::Int32, values: vec![] }, + ], + }) + } else { + Some(llr_Expression::CodeBlock(vec![ + llr_Expression::StoreLocalVariable { + name: repeated_indices.clone(), + value: llr_Expression::Array { element_ty: Type::Int32, values: vec![] } + .into(), + }, + llr_Expression::ExtraBuiltinFunctionCall { + function: "solve_grid_layout".into(), + arguments: vec![ + data, + llr_Expression::ReadLocalVariable { + name: repeated_indices, + ty: Type::Array(Type::Int32.into()), + }, + ], + }, + ])) + } + } + crate::layout::Layout::PathLayout(layout) => { + let width = layout_geometry_size(&layout.rect, Orientation::Horizontal, ctx)?; + let height = layout_geometry_size(&layout.rect, Orientation::Vertical, ctx)?; + let elements = compile_path(&layout.path, ctx)?; + let offset = if let Some(expr) = &layout.offset_reference { + llr_Expression::PropertyReference(ctx.map_property_reference(expr)?) + } else { + llr_Expression::NumberLiteral(0.) + }; + + let count = layout.elements.len(); // FIXME! repeater + Some(llr_Expression::ExtraBuiltinFunctionCall { + function: "solve_path_layout".into(), + arguments: vec![make_struct( + "PathLayoutData".into(), + [ + ("width", Type::Float32, width), + ("height", Type::Float32, height), + ("x", Type::Float32, llr_Expression::NumberLiteral(0.)), + ("y", Type::Float32, llr_Expression::NumberLiteral(0.)), + ("elements", elements.ty(), elements), + ("offset", Type::Int32, offset), + ("item_count", Type::Int32, llr_Expression::NumberLiteral(count as _)), + ], + )], + }) + } + } +} + +fn box_layout_data( + layout: &crate::layout::BoxLayout, + orientation: Orientation, + ctx: &ExpressionContext, + repeater_indices: Option<&mut String>, +) -> Option<(llr_Expression, llr_Expression)> { + let alignment = if let Some(expr) = &layout.geometry.alignment { + llr_Expression::PropertyReference(ctx.map_property_reference(expr)?) + } else { + let e = crate::typeregister::LAYOUT_ALIGNMENT_ENUM.with(|e| e.clone()); + llr_Expression::EnumerationValue(EnumerationValue { + value: e.default_value, + enumeration: e, + }) + }; + + let repeater_count = + layout.elems.iter().filter(|i| i.element.borrow().repeated.is_some()).count(); + + let element_ty = Type::Struct { + fields: IntoIterator::into_iter([( + "constraint".to_string(), + crate::layout::layout_info_type(), + )]) + .collect(), + name: Some("BoxLayoutCellData".into()), + node: None, + }; + + if repeater_count == 0 { + let cells = llr_Expression::Array { + values: layout + .elems + .iter() + .map(|li| { + let layout_info = + get_layout_info(&li.element, ctx, &li.constraints, orientation).unwrap(); + make_struct( + "BoxLayoutCellData".into(), + [("constraint", crate::layout::layout_info_type(), layout_info)], + ) + }) + .collect(), + element_ty, + }; + Some((cells, alignment)) + } else { + let mut elements = vec![]; + for item in &layout.elems { + if item.element.borrow().repeated.is_some() { + let repeater_index = + match ctx.mapping.element_mapping.get(&item.element.clone().into()).unwrap() { + LoweredElement::Repeated { repeated_index } => *repeated_index, + _ => panic!(), + }; + elements.push(Either::Right(repeater_index)) + } else { + let layout_info = + get_layout_info(&item.element, ctx, &item.constraints, orientation).unwrap(); + elements.push(Either::Left(make_struct( + "BoxLayoutCellData".into(), + [("constraint", crate::layout::layout_info_type(), layout_info)], + ))); + } + } + Some(( + llr_Expression::BoxLayoutCellDataArray { + elements, + repeater_indices: repeater_indices.map(|ri| { + *ri = "repeater_indices".into(); + (*ri).clone() + }), + }, + alignment, + )) + } +} + +fn grid_layout_cell_data( + layout: &crate::layout::GridLayout, + orientation: Orientation, + ctx: &ExpressionContext, +) -> Option { + Some(llr_Expression::Array { + element_ty: Type::Struct { + fields: IntoIterator::into_iter([ + ("col_or_row".to_string(), Type::Int32), + ("span".to_string(), Type::Int32), + ("constraint".to_string(), crate::layout::layout_info_type()), + ]) + .collect(), + name: Some("GridLayoutCellData".into()), + node: None, + }, + values: layout + .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) + .unwrap(); + + make_struct( + "BoxLayoutCellData".into(), + [ + ("constraint", crate::layout::layout_info_type(), layout_info), + ("col_or_row", Type::Int32, llr_Expression::NumberLiteral(col_or_row as _)), + ("span", Type::Int32, llr_Expression::NumberLiteral(span as _)), + ], + ) + }) + .collect(), + }) +} + +fn generate_layout_padding_and_spacing( + layout_geometry: &crate::layout::LayoutGeometry, + orientation: Orientation, + ctx: &ExpressionContext, +) -> Option<(llr_Expression, llr_Expression)> { + let padding_prop = |expr| { + Some(if let Some(expr) = expr { + llr_Expression::PropertyReference(ctx.map_property_reference(expr)?) + } else { + llr_Expression::NumberLiteral(0.) + }) + }; + let spacing = padding_prop(layout_geometry.spacing.as_ref())?; + let (begin, end) = layout_geometry.padding.begin_end(orientation); + + let padding = make_struct( + "Padding".into(), + [ + ("begin", Type::Float32, padding_prop(begin)?), + ("end", Type::Float32, padding_prop(end)?), + ], + ); + + Some((padding, spacing)) +} + +fn layout_geometry_size( + rect: &crate::layout::LayoutRect, + orientation: Orientation, + ctx: &ExpressionContext, +) -> Option { + match rect.size_reference(orientation) { + Some(nr) => Some(llr_Expression::PropertyReference(ctx.map_property_reference(nr)?)), + None => Some(llr_Expression::NumberLiteral(0.)), + } +} + +fn get_layout_info( + elem: &ElementRc, + ctx: &ExpressionContext, + constraints: &crate::layout::LayoutConstraints, + orientation: Orientation, +) -> Option { + let layout_info = if let Some(layout_info_prop) = &elem.borrow().layout_info_prop(orientation) { + llr_Expression::PropertyReference(ctx.map_property_reference(layout_info_prop)?) + } else { + lower_expression(&crate::layout::implicit_layout_info_call(elem, orientation), ctx)? + }; + + if constraints.has_explicit_restrictions() { + llr_Expression::StoreLocalVariable { + name: "layout_info".into(), + value: layout_info.into(), + }; + let ty = crate::layout::layout_info_type(); + let fields = match &ty { + Type::Struct { fields, .. } => fields, + _ => panic!(), + }; + let mut values = fields + .keys() + .map(|p| { + ( + p.clone(), + llr_Expression::StructFieldAccess { + base: llr_Expression::ReadLocalVariable { + name: "layout_info".into(), + ty: ty.clone(), + } + .into(), + name: p.clone(), + }, + ) + }) + .collect::>(); + + for (nr, s) in constraints.for_each_restrictions(orientation) { + values.insert( + s.into(), + llr_Expression::PropertyReference(ctx.map_property_reference(nr)?), + ); + } + Some(llr_Expression::Struct { ty, values }) + } else { + Some(layout_info) + } +} + +fn compile_path( + path: &crate::expression_tree::Path, + ctx: &ExpressionContext, +) -> Option { + match path { + crate::expression_tree::Path::Elements(elements) => { + let converted_elements = elements + .iter() + .map(|element| { + let ty = Type::Struct { + fields: element + .element_type + .properties + .iter() + .map(|(k, v)| (k.clone(), v.ty.clone())) + .collect(), + name: element.element_type.native_class.cpp_type.clone(), + node: None, + }; + + llr_Expression::Struct { + ty, + values: element + .bindings + .iter() + .map(|(property, expr)| { + ( + property.clone(), + lower_expression(&expr.borrow().expression, ctx).unwrap(), + ) + }) + .collect(), + } + }) + .collect(); + Some(llr_Expression::Cast { + from: llr_Expression::Array { + element_ty: Type::PathElements, + values: converted_elements, + } + .into(), + to: Type::PathElements, + }) + } + crate::expression_tree::Path::Events(events) => { + Some(llr_Expression::PathEvents(events.clone())) + } + } +} + +fn make_struct( + name: String, + it: impl IntoIterator, +) -> llr_Expression { + let mut fields = BTreeMap::::new(); + let mut values = HashMap::::new(); + for (name, ty, expr) in it { + fields.insert(name.to_string(), ty); + values.insert(name.to_string(), expr); + } + + llr_Expression::Struct { ty: Type::Struct { fields, name: Some(name), node: None }, values } +} diff --git a/sixtyfps_compiler/llr/lower_to_item_tree.rs b/sixtyfps_compiler/llr/lower_to_item_tree.rs index 08b15ed3c..6e7e01038 100644 --- a/sixtyfps_compiler/llr/lower_to_item_tree.rs +++ b/sixtyfps_compiler/llr/lower_to_item_tree.rs @@ -26,9 +26,23 @@ pub fn lower_to_item_tree(component: &Rc) -> PublicComponent { } let sc = lower_sub_component(component, &state, None); + let public_properties = component + .root_element + .borrow() + .property_declarations + .iter() + .filter(|(_, c)| c.expose_in_public_api) + .map(|(p, c)| { + let property_reference = sc + .mapping + .map_property_reference(&NamedReference::new(&component.root_element, p), &state) + .unwrap(); + (p.clone(), (c.property_type.clone(), property_reference)) + }) + .collect(); let item_tree = ItemTree { - root: SubComponentInstance { ty: sc.sub_component.clone() }, - tree: make_tree(&state, component), + tree: make_tree(&state, &component.root_element, &sc, &[]), + root: Rc::try_unwrap(sc.sub_component).unwrap(), parent_context: None, }; PublicComponent { @@ -39,6 +53,7 @@ pub fn lower_to_item_tree(component: &Rc) -> PublicComponent { .into_iter() .map(|(c, sc)| (c.id.clone(), sc.sub_component)) .collect(), + public_properties, } } @@ -51,13 +66,14 @@ pub struct LoweringState { #[derive(Debug, Clone)] pub enum LoweredElement { SubComponent { sub_component_index: usize }, - NativeItem { item_index: usize }, //property_mapping: HashMap, PropertyIndex)>, + NativeItem { item_index: usize }, + Repeated { repeated_index: usize }, } #[derive(Default, Debug, Clone)] pub struct LoweredSubComponentMapping { - element_mapping: HashMap, LoweredElement>, - property_mapping: HashMap, + pub element_mapping: HashMap, LoweredElement>, + pub property_mapping: HashMap, } impl LoweredSubComponentMapping { @@ -72,11 +88,20 @@ impl LoweredSubComponentMapping { if let Some(x) = state.global_properties.get(&from) { return Some(x.clone()); } - match self.element_mapping.get(&from.element().into())? { + let element = from.element(); + if let Some(alias) = element + .borrow() + .property_declarations + .get(from.name()) + .and_then(|x| x.is_alias.as_ref()) + { + return state.map_property_reference(alias); + } + match self.element_mapping.get(&element.clone().into())? { LoweredElement::SubComponent { sub_component_index } => { - if let Type::Component(base) = &from.element().borrow().base_type { + if let Type::Component(base) = &element.borrow().base_type { return Some(property_reference_within_sub_component( - state.map_property_reference(NamedReference::new( + state.map_property_reference(&NamedReference::new( &base.root_element, from.name(), ))?, @@ -92,6 +117,7 @@ impl LoweredSubComponentMapping { prop_name: from.name().into(), }); } + LoweredElement::Repeated { .. } => unreachable!(), } } } @@ -102,7 +128,7 @@ pub struct LoweredSubComponent { } impl LoweringState { - pub fn map_property_reference(&self, from: NamedReference) -> Option { + pub fn map_property_reference(&self, from: &NamedReference) -> Option { if let Some(x) = self.global_properties.get(&from) { return Some(x.clone()); } @@ -112,7 +138,7 @@ impl LoweringState { .sub_components .get(&element.borrow().enclosing_component.upgrade().unwrap().into())?; - enclosing.mapping.map_property_reference(&from, self) + enclosing.mapping.map_property_reference(from, self) } } @@ -160,9 +186,11 @@ fn lower_sub_component( properties: Default::default(), items: Default::default(), repeated: Default::default(), + popup_windows: Default::default(), sub_components: Default::default(), property_init: Default::default(), two_way_bindings: Default::default(), + const_properties: Default::default(), }; let mut mapping = LoweredSubComponentMapping::default(); let mut property_bindings = vec![]; @@ -201,6 +229,10 @@ fn lower_sub_component( } } if elem.repeated.is_some() { + mapping.element_mapping.insert( + element.clone().into(), + LoweredElement::Repeated { repeated_index: repeated.len() }, + ); repeated.push(element.clone()); return; } @@ -218,14 +250,16 @@ fn lower_sub_component( continue; } let prop_ref = state - .map_property_reference(NamedReference::new(&comp.root_element, p)) + .map_property_reference(&NamedReference::new(&comp.root_element, p)) .map(|x| property_reference_within_sub_component(x, sub_component_index)); property_bindings.push((prop_ref.unwrap(), b.borrow().clone())); } - sub_component.sub_components.push(SubComponentInstance { ty }); + sub_component + .sub_components + .push(SubComponentInstance { ty, name: elem.id.clone() }); } - Type::Native(_) => { + Type::Native(n) => { let item_index = sub_component.items.len(); mapping .element_mapping @@ -243,6 +277,11 @@ fn lower_sub_component( b.borrow().clone(), )); } + sub_component.items.push(Item { + ty: n.clone(), + name: elem.id.clone(), + is_flickable_viewport: elem.is_flickable_viewport, + }) } _ => unreachable!(), }; @@ -266,9 +305,21 @@ fn lower_sub_component( .push((prop.clone(), BindingExpression { expression, animation })) } } - for elem in repeated { - sub_component.repeated.push(lower_repeated_component(&elem, &ctx)) - } + sub_component.repeated = + repeated.into_iter().map(|elem| lower_repeated_component(&elem, &ctx)).collect(); + sub_component.popup_windows = component + .popup_windows + .borrow() + .iter() + .map(|popup| lower_popup_component(&popup.component, &ctx)) + .collect(); + + crate::generator::for_each_const_properties(component, |elem, n| { + if let Some(x) = ctx.map_property_reference(&NamedReference::new(elem, n)) { + sub_component.const_properties.push(x); + } + }); + LoweredSubComponent { sub_component: Rc::new(sub_component), mapping } } @@ -277,12 +328,12 @@ fn lower_repeated_component(elem: &ElementRc, ctx: &ExpressionContext) -> Repeat let component = e.base_type.as_component().clone(); let repeated = e.repeated.as_ref().unwrap(); - let sc = ctx.state.sub_component(&component); + let sc = lower_sub_component(&component, &ctx.state, Some(ctx)); RepeatedElement { model: super::lower_expression::lower_expression(&repeated.model, ctx).unwrap(), sub_tree: ItemTree { - root: SubComponentInstance { ty: sc.sub_component.clone() }, - tree: make_tree(ctx.state, &component), + tree: make_tree(ctx.state, &component.root_element, &sc, &[]), + root: Rc::try_unwrap(sc.sub_component).unwrap(), parent_context: Some(e.enclosing_component.upgrade().unwrap().id.clone()), }, index_prop: 1, @@ -290,6 +341,26 @@ fn lower_repeated_component(elem: &ElementRc, ctx: &ExpressionContext) -> Repeat } } +fn lower_popup_component(component: &Rc, ctx: &ExpressionContext) -> ItemTree { + let sc = lower_sub_component(component, &ctx.state, Some(ctx)); + ItemTree { + tree: make_tree(ctx.state, &component.root_element, &sc, &[]), + root: Rc::try_unwrap(sc.sub_component).unwrap(), + parent_context: Some( + component + .parent_element + .upgrade() + .unwrap() + .borrow() + .enclosing_component + .upgrade() + .unwrap() + .id + .clone(), + ), + } +} + fn lower_global( global: &Rc, global_index: usize, @@ -297,6 +368,7 @@ fn lower_global( ) -> GlobalComponent { let mut mapping = LoweredSubComponentMapping::default(); let mut properties = vec![]; + let mut const_properties = vec![]; for (p, x) in &global.root_element.borrow().property_declarations { let property_index = properties.len(); @@ -307,6 +379,7 @@ fn lower_global( ); properties.push(Property { name: p.clone(), ty: x.property_type.clone() }); + const_properties.push(nr.is_constant()); state .global_properties .insert(nr.clone(), PropertyReference::Global { global_index, property_index }); @@ -330,9 +403,45 @@ fn lower_global( } } - GlobalComponent { name: global.id.clone(), properties, init_values } + GlobalComponent { name: global.id.clone(), properties, init_values, const_properties } } -fn make_tree(state: &LoweringState, component: &Rc) -> TreeNode { - todo!() +fn make_tree( + state: &LoweringState, + element: &ElementRc, + component: &LoweredSubComponent, + sub_component_path: &[usize], +) -> TreeNode { + let e = element.borrow(); + let children = e.children.iter().map(|c| make_tree(state, c, component, sub_component_path)); + match component.mapping.element_mapping.get(&ByAddress(element.clone())).unwrap() { + LoweredElement::SubComponent { sub_component_index } => { + let sub_component = e.sub_component().unwrap(); + let new_sub_component_path = sub_component_path + .iter() + .copied() + .chain(std::iter::once(*sub_component_index)) + .collect::>(); + let mut tree_node = make_tree( + state, + &sub_component.root_element, + state.sub_component(sub_component), + &new_sub_component_path, + ); + tree_node.children.extend(children); + tree_node + } + LoweredElement::NativeItem { item_index } => TreeNode { + sub_component_path: sub_component_path.into(), + item_index: *item_index, + children: children.collect(), + repeated: false, + }, + LoweredElement::Repeated { repeated_index } => TreeNode { + sub_component_path: sub_component_path.into(), + item_index: *repeated_index, + children: vec![], + repeated: true, + }, + } } diff --git a/sixtyfps_compiler/typeregister.rs b/sixtyfps_compiler/typeregister.rs index 97ac5ae49..ad01d6db9 100644 --- a/sixtyfps_compiler/typeregister.rs +++ b/sixtyfps_compiler/typeregister.rs @@ -38,8 +38,8 @@ const RESERVED_LAYOUT_PROPERTIES: &[(&str, Type)] = &[ ]; thread_local! { - pub static DIALOG_BUTTON_ROLE_ENUM: Type = - Type::Enumeration(Rc::new(Enumeration { + pub static DIALOG_BUTTON_ROLE_ENUM: Rc = + Rc::new(Enumeration { name: "DialogButtonRole".into(), values: IntoIterator::into_iter([ "none".to_owned(), @@ -52,7 +52,16 @@ thread_local! { ]) .collect(), default_value: 0, - })); + }); + + pub static LAYOUT_ALIGNMENT_ENUM: Rc = + Rc::new(Enumeration { + name: "LayoutAlignment".into(), + values: IntoIterator::into_iter( + ["stretch", "center", "start", "end", "space-between", "space-around"] + ).map(String::from).collect(), + default_value: 0, + }); } const RESERVED_OTHER_PROPERTIES: &[(&str, Type)] = &[ @@ -79,7 +88,7 @@ pub fn reserved_properties() -> impl Iterator { .chain(IntoIterator::into_iter([ ("forward-focus", Type::ElementReference), ("focus", BuiltinFunction::SetFocusItem.ty()), - ("dialog-button-role", DIALOG_BUTTON_ROLE_ENUM.with(|e| e.clone())), + ("dialog-button-role", Type::Enumeration(DIALOG_BUTTON_ROLE_ENUM.with(|e| e.clone()))), ])) } @@ -179,10 +188,6 @@ impl TypeRegister { declare_enum("TextVerticalAlignment", &["top", "center", "bottom"]); declare_enum("TextWrap", &["no-wrap", "word-wrap"]); declare_enum("TextOverflow", &["clip", "elide"]); - declare_enum( - "LayoutAlignment", - &["stretch", "center", "start", "end", "space-between", "space-around"], - ); declare_enum("ImageFit", &["fill", "contain", "cover"]); declare_enum("ImageRendering", &["smooth", "pixelated"]); declare_enum("EventResult", &["reject", "accept"]); @@ -229,7 +234,10 @@ impl TypeRegister { ); declare_enum("PointerEventKind", &["cancel", "down", "up"]); declare_enum("PointerEventButton", &["none", "left", "right", "middle"]); - register.insert_type(DIALOG_BUTTON_ROLE_ENUM.with(|x| x.clone())); + DIALOG_BUTTON_ROLE_ENUM + .with(|e| register.insert_type_with_name(Type::Enumeration(e.clone()), e.name.clone())); + LAYOUT_ALIGNMENT_ENUM + .with(|e| register.insert_type_with_name(Type::Enumeration(e.clone()), e.name.clone())); register.supported_property_animation_types.insert(Type::Float32.to_string()); register.supported_property_animation_types.insert(Type::Int32.to_string());