diff --git a/api/sixtyfps-rs/lib.rs b/api/sixtyfps-rs/lib.rs index b21f5c0b2..11b1675ef 100644 --- a/api/sixtyfps-rs/lib.rs +++ b/api/sixtyfps-rs/lib.rs @@ -72,6 +72,7 @@ pub mod re_exports { pub use const_field_offset::{self, FieldOffsets}; pub use once_cell::sync::Lazy; pub use sixtyfps_corelib::abi::datastructures::*; + pub use sixtyfps_corelib::abi::model::*; pub use sixtyfps_corelib::abi::primitives::*; pub use sixtyfps_corelib::abi::properties::Property; pub use sixtyfps_corelib::abi::signals::Signal; diff --git a/examples/rusttest/src/main.rs b/examples/rusttest/src/main.rs index 88583179a..fa5791bf2 100644 --- a/examples/rusttest/src/main.rs +++ b/examples/rusttest/src/main.rs @@ -38,11 +38,25 @@ component ButtonRectangle := Rectangle { text: button_text; color: black; } - color: { button_area.pressed ? red : green; } + color: { button_area.pressed ? red : #5898; } } Hello := Rectangle { + for x in 8: Rectangle { + color: #8005; + width: 75; + height: 75; + Rectangle { + color: #00f5; + width: 25; + height: 25; + x: 25; + y: 25; + } + } + + signal foobar; signal plus_clicked; signal minus_clicked; @@ -50,12 +64,7 @@ Hello := Rectangle { color: white; - TwoRectangle { - width: 100; - height: 100; - color: blue; - clicked => { foobar() } - } + Rectangle { x: 100; y: 100; @@ -77,7 +86,7 @@ Hello := Rectangle { } ButtonRectangle { - color: #8a8; + color: blue; x: 50; y: 225; clicked => { counter += 1 } @@ -85,7 +94,7 @@ Hello := Rectangle { } counter_label := Text { x: 100; y: 300; text: counter; color: black; } ButtonRectangle { - color: #aa8; + color: yellow; x: 50; y: 350; clicked => { minus_clicked() } diff --git a/sixtyfps_compiler/generator/cpp.rs b/sixtyfps_compiler/generator/cpp.rs index 41fc8db8b..94d1cba8f 100644 --- a/sixtyfps_compiler/generator/cpp.rs +++ b/sixtyfps_compiler/generator/cpp.rs @@ -146,6 +146,11 @@ impl CppType for Type { } fn handle_item(item: &Element, main_struct: &mut Struct, init: &mut Vec) { + if item.repeated.is_some() { + println!("FIXME: implement for"); + return; + } + main_struct.members.push(Declaration::Var(Var { ty: format!("sixtyfps::{}", item.base_type.as_builtin().class_name), name: item.id.clone(), @@ -324,7 +329,7 @@ pub fn generate(component: &Component, diag: &mut Diagnostics) -> Option Option { + let mut extra_components = vec![]; let mut declared_property_var_names = vec![]; let mut declared_property_vars = vec![]; let mut declared_property_types = vec![]; @@ -96,16 +97,58 @@ pub fn generate(component: &Component, diag: &mut Diagnostics) -> Option c, + _ => panic!("should be a component because of the repeater_component pass"), + }; + + let repeater_index = repeated_element_names.len(); + + let repeater_id = quote::format_ident!("repeater_{}", repeater_index); + let rep_component_id = self::component_id(&*base_component); + + extra_components.push(generate(&*base_component, diag).unwrap_or_default()); + extra_components.push(quote! { + impl sixtyfps::re_exports::RepeatedComponent for #rep_component_id { + type Data = (); // FIXME + fn update(&self, _index: usize, _data: &Self::Data) { + // FIXME() + } + } + }); + + assert!( + repeated.model.is_constant() + && matches!(repeated.model.ty(), Type::Int32 | Type::Float32), + "TODO: currently model can only be integers" + ); + let count = compile_expression(&repeated.model, component); + init.push(quote!(self_.#repeater_id.update_model(&[() ; (#count) as usize ]);)); + + item_tree_array.push(quote!( + sixtyfps::re_exports::ItemTreeNode::DynamicTree { + index: #repeater_index, + } + )); + repeated_visit_branch.push(quote!( + #repeater_index => base.#repeater_id.visit(visitor), + )); + + repeated_element_names.push(repeater_id); + repeated_element_components.push(rep_component_id); + } else { let field_name = quote::format_ident!("{}", item.id); let children_count = item.children.len() as u32; item_tree_array.push(quote!( @@ -113,7 +156,7 @@ pub fn generate(component: &Component, diag: &mut Diagnostics) -> Option Option Option,)* #(#declared_signals : sixtyfps::re_exports::Signal<()>,)* + #(#repeated_element_names : sixtyfps::re_exports::Repeater<#repeated_element_components>,)* } impl core::default::Default for #component_id { @@ -185,6 +227,7 @@ pub fn generate(component: &Component, diag: &mut Diagnostics) -> Option Option panic!("invalid dyn_index {}", dyn_index), + } + } } fn create() -> Self { Default::default() @@ -213,9 +262,20 @@ pub fn generate(component: &Component, diag: &mut Diagnostics) -> Option proc_macro2::Ident { + if component.id.is_empty() { + quote::format_ident!("{}", component.root_element.borrow().id) + } else { + quote::format_ident!("{}", component.id) + } +} + fn access_member(element: &ElementRc, name: &str) -> TokenStream { let e = element.borrow(); let name_ident = quote::format_ident!("{}", name); diff --git a/sixtyfps_compiler/lib.rs b/sixtyfps_compiler/lib.rs index d13c2b62e..dadbc9608 100644 --- a/sixtyfps_compiler/lib.rs +++ b/sixtyfps_compiler/lib.rs @@ -26,6 +26,7 @@ mod passes { pub mod inlining; pub mod lower_layout; pub mod move_declarations; + pub mod repeater_component; pub mod resolving; pub mod unique_id; } @@ -45,8 +46,9 @@ pub fn run_passes( passes::inlining::inline(doc); passes::lower_layout::lower_layouts(&doc.root_component, diag); passes::unique_id::assign_unique_id(&doc.root_component); - passes::move_declarations::move_declarations(&doc.root_component); if compiler_config.embed_resources { passes::collect_resources::collect_resources(&doc.root_component); } + passes::repeater_component::create_repeater_components(&doc.root_component, diag); + passes::move_declarations::move_declarations(&doc.root_component); } diff --git a/sixtyfps_compiler/passes/resolving.rs b/sixtyfps_compiler/passes/resolving.rs index f83fb5d45..72bb0369f 100644 --- a/sixtyfps_compiler/passes/resolving.rs +++ b/sixtyfps_compiler/passes/resolving.rs @@ -50,8 +50,7 @@ pub fn resolve_expressions(doc: &Document, diag: &mut Diagnostics, tr: &mut Type component: component.clone(), diag, }; - r.model = - Expression::from_binding_expression_node(node.clone(), &mut lookup_ctx) + r.model = Expression::from_expression_node(node.clone(), &mut lookup_ctx) } } elem.borrow_mut().repeated = repeated; diff --git a/sixtyfps_runtime/corelib/abi/datastructures.rs b/sixtyfps_runtime/corelib/abi/datastructures.rs index 388b80ab3..51c8c1ea0 100644 --- a/sixtyfps_runtime/corelib/abi/datastructures.rs +++ b/sixtyfps_runtime/corelib/abi/datastructures.rs @@ -96,22 +96,8 @@ pub enum ItemTreeNode { /// A placeholder for many instance of item in their own component which /// are instentiated according to a model. DynamicTree { - /// Component vtable. - /// This component is going to be instantiated as many time as the model tells - component_type: *const ComponentVTable, - - /// vtable of the model - model_type: *const super::model::ModelType, - - /// byte offset of the ModelImpl within the component. - /// The model is an instance of the model described by model_type and must be - /// stored within the component - model_offset: isize, - - /// byte offset of the vector of components within the parent component - /// (ComponentVec) - /// a ComponentVec must be stored within the component to represent this tree - components_holder_offset: isize, + /// the undex which is passed in the visit_dynamic callback. + index: usize, }, } @@ -320,12 +306,16 @@ pub unsafe extern "C" fn sixtyfps_visit_item_tree( index: isize, visitor: VRefMut, ) { + fn visit_dynamic(_base: &u8, _visitor: vtable::VRefMut, _dyn_index: usize) { + todo!() + } crate::item_tree::visit_item_tree( &*(component.as_ptr() as *const u8), component, item_tree.as_slice(), index, visitor, + visit_dynamic, ) } diff --git a/sixtyfps_runtime/corelib/abi/model.rs b/sixtyfps_runtime/corelib/abi/model.rs index 7b204d0aa..95dbddd78 100644 --- a/sixtyfps_runtime/corelib/abi/model.rs +++ b/sixtyfps_runtime/corelib/abi/model.rs @@ -1,7 +1,7 @@ //! Models -/// Opaque type of a model, represented bith a ModelType -pub struct ModelImpl; +/* +use super::datastructures::ComponentVTable; /// Virtual table for a model. /// @@ -9,18 +9,58 @@ pub struct ModelImpl; /// /// TODO: how to get notification when it changes #[repr(C)] -pub struct ModelType { +#[vtable] +pub struct ModelVTable { /// Number of items - count: unsafe fn(*const ModelType, *const ModelImpl) -> u32, + count: unsafe fn(VRef) -> u32, /// Returns the data. (FIXME: find out what this returns exactly) - data: unsafe fn(*const ModelType, *const ModelImpl, n: u32) -> *const (), -} - + data: unsafe fn(VRef, n: u32) -> *const (), +}*/ +/* /// This structure will hold a vector of the component instaces #[repr(C)] -pub struct ComponentVec { - /// Should be some kind of smart pointer that deletes the component - /// Should also be a repr(c) thing with some kind of init method - _todo: Vec<*mut dyn super::datastructures::Component>, +pub struct ComponentVecHolder { + mode: vtable::VBox + // Possible optimization: all the VBox should have the same VTable kown to the parent component + _todo: Vec>, +} +*/ + +/// Component that can be instantiated by a repeater. +pub trait RepeatedComponent: crate::abi::datastructures::Component { + /// The data corresponding to the model + type Data; + + /// Update this component at the given index and the given data + fn update(&self, index: usize, data: &Self::Data); +} + +/// This field is put in a component when using the `for` syntax +/// It helps instantiating the components `C` +#[derive(Default)] +pub struct Repeater { + components: Vec>, +} + +impl Repeater +where + C: RepeatedComponent, +{ + /// Called when the model is changed + pub fn update_model(&mut self, data: &[Data]) { + self.components.clear(); + for (i, d) in data.iter().enumerate() { + let c = C::create(); + c.update(i, d); + self.components.push(Box::new(c)); + } + } + + /// Call the visitor for each component + pub fn visit(&self, mut visitor: super::datastructures::ItemVisitorRefMut) { + for c in &self.components { + c.visit_children_item(-1, visitor.borrow_mut()); + } + } } diff --git a/sixtyfps_runtime/corelib/item_tree.rs b/sixtyfps_runtime/corelib/item_tree.rs index 6064c30fa..bd0d181e2 100644 --- a/sixtyfps_runtime/corelib/item_tree.rs +++ b/sixtyfps_runtime/corelib/item_tree.rs @@ -34,12 +34,13 @@ pub fn visit_item_tree( item_tree: &[ItemTreeNode], index: isize, mut visitor: vtable::VRefMut, + visit_dynamic: fn(base: &Base, visitor: vtable::VRefMut, dyn_index: usize), ) { let mut visit_at_index = |idx: usize| match &item_tree[idx] { ItemTreeNode::Item { item, .. } => { visitor.visit_item(component, idx as isize, item.apply(base)); } - _ => todo!(), + ItemTreeNode::DynamicTree { index } => visit_dynamic(base, visitor.borrow_mut(), *index), }; if index == -1 { visit_at_index(0); diff --git a/sixtyfps_runtime/interpreter/dynamic_component.rs b/sixtyfps_runtime/interpreter/dynamic_component.rs index 14bfd399d..26728b20e 100644 --- a/sixtyfps_runtime/interpreter/dynamic_component.rs +++ b/sixtyfps_runtime/interpreter/dynamic_component.rs @@ -81,7 +81,11 @@ unsafe extern "C" fn visit_children_item( item_tree.as_slice().into(), index, v, + visit_dynamic, ); + fn visit_dynamic(base: &Instance, visitor: ItemVisitorRefMut, dyn_index: usize) { + todo!() + } } /// Information attached to a builtin item @@ -153,7 +157,8 @@ pub fn load( generator::build_array_helper(&tree.root_component, |rc_item, child_offset| { let item = rc_item.borrow(); if item.repeated.is_some() { - todo!() + println!("FIXME: implement for"); + return; } let rt = &rtti[&*item.base_type.as_builtin().class_name]; let offset = builder.add_field(rt.type_info); diff --git a/tests/rustdriver/build.rs b/tests/rustdriver/build.rs index dda8b2d24..dca2886eb 100644 --- a/tests/rustdriver/build.rs +++ b/tests/rustdriver/build.rs @@ -15,7 +15,7 @@ fn main() -> std::io::Result<()> { let module_name = testcase.relative_path.file_stem().unwrap().to_string_lossy().replace("/", "_"); - write!(generated_file, "#[path=\"{0}.rs\"] mod {0};\n", module_name)?; + write!(generated_file, "#[path=\"{0}.rs\"] mod r#{0};\n", module_name)?; let source = std::fs::read_to_string(&testcase.absolute_path)?; let mut output = std::fs::File::create(