/* 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 */ /*! module for the Rust code generator */ use crate::diagnostics::{BuildDiagnostics, CompilerDiagnostic, Level, Spanned}; use crate::expression_tree::{ BuiltinFunction, EasingCurve, Expression, NamedReference, OperatorClass, Path, }; use crate::layout::{gen::LayoutItemCodeGen, Layout, LayoutElement}; use crate::object_tree::{Component, Document, ElementRc}; use crate::typeregister::Type; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use std::rc::Rc; fn rust_type( ty: &Type, span: &crate::diagnostics::Span, ) -> Result { match ty { Type::Int32 => Ok(quote!(i32)), Type::Float32 => Ok(quote!(f32)), Type::String => Ok(quote!(sixtyfps::re_exports::SharedString)), Type::Color => Ok(quote!(sixtyfps::re_exports::Color)), Type::Duration => Ok(quote!(i64)), Type::Length => Ok(quote!(f32)), Type::LogicalLength => Ok(quote!(f32)), Type::Bool => Ok(quote!(bool)), Type::Resource => Ok(quote!(sixtyfps::re_exports::Resource)), Type::Object(o) => { let elem = o.values().map(|v| rust_type(v, span)).collect::, _>>()?; // This will produce a tuple Ok(quote!((#(#elem,)*))) } Type::Array(o) => { let inner = rust_type(&o, span)?; Ok(quote!(sixtyfps::re_exports::ModelHandle<#inner>)) } Type::Component(c) if c.root_element.borrow().base_type == Type::Void => { let c = format_ident!("{}", c.id); Ok(quote!(#c)) } _ => Err(CompilerDiagnostic { message: format!("Cannot map property type {} to Rust", ty), span: span.clone(), level: Level::Error, }), } } /// Generate the rust code for the given component. /// /// Fill the diagnostic in case of error. pub fn generate(doc: &Document, diag: &mut BuildDiagnostics) -> Option { let (structs_ids, structs): (Vec<_>, Vec<_>) = doc .exports() .iter() .filter(|(_, component)| component.root_element.borrow().base_type == Type::Void) .map(|(_, component)| (component_id(component), generate_struct(component, diag))) .unzip(); let compo = generate_component(&doc.root_component, diag)?; let compo_id = component_id(&doc.root_component); let compo_module = format_ident!("sixtyfps_generated_{}", compo_id); /*let (version_major, version_minor, version_patch): (usize, usize, usize) = ( env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(), env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(), env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(), );*/ let version_check = format_ident!( "VersionCheck_{}_{}_{}", env!("CARGO_PKG_VERSION_MAJOR"), env!("CARGO_PKG_VERSION_MINOR"), env!("CARGO_PKG_VERSION_PATCH"), ); Some(quote! { #[allow(non_snake_case)] mod #compo_module { #(#structs)* #compo const _THE_SAME_VERSION_MUST_BE_USED_FOR_THE_COMPILER_AND_THE_RUNTIME : sixtyfps::#version_check = sixtyfps::#version_check; } pub use #compo_module::{#compo_id #(,#structs_ids)* }; }) } fn generate_struct(component: &Rc, diag: &mut BuildDiagnostics) -> TokenStream { let component_id = component_id(component); debug_assert_eq!(component.root_element.borrow().base_type, Type::Void); let (declared_property_vars, declared_property_types): (Vec<_>, Vec<_>) = component .root_element .borrow() .property_declarations .iter() .map(|(name, decl)| { ( format_ident!("{}", name), rust_type(&decl.property_type, &decl.type_node.span()).unwrap_or_else(|err| { diag.push_internal_error(err.into()); quote!(()) }), ) }) .unzip(); quote! { #[derive(Default, PartialEq, Debug, Clone)] pub struct #component_id { #(pub #declared_property_vars : #declared_property_types),* } } } /// Generate the rust code for the given component. /// /// Fill the diagnostic in case of error. fn generate_component( component: &Rc, diag: &mut BuildDiagnostics, ) -> Option { let mut extra_components = vec![]; let mut declared_property_vars = vec![]; let mut declared_property_types = vec![]; let mut declared_signals = vec![]; let mut declared_signals_types = vec![]; let mut property_and_signal_accessors: Vec = vec![]; for (prop_name, property_decl) in component.root_element.borrow().property_declarations.iter() { let prop_ident = format_ident!("{}", prop_name); if let Type::Signal { args } = &property_decl.property_type { declared_signals.push(prop_ident.clone()); let signal_args = args .iter() .map(|a| rust_type(a, &property_decl.type_node.span())) .collect::, _>>() .unwrap_or_else(|err| { diag.push_internal_error(err.into()); vec![] }); if property_decl.expose_in_public_api { let args_name = (0..signal_args.len()).map(|i| format_ident!("arg_{}", i)).collect::>(); let emitter_ident = format_ident!("emit_{}", prop_name); property_and_signal_accessors.push( quote!( #[allow(dead_code)] pub fn #emitter_ident(self: ::core::pin::Pin<&Self>, #(#args_name : #signal_args,)*) { Self::FIELD_OFFSETS.#prop_ident.apply_pin(self).emit(&(#(#args_name,)*)) } ) .into(), ); let on_ident = format_ident!("on_{}", prop_name); let args_index = (0..signal_args.len()).map(proc_macro2::Literal::usize_unsuffixed); property_and_signal_accessors.push( quote!( #[allow(dead_code)] pub fn #on_ident(self: ::core::pin::Pin<&Self>, f: impl Fn(#(#signal_args),*) + 'static) { #[allow(unused)] Self::FIELD_OFFSETS.#prop_ident.apply_pin(self).set_handler( // FIXME: why do i need to clone here? move |args| f(#(args.#args_index.clone()),*) ) } ) .into(), ); } declared_signals_types.push(signal_args); } else { let rust_property_type = rust_type(&property_decl.property_type, &property_decl.type_node.span()) .unwrap_or_else(|err| { diag.push_internal_error(err.into()); quote!().into() }); if property_decl.expose_in_public_api { let getter_ident = format_ident!("get_{}", prop_name); let setter_ident = format_ident!("set_{}", prop_name); let prop = if let Some(alias) = &property_decl.is_alias { access_member( &alias.element.upgrade().unwrap(), &alias.name, component, quote!(self), false, ) } else { quote!(Self::FIELD_OFFSETS.#prop_ident.apply_pin(self)) }; property_and_signal_accessors.push( quote!( #[allow(dead_code)] pub fn #getter_ident(self: ::core::pin::Pin<&Self>) -> #rust_property_type { #[allow(unused_imports)] use sixtyfps::re_exports::*; #prop.get() } ) .into(), ); let set_value = property_set_value_tokens( component, &component.root_element, prop_name, quote!(value), ); property_and_signal_accessors.push( quote!( #[allow(dead_code)] pub fn #setter_ident(self: ::core::pin::Pin<&Self>, value: #rust_property_type) { #[allow(unused_imports)] use sixtyfps::re_exports::*; #prop.#set_value } ) .into(), ); } if property_decl.is_alias.is_none() { declared_property_vars.push(prop_ident.clone()); declared_property_types.push(rust_property_type.clone()); } } } if diag.has_error() { return None; } let component_id = component_id(component); let mut item_tree_array = Vec::new(); let mut item_names = Vec::new(); let mut item_types = Vec::new(); let mut repeated_element_names = Vec::new(); let mut repeated_element_components = Vec::new(); let mut repeated_visit_branch = Vec::new(); let mut repeated_input_branch = Vec::new(); let mut init = Vec::new(); let mut maybe_window_field_decl = None; let mut maybe_window_field_init = None; super::build_array_helper(component, |item_rc, children_index, is_flickable_rect| { let item = item_rc.borrow(); if is_flickable_rect { let field_name = format_ident!("{}", item.id); let children_count = item.children.len() as u32; let children_index = item_tree_array.len() as u32 + 1; item_tree_array.push(quote!( sixtyfps::re_exports::ItemTreeNode::Item{ item: VOffset::new(#component_id::FIELD_OFFSETS.#field_name + sixtyfps::re_exports::Flickable::FIELD_OFFSETS.viewport), chilren_count: #children_count, children_index: #children_index, } )); } else if let Some(repeated) = &item.repeated { let base_component = item.base_type.as_component(); let repeater_index = repeated_element_names.len(); let repeater_id = format_ident!("repeater_{}", item.id); let rep_component_id = self::component_id(&*base_component); extra_components.push(generate_component(&*base_component, diag).unwrap_or_else( || { assert!(diag.has_error()); Default::default() }, )); extra_components.push(if repeated.is_conditional_element { quote! { impl sixtyfps::re_exports::RepeatedComponent for #rep_component_id { type Data = (); fn update(&self, _: usize, _: Self::Data) { } } } } else { let data_type = rust_type( &Expression::RepeaterModelReference { element: Rc::downgrade(item_rc) }.ty(), &item.node.as_ref().map_or_else(Default::default, |n| n.span()), ) .unwrap_or_else(|err| { diag.push_internal_error(err.into()); quote!().into() }); quote! { impl sixtyfps::re_exports::RepeatedComponent for #rep_component_id { type Data = #data_type; fn update(&self, index: usize, data: Self::Data) { self.index.set(index); self.model_data.set(data) } } } }); let mut model = compile_expression(&repeated.model, component); if repeated.is_conditional_element { model = quote!(sixtyfps::re_exports::ModelHandle::Some(std::rc::Rc::::new(#model))) } // FIXME: there could be an optimization if `repeated.model.is_constant()`, we don't need a binding init.push(quote! { self_pinned.#repeater_id.set_model_binding({ let self_weak = sixtyfps::re_exports::PinWeak::downgrade(self_pinned.clone()); move || { let self_pinned = self_weak.upgrade().unwrap(); let _self = self_pinned.as_ref(); (#model) as _ } }); }); repeated_visit_branch.push(quote!( #repeater_index => { #component_id::FIELD_OFFSETS.#repeater_id.apply_pin(self_pinned).ensure_updated( || { #rep_component_id::new(self_pinned.self_weak.get().unwrap().clone()) } ); self_pinned.#repeater_id.visit(order, visitor) } )); repeated_input_branch.push(quote!( #repeater_index => self.#repeater_id.input_event(rep_index, event, window), )); item_tree_array.push(quote!( sixtyfps::re_exports::ItemTreeNode::DynamicTree { index: #repeater_index, } )); repeated_element_names.push(repeater_id); repeated_element_components.push(rep_component_id); } else { let field_name = format_ident!("{}", item.id); let children_count = if super::is_flickable(item_rc) { 1 } else { item.children.len() as u32 }; item_tree_array.push(quote!( sixtyfps::re_exports::ItemTreeNode::Item{ item: VOffset::new(#component_id::FIELD_OFFSETS.#field_name), chilren_count: #children_count, children_index: #children_index, } )); for (k, binding_expression) in &item.bindings { let rust_property_ident = format_ident!("{}", k); let rust_property = if item.property_declarations.contains_key(k) { quote!(#rust_property_ident) } else if let Some(vp) = super::as_flickable_viewport_property(item_rc, k) { let rust_property_ident = format_ident!("{}", vp); quote!(#field_name.viewport.#rust_property_ident) } else { quote!(#field_name.#rust_property_ident) }; let tokens_for_expression = compile_expression(binding_expression, &component); if matches!(item.lookup_property(k.as_str()), Type::Signal{..}) { init.push(quote!( self_pinned.#rust_property.set_handler({ let self_weak = sixtyfps::re_exports::PinWeak::downgrade(self_pinned.clone()); move |args| { let self_pinned = self_weak.upgrade().unwrap(); let _self = self_pinned.as_ref(); #tokens_for_expression; } }); )); } else { let setter = if binding_expression.is_constant() { quote!(set((#tokens_for_expression) as _)) } else { property_set_binding_tokens( component, &item_rc, k, quote!({ let self_weak = sixtyfps::re_exports::PinWeak::downgrade(self_pinned.clone()); move || { let self_pinned = self_weak.upgrade().unwrap(); let _self = self_pinned.as_ref(); (#tokens_for_expression) as _ } }), ) }; init.push(quote!( self_pinned.#rust_property.#setter; )); } } item_names.push(field_name); item_types.push(format_ident!("{}", item.base_type.as_native().class_name)); } }); let resource_symbols: Vec = if component.embed_file_resources.get() { component .referenced_file_resources .borrow() .iter() .map(|(path, id)| { let symbol = format_ident!("SFPS_EMBEDDED_RESOURCE_{}", id); quote!(const #symbol: &'static [u8] = ::core::include_bytes!(#path);) }) .collect() } else { Vec::new() }; let layouts = compute_layout(component, &repeated_element_names); let mut visibility = None; let mut parent_component_type = None; let mut has_window_impl = None; if let Some(parent_element) = component.parent_element.upgrade() { if !parent_element.borrow().repeated.as_ref().map_or(false, |r| r.is_conditional_element) { declared_property_vars.push(format_ident!("index")); declared_property_types.push(quote!(usize)); declared_property_vars.push(format_ident!("model_data")); declared_property_types.push( rust_type( &Expression::RepeaterModelReference { element: component.parent_element.clone(), } .ty(), &parent_element .borrow() .node .as_ref() .map_or_else(Default::default, |n| n.span()), ) .unwrap_or_else(|err| { diag.push_internal_error(err.into()); quote!().into() }), ); } parent_component_type = Some(self::component_id( &parent_element.borrow().enclosing_component.upgrade().unwrap(), )); } else { // FIXME: This field is public for testing. maybe_window_field_decl = Some(quote!(pub window: sixtyfps::re_exports::ComponentWindow)); maybe_window_field_init = Some(quote!(window: sixtyfps::create_window())); let root_elem = component.root_element.borrow(); let root_item_name = format_ident!("{}", root_elem.id); property_and_signal_accessors.push(quote! { pub fn run(self : core::pin::Pin>) { use sixtyfps::re_exports::*; let root_item = Self::FIELD_OFFSETS.#root_item_name.apply_pin(self.as_ref()); self.as_ref().window.run(VRef::new_pin(self.as_ref()), VRef::new_pin(root_item)); } }); property_and_signal_accessors.push(quote! { pub fn as_weak(self: core::pin::Pin>) -> sixtyfps::re_exports::PinWeak { sixtyfps::re_exports::PinWeak::downgrade(self) } }); visibility = Some(quote!(pub)); has_window_impl = Some(quote!( impl sixtyfps::testing::HasWindow for #component_id { fn component_window(&self) -> &sixtyfps::re_exports::ComponentWindow { &self.window } } )) }; // Trick so we can use `#()` as a `if let Some` in `quote!` let parent_component_type = parent_component_type.iter().collect::>(); let item_tree_array_len = item_tree_array.len(); if diag.has_error() { return None; } let drop_impl = { let guarded_window_ref = { let mut root_component = component.clone(); let mut component_rust = quote!(self); while let Some(p) = root_component.parent_element.upgrade() { root_component = p.borrow().enclosing_component.upgrade().unwrap(); component_rust = quote!(if let Some(parent) = #component_rust.parent.upgrade() { parent } else { return; }.as_ref()); } quote!(#component_rust.as_ref().window) }; quote!(impl sixtyfps::re_exports::PinnedDrop for #component_id { fn drop(self: core::pin::Pin<&mut #component_id>) { use sixtyfps::re_exports::*; #guarded_window_ref.free_graphics_resources(VRef::new_pin(self.as_ref())); } }) }; Some(quote!( #(#resource_symbols)* #[derive(sixtyfps::re_exports::FieldOffsets)] #[const_field_offset(sixtyfps::re_exports::const_field_offset)] #[repr(C)] #[pin_drop] #visibility struct #component_id { #(#item_names : sixtyfps::re_exports::#item_types,)* #(#declared_property_vars : sixtyfps::re_exports::Property<#declared_property_types>,)* #(#declared_signals : sixtyfps::re_exports::Signal<(#(#declared_signals_types,)*)>,)* #(#repeated_element_names : sixtyfps::re_exports::Repeater<#repeated_element_components>,)* self_weak: sixtyfps::re_exports::OnceCell>, #(parent : sixtyfps::re_exports::PinWeak<#parent_component_type>,)* mouse_grabber: ::core::cell::Cell, #maybe_window_field_decl } impl sixtyfps::re_exports::Component for #component_id { fn visit_children_item(self: ::core::pin::Pin<&Self>, index: isize, order: sixtyfps::re_exports::TraversalOrder, visitor: sixtyfps::re_exports::ItemVisitorRefMut) -> sixtyfps::re_exports::VisitChildrenResult { use sixtyfps::re_exports::*; return sixtyfps::re_exports::visit_item_tree(self, VRef::new_pin(self), Self::item_tree(), index, order, visitor, visit_dynamic); #[allow(unused)] fn visit_dynamic(self_pinned: ::core::pin::Pin<&#component_id>, order: sixtyfps::re_exports::TraversalOrder, visitor: ItemVisitorRefMut, dyn_index: usize) -> VisitChildrenResult { match dyn_index { #(#repeated_visit_branch)* _ => panic!("invalid dyn_index {}", dyn_index), } } } fn input_event(self: ::core::pin::Pin<&Self>, mouse_event : sixtyfps::re_exports::MouseEvent, window: &sixtyfps::re_exports::ComponentWindow) -> sixtyfps::re_exports::InputEventResult { use sixtyfps::re_exports::*; let mouse_grabber = self.mouse_grabber.get(); #[allow(unused)] let (status, new_grab) = if let Some((item_index, rep_index)) = mouse_grabber.aborted_indexes() { let tree = Self::item_tree(); let offset = item_offset(self, tree, item_index); let mut event = mouse_event.clone(); event.pos -= offset.to_vector(); let res = match tree[item_index] { ItemTreeNode::Item { item, .. } => { item.apply_pin(self).as_ref().input_event(event, window) } ItemTreeNode::DynamicTree { index } => { match index { #(#repeated_input_branch)* _ => panic!("invalid index {}", index), } } }; match res { InputEventResult::GrabMouse => (res, mouse_grabber), _ => (res, VisitChildrenResult::CONTINUE), } } else { process_ungrabbed_mouse_event(VRef::new_pin(self), mouse_event, window) }; self.mouse_grabber.set(new_grab); status } #layouts } impl #component_id{ pub fn new(#(parent: sixtyfps::re_exports::PinWeak::<#parent_component_type>)*) -> core::pin::Pin> { #![allow(unused)] use sixtyfps::re_exports::*; ComponentVTable_static!(static VT for #component_id); let mut self_ = Self { #(#item_names : ::core::default::Default::default(),)* #(#declared_property_vars : ::core::default::Default::default(),)* #(#declared_signals : ::core::default::Default::default(),)* #(#repeated_element_names : ::core::default::Default::default(),)* self_weak : ::core::default::Default::default(), #(parent : parent as sixtyfps::re_exports::PinWeak::<#parent_component_type>,)* mouse_grabber: ::core::cell::Cell::new(sixtyfps::re_exports::VisitChildrenResult::CONTINUE), #maybe_window_field_init }; let self_pinned = std::rc::Rc::pin(self_); self_pinned.self_weak.set(PinWeak::downgrade(self_pinned.clone())).map_err(|_|()) .expect("Can only be pinned once"); #(#init)* self_pinned } #(#property_and_signal_accessors)* fn item_tree() -> &'static [sixtyfps::re_exports::ItemTreeNode] { use sixtyfps::re_exports::*; // FIXME: ideally this should be a const static ITEM_TREE : Lazy<[sixtyfps::re_exports::ItemTreeNode<#component_id>; #item_tree_array_len]> = Lazy::new(|| [#(#item_tree_array),*]); &*ITEM_TREE } } #drop_impl #has_window_impl #(#extra_components)* )) } /// Return an identifier suitable for this component fn component_id(component: &Component) -> proc_macro2::Ident { if component.id.is_empty() { let s = &component.root_element.borrow().id; // Capitalize first leter: let mut it = s.chars(); let id = it.next().map(|c| c.to_ascii_uppercase()).into_iter().chain(it).collect::(); format_ident!("{}", id) } else { format_ident!("{}", component.id) } } fn property_animation_tokens( component: &Rc, element: &ElementRc, property_name: &str, ) -> Option { if let Some(animation) = element.borrow().property_animations.get(property_name) { let bindings: Vec = animation .borrow() .bindings .iter() .map(|(prop, initializer)| { let prop_ident = format_ident!("{}", prop); let initializer = compile_expression(initializer, component); quote!(#prop_ident: #initializer as _) }) .collect(); Some(quote!(&sixtyfps::re_exports::PropertyAnimation{ #(#bindings, )* ..::core::default::Default::default() })) } else { None } } fn property_set_value_tokens( component: &Rc, element: &ElementRc, property_name: &str, value_tokens: TokenStream, ) -> TokenStream { if let Some(animation_tokens) = property_animation_tokens(component, element, property_name) { quote!(set_animated_value(#value_tokens, #animation_tokens)) } else { quote!(set(#value_tokens)) } } fn property_set_binding_tokens( component: &Rc, element: &ElementRc, property_name: &str, binding_tokens: TokenStream, ) -> TokenStream { if let Some(animation_tokens) = property_animation_tokens(component, element, property_name) { quote!(set_animated_binding(#binding_tokens, #animation_tokens)) } else { quote!(set_binding(#binding_tokens)) } } /// Returns the code that can access the given property or signal (but without the set or get) /// /// to be used like: /// ```ignore /// let access = access_member(...) /// quote!(#access.get()) /// ``` fn access_member( element: &ElementRc, name: &str, component: &Rc, component_rust: TokenStream, is_special: bool, ) -> TokenStream { let e = element.borrow(); let enclosing_component = e.enclosing_component.upgrade().unwrap(); if Rc::ptr_eq(component, &enclosing_component) { let component_id = component_id(&enclosing_component); let name_ident = format_ident!("{}", name); if e.property_declarations.contains_key(name) || is_special { quote!(#component_id::FIELD_OFFSETS.#name_ident.apply_pin(#component_rust)) } else if let Some(vp) = super::as_flickable_viewport_property(element, name) { let name_ident = format_ident!("{}", vp); let elem_ident = format_ident!("{}", e.id); quote!((#component_id::FIELD_OFFSETS.#elem_ident + sixtyfps::re_exports::Flickable::FIELD_OFFSETS.viewport + sixtyfps::re_exports::Rectangle::FIELD_OFFSETS.#name_ident) .apply_pin(#component_rust) ) } else { let elem_ident = format_ident!("{}", e.id); let elem_ty = format_ident!("{}", e.base_type.as_native().class_name); quote!((#component_id::FIELD_OFFSETS.#elem_ident + #elem_ty::FIELD_OFFSETS.#name_ident) .apply_pin(#component_rust) ) } } else { access_member( element, name, &component .parent_element .upgrade() .unwrap() .borrow() .enclosing_component .upgrade() .unwrap(), quote!(#component_rust.parent.upgrade().unwrap().as_ref()), is_special, ) } } /// Return an expression that gets the window fn window_ref_expression(component: &Rc) -> TokenStream { let mut root_component = component.clone(); let mut component_rust = quote!(_self); while let Some(p) = root_component.parent_element.upgrade() { root_component = p.borrow().enclosing_component.upgrade().unwrap(); component_rust = quote!(#component_rust.parent.upgrade().unwrap().as_ref()); } quote!(#component_rust.as_ref().window) } fn compile_expression(e: &Expression, component: &Rc) -> TokenStream { match e { Expression::StringLiteral(s) => quote!(sixtyfps::re_exports::SharedString::from(#s)), Expression::NumberLiteral(n, unit) => { let n = unit.normalize(*n); quote!(#n) } Expression::BoolLiteral(b) => quote!(#b), Expression::Cast { from, to } => { let f = compile_expression(&*from, &component); match (from.ty(), to) { (Type::Float32, Type::String) | (Type::Int32, Type::String) => { quote!(sixtyfps::re_exports::SharedString::from(format!("{}", #f).as_str())) } (Type::Float32, Type::Model) | (Type::Int32, Type::Model) => { quote!(sixtyfps::re_exports::ModelHandle::Some(std::rc::Rc::::new(#f as usize))) } (Type::Float32, Type::Color) => { quote!(sixtyfps::re_exports::Color::from_argb_encoded(#f as u32)) } (Type::Object(ref o), Type::Component(c)) => { let fields = o.iter().enumerate().map(|(index, (name, _))| { let index = proc_macro2::Literal::usize_unsuffixed(index); let name = format_ident!("{}", name); quote!(#name: obj.#index as _) }); let id = component_id(c); quote!({ let obj = #f; #id { #(#fields),*} }) } _ => f, } } Expression::PropertyReference(NamedReference { element, name }) => { let access = access_member( &element.upgrade().unwrap(), name.as_str(), component, quote!(_self), false, ); quote!(#access.get()) } Expression::BuiltinFunctionReference(funcref) => match funcref { BuiltinFunction::GetWindowScaleFactor => { let window_ref = window_ref_expression(component); quote!(#window_ref.scale_factor) } BuiltinFunction::Debug => quote!((|x| println!("{:?}", x))), }, Expression::RepeaterIndexReference { element } => { let access = access_member( &element.upgrade().unwrap().borrow().base_type.as_component().root_element, "index", component, quote!(_self), true, ); quote!(#access.get()) } Expression::RepeaterModelReference { element } => { let access = access_member( &element.upgrade().unwrap().borrow().base_type.as_component().root_element, "model_data", component, quote!(_self), true, ); quote!(#access.get()) } Expression::FunctionParameterReference { index, .. } => { let i = proc_macro2::Literal::usize_unsuffixed(*index); quote! {args.#i.clone()} } Expression::ObjectAccess { base, name } => match base.ty() { Type::Object(ty) => { let index = ty .keys() .position(|k| k == name) .expect("Expression::ObjectAccess: Cannot find a key in an object"); let index = proc_macro2::Literal::usize_unsuffixed(index); let base_e = compile_expression(base, component); quote!((#base_e).#index ) } Type::Component(c) if c.root_element.borrow().base_type == Type::Void => { let base_e = compile_expression(base, component); let name = format_ident!("{}", name); quote!((#base_e).#name) } _ => panic!("Expression::ObjectAccess's base expression is not an Object type"), }, Expression::CodeBlock(sub) => { let map = sub.iter().map(|e| compile_expression(e, &component)); quote!({ #(#map);* }) } Expression::SignalReference(NamedReference { element, name, .. }) => access_member( &element.upgrade().unwrap(), name.as_str(), component, quote!(_self), false, ), Expression::FunctionCall { function, arguments } => { let f = compile_expression(function, &component); let a = arguments.iter().map(|a| compile_expression(a, &component)); if let Type::Signal { args } = function.ty() { let cast = args.iter().map(|ty| match ty { Type::Bool => quote!(as bool), Type::Int32 => quote!(as i32), Type::Float32 => quote!(as f32), _ => quote!(.clone()), }); quote! { #f.emit(&(#((#a)#cast,)*).into())} } else { quote! { #f(#(#a.clone()),*)} } } Expression::SelfAssignment { lhs, rhs, op } => match &**lhs { Expression::PropertyReference(NamedReference { element, name }) => { let lhs = access_member( &element.upgrade().unwrap(), name.as_str(), component, quote!(_self), false, ); let rhs = compile_expression(&*rhs, &component); if *op == '=' { quote!( #lhs.set((#rhs) as _) ) } else { let op = proc_macro2::Punct::new(*op, proc_macro2::Spacing::Alone); quote!( #lhs.set(((#lhs.get() as f64) #op (#rhs as f64)) as _) ) } } _ => panic!("typechecking should make sure this was a PropertyReference"), }, Expression::BinaryExpression { lhs, rhs, op } => { let conv = match crate::expression_tree::operator_class(*op) { OperatorClass::ArithmeticOp => Some(quote!(as f64)), OperatorClass::ComparisonOp if matches!( lhs.ty(), Type::Int32 | Type::Float32 | Type::Duration | Type::Length | Type::LogicalLength ) => { Some(quote!(as f64)) } _ => None, }; let lhs = compile_expression(&*lhs, &component); let rhs = compile_expression(&*rhs, &component); let op = match op { '=' => quote!(==), '!' => quote!(!=), '≤' => quote!(<=), '≥' => quote!(>=), '&' => quote!(&&), '|' => quote!(||), _ => proc_macro2::TokenTree::Punct(proc_macro2::Punct::new( *op, proc_macro2::Spacing::Alone, )) .into(), }; quote!( ((#lhs #conv ) #op (#rhs #conv )) ) } Expression::UnaryOp { sub, op } => { let sub = compile_expression(&*sub, &component); let op = proc_macro2::Punct::new(*op, proc_macro2::Spacing::Alone); quote!( #op #sub ) } Expression::ResourceReference { absolute_source_path } => { if let Some(id) = component .referenced_file_resources .borrow() .get(absolute_source_path) .filter(|_| component.embed_file_resources.get()) { let symbol = format_ident!("SFPS_EMBEDDED_RESOURCE_{}", id); quote!(sixtyfps::re_exports::Resource::EmbeddedData(#symbol.into())) } else { quote!(sixtyfps::re_exports::Resource::AbsoluteFilePath(sixtyfps::re_exports::SharedString::from(#absolute_source_path))) } } Expression::Condition { condition, true_expr, false_expr } => { let condition_code = compile_expression(&*condition, component); let true_code = compile_expression(&*true_expr, component); let false_code = compile_expression(&*false_expr, component); quote!( if #condition_code { #true_code } else { (#false_code) as _ } ) } Expression::Invalid | Expression::Uncompiled(_) | Expression::TwoWayBinding(_) => { let error = format!("unsupported expression {:?}", e); quote!(compile_error! {#error}) } Expression::Array { values, element_ty } => { let rust_element_ty = rust_type(&element_ty, &Default::default()).unwrap(); let val = values.iter().map(|e| compile_expression(e, component)); quote!(sixtyfps::re_exports::ModelHandle::Some( std::rc::Rc::new(sixtyfps::re_exports::VecModel::<#rust_element_ty>::from(vec![#(#val as _),*])) )) } Expression::Object { ty, values } => { if let Type::Object(ty) = ty { let elem = ty.iter().map(|(k, t)| { values.get(k).map(|e| { let ce = compile_expression(e, component); let t = rust_type(t, &Default::default()).unwrap_or_default(); quote!(#ce as #t) }) }); // This will produce a tuple quote!((#(#elem,)*)) } else { panic!("Expression::Object is not a Type::Object") } } Expression::PathElements { elements } => compile_path(elements, component), Expression::StoreLocalVariable { name, value } => { let value = compile_expression(value, component); let name = format_ident!("{}", name); quote!(let #name = #value;) } Expression::ReadLocalVariable { name, .. } => { let name = format_ident!("{}", name); quote!(#name) } Expression::EasingCurve(EasingCurve::Linear) => { quote!(sixtyfps::re_exports::EasingCurve::Linear) } Expression::EasingCurve(EasingCurve::CubicBezier(a, b, c, d)) => { quote!(sixtyfps::re_exports::EasingCurve::CubicBezier([#a, #b, #c, #d])) } Expression::EnumerationValue(value) => { let base_ident = format_ident!("{}", value.enumeration.name); let value_ident = format_ident!("{}", value.to_string()); quote!(sixtyfps::re_exports::#base_ident::#value_ident) } } } struct RustLanguageLayoutGen; impl crate::layout::gen::Language for RustLanguageLayoutGen { type CompiledCode = TokenStream; } type LayoutTreeItem<'a> = crate::layout::gen::LayoutTreeItem<'a, RustLanguageLayoutGen>; impl LayoutItemCodeGen for Layout { fn get_property_ref(&self, name: &str) -> TokenStream { let moved_property_name = match self.rect().mapped_property_name(name) { Some(name) => name, None => return quote!(None), }; let n = format_ident!("{}", moved_property_name); quote! {Some(&self.#n)} } fn get_layout_info_ref<'a, 'b>( &'a self, layout_tree: &'b mut Vec>, component: &Rc, ) -> TokenStream { let self_as_layout_tree_item = collect_layouts_recursively(layout_tree, &self, component); match self_as_layout_tree_item { LayoutTreeItem::GridLayout { cell_ref_variable, spacing, padding, .. } => { quote!(grid_layout_info(&Slice::from_slice(&#cell_ref_variable), #spacing, #padding)) } LayoutTreeItem::PathLayout(_) => todo!(), } } } impl LayoutItemCodeGen for LayoutElement { fn get_property_ref(&self, name: &str) -> TokenStream { let e = format_ident!("{}", self.element.borrow().id); if self.element.borrow().lookup_property(name) == Type::Length { let n = format_ident!("{}", name); quote! {Some(&self.#e.#n)} } else { quote! {None} } } fn get_layout_info_ref<'a, 'b>( &'a self, layout_tree: &'b mut Vec>, component: &Rc, ) -> TokenStream { let e = format_ident!("{}", self.element.borrow().id); let element_info = quote!(Self::FIELD_OFFSETS.#e.apply_pin(self).layouting_info(&window)); match &self.layout { Some(layout) => { let layout_info = layout.get_layout_info_ref(layout_tree, component); quote!(#layout_info.merge(&#element_info)) } None => element_info, } } } fn collect_layouts_recursively<'a, 'b>( layout_tree: &'b mut Vec>, layout: &'a Layout, component: &Rc, ) -> &'b LayoutTreeItem<'a> { let get_property_ref = LayoutItemCodeGen::::get_property_ref; match layout { Layout::GridLayout(grid_layout) => { let cells: Vec = grid_layout .elems .iter() .map(|cell| { let width = get_property_ref(&cell.item, "width"); let height = get_property_ref(&cell.item, "height"); let x = get_property_ref(&cell.item, "x"); let y = get_property_ref(&cell.item, "y"); let (col, row, colspan, rowspan) = (cell.col, cell.row, cell.colspan, cell.rowspan); let mut layout_info = cell.item.get_layout_info_ref(layout_tree, component); if cell.has_explicit_restrictions() { let (name, expr): (Vec<_>, Vec<_>) = cell .for_each_restrictions() .iter() .filter_map(|(e, s)| { e.as_ref().map(|e| { (format_ident!("{}", s), compile_expression(&e, component)) }) }) .unzip(); layout_info = quote!({ let mut layout_info = #layout_info; #(layout_info.#name = #expr;)* layout_info }); } quote!(GridLayoutCellData { x: #x, y: #y, width: #width, height: #height, col: #col, row: #row, colspan: #colspan, rowspan: #rowspan, constraint: #layout_info, }) }) .collect(); let cell_ref_variable = format_ident!("cells_{}", layout_tree.len()); let cell_creation_code = quote!(let #cell_ref_variable = [#( #cells ),*];); let (spacing, spacing_creation_code) = if let Some(spacing) = &grid_layout.spacing { let variable = format_ident!("spacing_{}", layout_tree.len()); let spacing_code = compile_expression(spacing, component); (quote!(#variable), Some(quote!(let #variable = #spacing_code;))) } else { (quote!(0.), None) }; let padding = { let padding_prop = |expr| { if let Some(expr) = expr { compile_expression(expr, component) } else { quote!(0.) } }; let left = padding_prop(grid_layout.padding.left.as_ref()); let right = padding_prop(grid_layout.padding.right.as_ref()); let top = padding_prop(grid_layout.padding.top.as_ref()); let bottom = padding_prop(grid_layout.padding.bottom.as_ref()); quote!(&sixtyfps::re_exports::Padding { left: #left, right: #right, top: #top, bottom: #bottom, }) }; layout_tree.push( LayoutTreeItem::GridLayout { grid: grid_layout, var_creation_code: quote!(#cell_creation_code #spacing_creation_code), cell_ref_variable: quote!(#cell_ref_variable), spacing, padding, } .into(), ); } Layout::PathLayout(layout) => layout_tree.push(layout.into()), } layout_tree.last().unwrap() } impl<'a> LayoutTreeItem<'a> { fn emit_solve_calls(&self, component: &Rc, code_stream: &mut Vec) { match self { LayoutTreeItem::GridLayout { grid, cell_ref_variable, spacing, padding, .. } => { let x_pos = compile_expression(&*grid.rect.x_reference, component); let y_pos = compile_expression(&*grid.rect.y_reference, component); let width = compile_expression(&*grid.rect.width_reference, component); let height = compile_expression(&*grid.rect.height_reference, component); code_stream.push(quote! { solve_grid_layout(&GridLayoutData { width: #width, height: #height, x: #x_pos as _, y: #y_pos as _, cells: Slice::from_slice(&#cell_ref_variable), spacing: #spacing, padding: #padding, }); }); } LayoutTreeItem::PathLayout(path_layout) => { let path_layout_item_data = |elem: &ElementRc, elem_rs: TokenStream, component_rust: TokenStream| { let prop_ref = |n: &str| { if elem.borrow().lookup_property(n) == Type::Length { let n = format_ident!("{}", n); quote! {Some(& #elem_rs.#n)} } else { quote! {None} } }; let prop_value = |n: &str| { if elem.borrow().lookup_property(n) == Type::Length { let accessor = access_member( &elem, n, &elem.borrow().enclosing_component.upgrade().unwrap(), component_rust.clone(), false, ); quote!(#accessor.get()) } else { quote! {0.} } }; let x = prop_ref("x"); let y = prop_ref("y"); let width = prop_value("width"); let height = prop_value("height"); quote!(PathLayoutItemData { x: #x, y: #y, width: #width, height: #height, }) }; let path_layout_item_data_for_elem = |elem: &ElementRc| { let e = format_ident!("{}", elem.borrow().id); path_layout_item_data(elem, quote!(self.#e), quote!(self)) }; let is_static_array = path_layout.elements.iter().all(|elem| elem.borrow().repeated.is_none()); let slice = if is_static_array { let items = path_layout.elements.iter().map(path_layout_item_data_for_elem); quote!( Slice::from_slice(&[#( #items ),*]) ) } else { let mut fixed_count = 0usize; let mut repeated_count = quote!(); let mut push_code = quote!(); for elem in &path_layout.elements { if elem.borrow().repeated.is_some() { let repeater_id = format_ident!("repeater_{}", elem.borrow().id); repeated_count = quote!(#repeated_count + self.#repeater_id.len()); let root_element = elem.borrow().base_type.as_component().root_element.clone(); let root_id = format_ident!("{}", root_element.borrow().id); let e = path_layout_item_data( &root_element, quote!(sub_comp.#root_id), quote!(sub_comp.as_ref()), ); push_code = quote! { #push_code let internal_vec = self.#repeater_id.components_vec(); for sub_comp in &internal_vec { items_vec.push(#e) } } } else { fixed_count += 1; let e = path_layout_item_data_for_elem(elem); push_code = quote! { #push_code items_vec.push(#e); } } } code_stream.push(quote! { let mut items_vec = Vec::with_capacity(#fixed_count #repeated_count); #push_code }); quote!(Slice::from_slice(items_vec.as_slice())) }; let path = compile_path(&path_layout.path, &component); let x_pos = compile_expression(&*path_layout.rect.x_reference, &component); let y_pos = compile_expression(&*path_layout.rect.y_reference, &component); let width = compile_expression(&*path_layout.rect.width_reference, &component); let height = compile_expression(&*path_layout.rect.width_reference, &component); let offset = compile_expression(&*path_layout.offset_reference, &component); code_stream.push(quote! { solve_path_layout(&PathLayoutData { items: #slice, elements: &#path, x: #x_pos, y: #y_pos, width: #width, height: #height, offset: #offset, }); }); } } } } fn compute_layout(component: &Rc, repeated_element_names: &[Ident]) -> TokenStream { let mut layouts = vec![]; component.layout_constraints.borrow().iter().for_each(|layout| { let mut inverse_layout_tree = Vec::new(); collect_layouts_recursively(&mut inverse_layout_tree, layout, component); layouts.extend(inverse_layout_tree.iter().filter_map(|layout| match layout { LayoutTreeItem::GridLayout { var_creation_code, .. } => Some(var_creation_code.clone()), LayoutTreeItem::PathLayout(_) => None, })); inverse_layout_tree .iter() .rev() .for_each(|layout| layout.emit_solve_calls(component, &mut layouts)); }); let window_ref = window_ref_expression(component); quote! { fn layout_info(self: ::core::pin::Pin<&Self>) -> sixtyfps::re_exports::LayoutInfo { todo!("Implement in rust.rs") } fn compute_layout(self: ::core::pin::Pin<&Self>) { #![allow(unused)] use sixtyfps::re_exports::*; let dummy = Property::::default(); let _self = self; let window = #window_ref.clone(); #(#layouts)* #(self.#repeated_element_names.compute_layout();)* } } } fn compile_path_events(events: &crate::expression_tree::PathEvents) -> TokenStream { use lyon::path::Event; let mut coordinates = Vec::new(); let converted_events: Vec = events .iter() .map(|event| match event { Event::Begin { at } => { coordinates.push(at); quote!(sixtyfps::re_exports::PathEvent::Begin) } Event::Line { from, to } => { coordinates.push(from); coordinates.push(to); quote!(sixtyfps::re_exports::PathEvent::Line) } Event::Quadratic { from, ctrl, to } => { coordinates.push(from); coordinates.push(ctrl); coordinates.push(to); quote!(sixtyfps::re_exports::PathEvent::Quadratic) } Event::Cubic { from, ctrl1, ctrl2, to } => { coordinates.push(from); coordinates.push(ctrl1); coordinates.push(ctrl2); coordinates.push(to); quote!(sixtyfps::re_exports::PathEvent::Cubic) } Event::End { last, first, close } => { debug_assert_eq!(coordinates.first(), Some(&first)); debug_assert_eq!(coordinates.last(), Some(&last)); if *close { quote!(sixtyfps::re_exports::PathEvent::EndClosed) } else { quote!(sixtyfps::re_exports::PathEvent::EndOpen) } } }) .collect(); let coordinates: Vec = coordinates .into_iter() .map(|pt| { let x = pt.x; let y = pt.y; quote!(sixtyfps::re_exports::Point::new(#x, #y)) }) .collect(); quote!(sixtyfps::re_exports::SharedArray::::from_slice(&[#(#converted_events),*]), sixtyfps::re_exports::SharedArray::::from_slice(&[#(#coordinates),*])) } fn compile_path(path: &Path, component: &Rc) -> TokenStream { match path { Path::Elements(elements) => { let converted_elements: Vec = elements .iter() .map(|element| { let mut bindings = element .bindings .iter() .map(|(property, expr)| { let prop_ident = format_ident!("{}", property); let binding_expr = compile_expression(expr, component); quote!(#prop_ident: #binding_expr as _).to_string() }) .collect::>(); if bindings.len() < element.element_type.properties.len() { bindings.push("..Default::default()".into()) } let bindings = bindings.join(","); let ctor_format_string = element .element_type .native_class.rust_type_constructor .as_ref() .expect( "Unexpected error in type registry: path element is lacking rust type name", ); ctor_format_string .replace("{}", &bindings) .parse() .expect("Error parsing rust path element constructor") }) .collect(); quote!(sixtyfps::re_exports::PathData::Elements( sixtyfps::re_exports::SharedArray::::from_slice(&[#(#converted_elements),*]) )) } Path::Events(events) => { let events = compile_path_events(events); quote!(sixtyfps::re_exports::PathData::Events(#events)) } } }