/* LICENSE BEGIN This file is part of the SixtyFPS Project -- https://sixtyfps.io Copyright (c) 2020 Olivier Goffart Copyright (c) 2020 Simon Hausmann SPDX-License-Identifier: GPL-3.0-only This file is also available under commercial licensing terms. Please contact info@sixtyfps.io for more information. LICENSE END */ use crate::{dynamic_type, eval}; use core::convert::TryInto; use core::ptr::NonNull; use dynamic_type::{Instance, InstanceBox}; use expression_tree::NamedReference; use object_tree::{Element, ElementRc}; use sixtyfps_compilerlib::langtype::Type; use sixtyfps_compilerlib::layout::{Layout, LayoutConstraints, LayoutItem, PathLayout}; use sixtyfps_compilerlib::*; use sixtyfps_compilerlib::{expression_tree::Expression, langtype::PropertyLookupResult}; use sixtyfps_corelib::component::{Component, ComponentRefPin, ComponentVTable}; use sixtyfps_corelib::graphics::{Rect, Resource}; use sixtyfps_corelib::item_tree::{ ItemTreeNode, ItemVisitorRefMut, ItemVisitorVTable, TraversalOrder, VisitChildrenResult, }; use sixtyfps_corelib::items::{ Flickable, ItemRc, ItemRef, ItemVTable, ItemWeak, PropertyAnimation, Rectangle, }; use sixtyfps_corelib::layout::{LayoutInfo, Padding}; use sixtyfps_corelib::model::RepeatedComponent; use sixtyfps_corelib::model::Repeater; use sixtyfps_corelib::properties::InterpolatedPropertyValue; use sixtyfps_corelib::rtti::{self, AnimatedBindingKind, FieldOffset, PropertyInfo}; use sixtyfps_corelib::slice::Slice; use sixtyfps_corelib::window::ComponentWindow; use sixtyfps_corelib::{Color, Property, SharedString}; use std::collections::HashMap; use std::{pin::Pin, rc::Rc}; pub struct ComponentBox<'id> { instance: InstanceBox<'id>, component_type: Rc>, } impl<'id> ComponentBox<'id> { /// Borrow this component as a `Pin` pub fn borrow<'a>(&'a self) -> ComponentRefPin<'a> { self.borrow_instance().borrow() } /// Safety: the lifetime is not unique pub fn description(&self) -> Rc> { return self.component_type.clone(); } pub fn borrow_instance<'a>(&'a self) -> InstanceRef<'a, 'id> { InstanceRef { instance: self.instance.as_pin_ref(), component_type: &self.component_type } } pub fn window(&self) -> ComponentWindow { (*self .component_type .window_offset .apply_pin(self.instance.as_pin_ref()) .as_pin_ref() .unwrap()) .clone() } } impl<'id> Drop for ComponentBox<'id> { fn drop(&mut self) { let instance_ref = self.borrow_instance(); match eval::window_ref(instance_ref) { Some(window) => { let items = self .component_type .items .values() .map(|item_within_component| unsafe { item_within_component.item_from_component(instance_ref.as_ptr()) }) .collect::>(); window.free_graphics_resources(&Slice::from_slice(items.as_slice())); } None => {} } } } pub(crate) struct ItemWithinComponent { offset: usize, pub(crate) rtti: Rc, elem: ElementRc, } impl ItemWithinComponent { pub(crate) unsafe fn item_from_component( &self, mem: *const u8, ) -> Pin> { Pin::new_unchecked(vtable::VRef::from_raw( NonNull::from(self.rtti.vtable), NonNull::new(mem.add(self.offset) as _).unwrap(), )) } pub(crate) fn item_index(&self) -> usize { *self.elem.borrow().item_index.get().unwrap() } } pub(crate) struct PropertiesWithinComponent { pub(crate) offset: usize, pub(crate) prop: Box>, } pub(crate) struct RepeaterWithinComponent<'par_id, 'sub_id> { /// The component description of the items to repeat pub(crate) component_to_repeat: Rc>, /// The model pub(crate) model: Expression, /// Offset of the `Repeater` offset: FieldOffset, Repeater>, } impl RepeatedComponent for ErasedComponentBox { type Data = eval::Value; fn update(&self, index: usize, data: Self::Data) { generativity::make_guard!(guard); let s = self.unerase(guard); s.component_type.set_property(s.borrow(), "index", index.try_into().unwrap()).unwrap(); s.component_type.set_property(s.borrow(), "model_data", data).unwrap(); } fn listview_layout(self: Pin<&Self>, offset_y: &mut f32, viewport_width: Pin<&Property>) { generativity::make_guard!(guard); let s = self.unerase(guard); self.borrow().as_ref().apply_layout(Default::default()); s.component_type .set_property(s.borrow(), "y", eval::Value::Number(*offset_y as f64)) .expect("cannot set y"); let h: f32 = s .component_type .get_property(s.borrow(), "height") .expect("missing height") .try_into() .expect("height not the right type"); let w: f32 = s .component_type .get_property(s.borrow(), "width") .expect("missing width") .try_into() .expect("width not the right type"); *offset_y += h; let vp_w = viewport_width.get(); if vp_w < w { viewport_width.set(w); } } fn box_layout_data<'a>(self: Pin<&'a Self>) -> BoxLayoutCellData<'a> { generativity::make_guard!(guard); let s = self.unerase(guard); let root_item = &s.component_type.original.root_element.clone(); let get_prop = |name: &str| { let PropertyLookupResult { resolved_name, property_type } = root_item.borrow().lookup_property(name); if property_type == Type::Length { let nr = NamedReference::new(root_item, resolved_name.as_ref()); let p = get_property_ptr(&nr, s.borrow_instance()); // Safety: assuming get_property_ptr returned a valid pointer, // we know that `Type::Length` is a property of type `f32` Some(unsafe { &*(p as *const Property) }) } else { None } }; let root_c = &s.component_type.original.layouts.borrow().root_constraints; BoxLayoutCellData { constraint: self.borrow().as_ref().layout_info(), x: get_prop("x"), y: get_prop("y"), width: if root_c.fixed_width { None } else { get_prop("width") }, height: if root_c.fixed_height { None } else { get_prop("height") }, } } } impl Component for ErasedComponentBox { fn visit_children_item( self: Pin<&Self>, index: isize, order: TraversalOrder, visitor: ItemVisitorRefMut, ) -> VisitChildrenResult { self.borrow().as_ref().visit_children_item(index, order, visitor) } fn layout_info(self: Pin<&Self>) -> sixtyfps_corelib::layout::LayoutInfo { self.borrow().as_ref().layout_info() } fn apply_layout(self: Pin<&Self>, r: sixtyfps_corelib::graphics::Rect) { self.borrow().as_ref().apply_layout(r) } fn get_item_ref<'a>(self: Pin<&'a Self>, index: usize) -> Pin> { // We're having difficulties transferring the lifetime to a pinned reference // to the other ComponentVTable with the same life time. So skip the vtable // indirection and call our implementation directly. unsafe { get_item_ref(self.get_ref().borrow(), index) } } fn parent_item(self: Pin<&Self>, index: usize, result: &mut ItemWeak) { self.borrow().as_ref().parent_item(index, result) } } sixtyfps_corelib::ComponentVTable_static!(static COMPONENT_BOX_VT for ErasedComponentBox); pub(crate) struct ComponentExtraData { pub(crate) globals: HashMap>>, pub(crate) self_weak: once_cell::unsync::OnceCell>, } impl Default for ComponentExtraData { fn default() -> Self { Self { globals: HashMap::new(), self_weak: Default::default() } } } struct ErasedRepeaterWithinComponent<'id>(RepeaterWithinComponent<'id, 'static>); impl<'id, 'sub_id> From> for ErasedRepeaterWithinComponent<'id> { fn from(from: RepeaterWithinComponent<'id, 'sub_id>) -> Self { // Safety: this is safe as we erase the sub_id lifetim. // As long as when we get it back we get an unique lifetime with ErasedRepeaterWithinComponent::unerase Self(unsafe { core::mem::transmute::< RepeaterWithinComponent<'id, 'sub_id>, RepeaterWithinComponent<'id, 'static>, >(from) }) } } impl<'id> ErasedRepeaterWithinComponent<'id> { pub fn unerase<'a, 'sub_id>( &'a self, _guard: generativity::Guard<'sub_id>, ) -> &'a RepeaterWithinComponent<'id, 'sub_id> { // Safety: we just go from 'static to an unique lifetime unsafe { core::mem::transmute::< &'a RepeaterWithinComponent<'id, 'static>, &'a RepeaterWithinComponent<'id, 'sub_id>, >(&self.0) } } /// Return a repeater with a component with a 'static lifetime /// /// Safety: one should ensure that the inner component is not mixed with other inner component unsafe fn get_untaged(&self) -> &RepeaterWithinComponent<'id, 'static> { &self.0 } } type Callback = sixtyfps_corelib::Callback<[eval::Value], eval::Value>; /// ComponentDescription is a representation of a component suitable for interpretation /// /// It contains information about how to create and destroy the Component. /// Its first member is the ComponentVTable for this component, since it is a `#[repr(C)]` /// structure, it is valid to cast a pointer to the ComponentVTable back to a /// ComponentDescription to access the extra field that are needed at runtime #[repr(C)] pub struct ComponentDescription<'id> { pub(crate) ct: ComponentVTable, /// INVARIANT: both dynamic_type and item_tree have the same lifetime id. Here it is erased to 'static dynamic_type: Rc>, item_tree: Vec>>, pub(crate) items: HashMap, pub(crate) custom_properties: HashMap, pub(crate) custom_callbacks: HashMap, Callback>>, repeater: Vec>, /// Map the Element::id of the repeater to the index in the `repeater` vec pub repeater_names: HashMap, /// Offset to a Option pub(crate) parent_component_offset: Option, Option>>>, /// Offset to the window reference pub(crate) window_offset: FieldOffset, Option>, /// Offset of a ComponentExtraData pub(crate) extra_data_offset: FieldOffset, ComponentExtraData>, /// Keep the Rc alive pub(crate) original: Rc, } extern "C" fn visit_children_item( component: ComponentRefPin, index: isize, order: TraversalOrder, v: ItemVisitorRefMut, ) -> VisitChildrenResult { generativity::make_guard!(guard); let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) }; let comp_rc = instance_ref.self_weak().get().unwrap().upgrade().unwrap(); sixtyfps_corelib::item_tree::visit_item_tree( instance_ref.instance, &vtable::VRc::into_dyn(comp_rc), instance_ref.component_type.item_tree.as_slice().into(), index, order, v, |_, order, visitor, index| { // `ensure_updated` needs a 'static lifetime so we must call get_untaged. // Safety: we do not mix the component with other component id in this function let rep_in_comp = unsafe { instance_ref.component_type.repeater[index].get_untaged() }; let repeater = rep_in_comp.offset.apply_pin(instance_ref.instance); let init = || { instantiate( rep_in_comp.component_to_repeat.clone(), Some(component), #[cfg(target_arch = "wasm32")] String::new(), ) }; if let Some(lv) = &rep_in_comp .component_to_repeat .original .parent_element .upgrade() .unwrap() .borrow() .repeated .as_ref() .unwrap() .is_listview { let assume_property_f32 = |prop| unsafe { Pin::new_unchecked(&*(prop as *const Property)) }; let get_prop = |nr: &NamedReference| -> f32 { eval::load_property(instance_ref, &nr.element.upgrade().unwrap(), &nr.name) .unwrap() .try_into() .unwrap() }; repeater.ensure_updated_listview( init, assume_property_f32(get_property_ptr(&lv.viewport_width, instance_ref)), assume_property_f32(get_property_ptr(&lv.viewport_height, instance_ref)), assume_property_f32(get_property_ptr(&lv.viewport_y, instance_ref)), get_prop(&lv.listview_width), assume_property_f32(get_property_ptr(&lv.listview_height, instance_ref)), ); } else { repeater.ensure_updated(init); } repeater.visit(order, visitor) }, ) } /// Information attached to a builtin item pub(crate) struct ItemRTTI { vtable: &'static ItemVTable, type_info: dynamic_type::StaticTypeInfo, pub(crate) properties: HashMap<&'static str, Box>, pub(crate) callbacks: HashMap<&'static str, Box>, } fn rtti_for>( ) -> (&'static str, Rc) { let rtti = ItemRTTI { vtable: T::static_vtable(), type_info: dynamic_type::StaticTypeInfo::new::(), properties: T::properties() .into_iter() .map(|(k, v)| (k, Box::new(v) as Box)) .collect(), callbacks: T::callbacks() .into_iter() .map(|(k, v)| (k, Box::new(v) as Box)) .collect(), }; (T::name(), Rc::new(rtti)) } /// Flickable is special because some of its property applies to the viewport. /// This adds the viewport property in the flickable's property list fn rtti_for_flickable() -> (&'static str, Rc) { let (name, mut rtti) = rtti_for::(); use rtti::BuiltinItem; let rect_prop = &["viewport_x", "viewport_y", "viewport_width", "viewport_height"]; struct FlickableViewPortPropertyInfo(&'static dyn rtti::PropertyInfo); fn viewport(flick: Pin) -> Pin<&Rectangle> { Flickable::FIELD_OFFSETS .viewport .apply_pin(ItemRef::downcast_pin::(flick).unwrap()) } impl eval::ErasedPropertyInfo for FlickableViewPortPropertyInfo { fn get(&self, item: Pin) -> eval::Value { (*self.0).get(viewport(item)).unwrap() } fn set( &self, item: Pin, value: eval::Value, animation: Option, ) { (*self.0).set(viewport(item), value, animation).unwrap() } fn set_binding( &self, item: Pin, binding: Box eval::Value>, animation: AnimatedBindingKind, ) { (*self.0).set_binding(viewport(item), binding, animation).unwrap(); } fn offset(&self) -> usize { (*self.0).offset() + Flickable::FIELD_OFFSETS.viewport.get_byte_offset() } unsafe fn link_two_ways(&self, item: Pin, property2: *const ()) { (*self.0).link_two_ways(viewport(item), property2) } } Rc::get_mut(&mut rtti).unwrap().properties.extend( Rectangle::properties().into_iter().filter_map(|(k, v)| { Some(( *rect_prop.iter().find(|x| x.ends_with(k))?, Box::new(FlickableViewPortPropertyInfo(v)) as Box, )) }), ); (name, rtti) } /// Create a ComponentDescription from a source. /// The path corresponding to the source need to be passed as well (path is used for diagnostics /// and loading relative assets) pub async fn load<'id>( source: String, path: std::path::PathBuf, compiler_config: CompilerConfiguration, guard: generativity::Guard<'id>, ) -> (Result>, ()>, sixtyfps_compilerlib::diagnostics::BuildDiagnostics) { let (syntax_node, diag) = parser::parse(source, Some(path.as_path())); if diag.has_error() { let mut d = sixtyfps_compilerlib::diagnostics::BuildDiagnostics::default(); d.add(diag); return (Err(()), d); } let (doc, diag) = compile_syntax_node(syntax_node, diag, compiler_config).await; if diag.has_error() { return (Err(()), diag); } (Ok(generate_component(&doc.root_component, guard)), diag) } fn generate_component<'id>( component: &Rc, guard: generativity::Guard<'id>, ) -> Rc> { let mut rtti = HashMap::new(); { use sixtyfps_corelib::items::*; rtti.extend( [ rtti_for::(), rtti_for::(), rtti_for::(), rtti_for::(), rtti_for::(), rtti_for::(), rtti_for::(), rtti_for::(), rtti_for_flickable(), rtti_for::(), rtti_for::(), rtti_for::(), rtti_for::(), ] .iter() .cloned(), ); trait NativeHelper { fn push(rtti: &mut HashMap<&str, Rc>); } impl NativeHelper for () { fn push(_rtti: &mut HashMap<&str, Rc>) {} } impl< T: 'static + Default + rtti::BuiltinItem + vtable::HasStaticVTable, Next: NativeHelper, > NativeHelper for (T, Next) { fn push(rtti: &mut HashMap<&str, Rc>) { let info = rtti_for::(); rtti.insert(info.0, info.1); Next::push(rtti); } } sixtyfps_rendering_backend_default::NativeWidgets::push(&mut rtti); } let rtti = Rc::new(rtti); let mut tree_array = vec![]; let mut items_types = HashMap::::new(); let mut builder = dynamic_type::TypeBuilder::new(guard); let mut repeater = vec![]; let mut repeater_names = HashMap::new(); generator::build_array_helper( component, |rc_item, child_offset, parent_index, is_flickable_rect| { let item = rc_item.borrow(); if is_flickable_rect { use vtable::HasStaticVTable; let offset = items_types[&item.id].offset + Flickable::FIELD_OFFSETS.viewport.get_byte_offset(); tree_array.push(ItemTreeNode::Item { item: unsafe { vtable::VOffset::from_raw(Rectangle::static_vtable(), offset) }, children_index: tree_array.len() as u32 + 1, chilren_count: item.children.len() as _, parent_index, }); } else if let Some(repeated) = &item.repeated { tree_array.push(ItemTreeNode::DynamicTree { index: repeater.len(), parent_index }); let base_component = item.base_type.as_component(); repeater_names.insert(item.id.clone(), repeater.len()); generativity::make_guard!(guard); repeater.push( RepeaterWithinComponent { component_to_repeat: generate_component(base_component, guard), offset: builder.add_field_type::>(), model: repeated.model.clone(), } .into(), ); } else { let rt = rtti.get(&*item.base_type.as_native().class_name).unwrap_or_else(|| { panic!("Native type not registered: {}", item.base_type.as_native().class_name) }); let offset = builder.add_field(rt.type_info); tree_array.push(ItemTreeNode::Item { item: unsafe { vtable::VOffset::from_raw(rt.vtable, offset) }, children_index: child_offset, chilren_count: if generator::is_flickable(rc_item) { 1 } else { item.children.len() as _ }, parent_index, }); items_types.insert( item.id.clone(), ItemWithinComponent { offset, rtti: rt.clone(), elem: rc_item.clone() }, ); } }, ); let mut custom_properties = HashMap::new(); let mut custom_callbacks = HashMap::new(); fn property_info( ) -> (Box>, dynamic_type::StaticTypeInfo) where T: std::convert::TryInto, eval::Value: std::convert::TryInto, { // Fixme: using u8 in PropertyInfo<> is not sound, we would need to materialize a type for out component ( Box::new(unsafe { vtable::FieldOffset::, _>::new_from_offset_pinned(0) }), dynamic_type::StaticTypeInfo::new::>(), ) } fn animated_property_info( ) -> (Box>, dynamic_type::StaticTypeInfo) where T: std::convert::TryInto, eval::Value: std::convert::TryInto, { // Fixme: using u8 in PropertyInfo<> is not sound, we would need to materialize a type for out component ( Box::new(unsafe { rtti::MaybeAnimatedPropertyInfoWrapper( vtable::FieldOffset::, _>::new_from_offset_pinned(0), ) }), dynamic_type::StaticTypeInfo::new::>(), ) } for (name, decl) in &component.root_element.borrow().property_declarations { if decl.is_alias.is_some() { continue; } let (prop, type_info) = match &decl.property_type { Type::Float32 => animated_property_info::(), Type::Int32 => animated_property_info::(), Type::String => property_info::(), Type::Color => animated_property_info::(), Type::Duration => animated_property_info::(), Type::Angle => animated_property_info::(), Type::Length => animated_property_info::(), Type::LogicalLength => animated_property_info::(), Type::Resource => property_info::(), Type::Bool => property_info::(), Type::Callback { .. } => { custom_callbacks.insert(name.clone(), builder.add_field_type::()); continue; } Type::Object { name: Some(name), .. } if name.ends_with("::StateInfo") => { property_info::() } Type::Object { .. } => property_info::(), Type::Array(_) => property_info::(), Type::Percent => property_info::(), Type::Enumeration(e) => match e.name.as_ref() { "LayoutAlignment" => property_info::(), "TextHorizontalAlignment" => { property_info::() } "TextVerticalAlignment" => { property_info::() } "TextWrap" => property_info::(), "TextOverflow" => property_info::(), "ImageFit" => property_info::(), "FillRule" => property_info::(), _ => panic!("unkown enum"), }, _ => panic!("bad type"), }; custom_properties.insert( name.clone(), PropertiesWithinComponent { offset: builder.add_field(type_info), prop }, ); } if component.parent_element.upgrade().is_some() { let (prop, type_info) = property_info::(); custom_properties.insert( "index".into(), PropertiesWithinComponent { offset: builder.add_field(type_info), prop }, ); // FIXME: make it a property for the correct type instead of being generic let (prop, type_info) = property_info::(); custom_properties.insert( "model_data".into(), PropertiesWithinComponent { offset: builder.add_field(type_info), prop }, ); } else { let (prop, type_info) = property_info::(); custom_properties.insert( "scale_factor".into(), PropertiesWithinComponent { offset: builder.add_field(type_info), prop }, ); } let parent_component_offset = if component.parent_element.upgrade().is_some() { Some(builder.add_field_type::>()) } else { None }; let window_offset = builder.add_field_type::>(); let extra_data_offset = builder.add_field_type::(); let t = ComponentVTable { visit_children_item, layout_info, apply_layout, get_item_ref, parent_item, drop_in_place, dealloc, }; let t = ComponentDescription { ct: t, dynamic_type: builder.build(), item_tree: tree_array, items: items_types, custom_properties, custom_callbacks, original: component.clone(), repeater, repeater_names, parent_component_offset, window_offset, extra_data_offset, }; Rc::new(t) } pub fn animation_for_property( component: InstanceRef, element: &Element, property_name: &str, ) -> AnimatedBindingKind { match element.property_animations.get(property_name) { Some(sixtyfps_compilerlib::object_tree::PropertyAnimation::Static(anim_elem)) => { AnimatedBindingKind::Animation(eval::new_struct_with_bindings( &anim_elem.borrow().bindings, &mut eval::EvalLocalContext::from_component_instance(component), )) } Some(sixtyfps_compilerlib::object_tree::PropertyAnimation::Transition { animations, state_ref, }) => { let component_ptr = component.as_ptr(); let vtable = NonNull::from(&component.component_type.ct).cast(); let animations = animations.clone(); let state_ref = state_ref.clone(); AnimatedBindingKind::Transition(Box::new( move || -> (PropertyAnimation, sixtyfps_corelib::animations::Instant) { generativity::make_guard!(guard); let component = unsafe { InstanceRef::from_pin_ref( Pin::new_unchecked(vtable::VRef::from_raw( vtable, NonNull::new_unchecked(component_ptr as *mut u8), )), guard, ) }; let mut context = eval::EvalLocalContext::from_component_instance(component); let state = eval::eval_expression(&state_ref, &mut context); let state_info: sixtyfps_corelib::properties::StateInfo = state.try_into().unwrap(); for a in &animations { if (a.is_out && a.state_id == state_info.previous_state) || (!a.is_out && a.state_id == state_info.current_state) { return ( eval::new_struct_with_bindings( &a.animation.borrow().bindings, &mut context, ), state_info.change_time, ); } } return Default::default(); }, )) } None => AnimatedBindingKind::NotAnimated, } } pub fn instantiate<'id>( component_type: Rc>, parent_ctx: Option, #[cfg(target_arch = "wasm32")] canvas_id: String, ) -> vtable::VRc { let mut instance = component_type.dynamic_type.clone().create_instance(); if let Some(parent) = parent_ctx { generativity::make_guard!(guard); // This is fine since we can only be called with a component that with our vtable which is a ComponentDescription let parent_instance_ref = unsafe { InstanceRef::from_pin_ref(parent, guard) }; let window = parent_instance_ref .component_type .window_offset .apply(parent_instance_ref.as_ref()) .as_ref() .unwrap() .clone(); *component_type.parent_component_offset.unwrap().apply_mut(instance.as_mut()) = Some(parent); *component_type.window_offset.apply_mut(instance.as_mut()) = Some(window); } else { let extra_data = component_type.extra_data_offset.apply_mut(instance.as_mut()); extra_data.globals = component_type .original .used_global .borrow() .iter() .map(|g| (g.id.clone(), crate::global_component::instantiate(g))) .collect(); #[cfg(not(target_arch = "wasm32"))] let window = Some(sixtyfps_rendering_backend_default::backend().create_window()); #[cfg(target_arch = "wasm32")] let window = Some(sixtyfps_rendering_backend_gl::create_gl_window_with_canvas_id(canvas_id)); *component_type.window_offset.apply_mut(instance.as_mut()) = window; } let component_box = ComponentBox { instance, component_type: component_type.clone() }; let instance_ref = component_box.borrow_instance(); sixtyfps_corelib::component::init_component_items( instance_ref.instance, instance_ref.component_type.item_tree.as_slice().into(), &eval::window_ref(instance_ref).unwrap(), ); for item_within_component in component_type.items.values() { unsafe { let item = item_within_component.item_from_component(instance_ref.as_ptr()); let elem = item_within_component.elem.borrow(); for (unresolved_prop_name, expr) in &elem.bindings { let PropertyLookupResult { resolved_name, property_type } = elem.lookup_property(unresolved_prop_name.as_str()); if let Type::Callback { .. } = property_type { let expr = expr.clone(); let component_type = component_type.clone(); let instance = component_box.instance.as_ptr(); let c = Pin::new_unchecked(vtable::VRef::from_raw( NonNull::from(&component_type.ct).cast(), instance.cast(), )); if let Some(callback) = item_within_component.rtti.callbacks.get(resolved_name.as_ref()) { callback.set_handler( item, Box::new(move |args| { generativity::make_guard!(guard); let mut local_context = eval::EvalLocalContext::from_function_arguments( InstanceRef::from_pin_ref(c, guard), args.iter().cloned().collect(), ); eval::eval_expression(&expr, &mut local_context) }), ) } else if let Some(callback_offset) = component_type.custom_callbacks.get(resolved_name.as_ref()) { let callback = callback_offset.apply(instance_ref.as_ref()); callback.set_handler(move |args| { generativity::make_guard!(guard); let mut local_context = eval::EvalLocalContext::from_function_arguments( InstanceRef::from_pin_ref(c, guard), args.iter().cloned().collect(), ); eval::eval_expression(&expr, &mut local_context) }) } else { panic!("unkown callback {}", unresolved_prop_name) } } else { if let Some(prop_rtti) = item_within_component.rtti.properties.get(resolved_name.as_ref()) { let maybe_animation = animation_for_property(instance_ref, &elem, resolved_name.as_ref()); let mut e = Some(&expr.expression); while let Some(Expression::TwoWayBinding(nr, next)) = &e { // Safety: The compiler must have ensured that the properties exist and are of the same type prop_rtti.link_two_ways(item, get_property_ptr(&nr, instance_ref)); e = next.as_deref(); } if let Some(e) = e { if e.is_constant() { prop_rtti.set( item, eval::eval_expression( e, &mut eval::EvalLocalContext::from_component_instance( instance_ref, ), ), maybe_animation.as_animation(), ); } else { let e = e.clone(); let component_type = component_type.clone(); let instance = component_box.instance.as_ptr(); let c = Pin::new_unchecked(vtable::VRef::from_raw( NonNull::from(&component_type.ct).cast(), instance.cast(), )); prop_rtti.set_binding( item, Box::new(move || { generativity::make_guard!(guard); eval::eval_expression( &e, &mut eval::EvalLocalContext::from_component_instance( InstanceRef::from_pin_ref(c, guard), ), ) }), maybe_animation, ); } } } else if let Some(PropertiesWithinComponent { offset, prop: prop_info, .. }) = component_type.custom_properties.get(resolved_name.as_ref()) { let c = Pin::new_unchecked(vtable::VRef::from_raw( NonNull::from(&component_type.ct).cast(), component_box.instance.as_ptr().cast(), )); let is_state_info = match property_type { Type::Object { name: Some(name), .. } if name.ends_with("::StateInfo") => { true } _ => false, }; if is_state_info { let prop = Pin::new_unchecked( &*(instance_ref.as_ptr().add(*offset) as *const Property), ); let e = expr.expression.clone(); sixtyfps_corelib::properties::set_state_binding(prop, move || { generativity::make_guard!(guard); eval::eval_expression( &e, &mut eval::EvalLocalContext::from_component_instance( InstanceRef::from_pin_ref(c, guard), ), ) .try_into() .unwrap() }); continue; } let maybe_animation = animation_for_property( instance_ref, &component_type.original.root_element.borrow(), resolved_name.as_ref(), ); let item = Pin::new_unchecked(&*instance_ref.as_ptr().add(*offset)); let mut e = Some(&expr.expression); while let Some(Expression::TwoWayBinding(nr, next)) = &e { // Safety: The compiler must have ensured that the properties exist and are of the same type prop_info.link_two_ways(item, get_property_ptr(&nr, instance_ref)); e = next.as_deref(); } if let Some(e) = e { if e.is_constant() { let v = eval::eval_expression( e, &mut eval::EvalLocalContext::from_component_instance( instance_ref, ), ); prop_info.set(item, v, None).unwrap(); } else { let e = e.clone(); prop_info .set_binding( item, Box::new(move || { generativity::make_guard!(guard); eval::eval_expression( &e, &mut eval::EvalLocalContext::from_component_instance(InstanceRef::from_pin_ref(c, guard)), ) }), maybe_animation, ) .unwrap(); } } } else { panic!("unkown property {}", unresolved_prop_name); } } } } } for rep_in_comp in &component_type.repeater { generativity::make_guard!(guard); let rep_in_comp = rep_in_comp.unerase(guard); let repeater = rep_in_comp.offset.apply_pin(instance_ref.instance); let expr = rep_in_comp.model.clone(); let component_type = component_type.clone(); let instance = component_box.instance.as_ptr(); let c = unsafe { Pin::new_unchecked(vtable::VRef::from_raw( NonNull::from(&component_type.ct).cast(), instance.cast(), )) }; repeater.set_model_binding(move || { generativity::make_guard!(guard); let m = eval::eval_expression( &expr, &mut eval::EvalLocalContext::from_component_instance(unsafe { InstanceRef::from_pin_ref(c, guard) }), ); sixtyfps_corelib::model::ModelHandle(Some(Rc::new( crate::value_model::ValueModel::new(m), ))) }); } let comp_rc = vtable::VRc::new(ErasedComponentBox::from(component_box)); { generativity::make_guard!(guard); let comp = comp_rc.unerase(guard); let weak = vtable::VRc::downgrade(&comp_rc); let instance_ref = comp.borrow_instance(); instance_ref.self_weak().set(weak).ok(); for extra_init_code in component_type.original.setup_code.borrow().iter() { eval::eval_expression( extra_init_code, &mut eval::EvalLocalContext::from_component_instance(instance_ref), ); } } comp_rc } fn get_property_ptr(nr: &NamedReference, instance: InstanceRef) -> *const () { let element = nr.element.upgrade().unwrap(); generativity::make_guard!(guard); let enclosing_component = eval::enclosing_component_for_element(&element, instance, guard); let element = element.borrow(); if element.id == element.enclosing_component.upgrade().unwrap().root_element.borrow().id { if let Some(x) = enclosing_component.component_type.custom_properties.get(&nr.name) { return unsafe { enclosing_component.as_ptr().add(x.offset).cast() }; }; }; let item_info = enclosing_component .component_type .items .get(element.id.as_str()) .unwrap_or_else(|| panic!("Unkown element for {}.{}", element.id, nr.name)); core::mem::drop(element); let item = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) }; unsafe { item.as_ptr().add(item_info.rtti.properties.get(nr.name.as_str()).unwrap().offset()).cast() } } use sixtyfps_corelib::layout::*; struct LayoutWithCells<'a, C> { geometry: &'a sixtyfps_compilerlib::layout::LayoutGeometry, cells: Vec, spacing: f32, padding: Padding, } pub struct ErasedComponentBox(ComponentBox<'static>); impl ErasedComponentBox { pub fn unerase<'a, 'id>( &'a self, _guard: generativity::Guard<'id>, ) -> Pin<&'a ComponentBox<'id>> { Pin::new( //Safety: 'id is unique because of `_guard` unsafe { core::mem::transmute::<&ComponentBox<'static>, &ComponentBox<'id>>(&self.0) }, ) } pub fn borrow<'a>(&'a self) -> ComponentRefPin<'a> { // Safety: it is safe to access self.0 here because the 'id lifetime does not leak self.0.borrow() } pub fn window(&self) -> ComponentWindow { self.0.window() } } impl<'id> From> for ErasedComponentBox { fn from(inner: ComponentBox<'id>) -> Self { // Safety: Nothing access the component directly, we only access it through unerased where // the lifetime is unique again unsafe { ErasedComponentBox(core::mem::transmute::, ComponentBox<'static>>( inner, )) } } } type RepeatedComponentRc = vtable::VRc; enum BoxLayoutCellTmpData<'a> { Item(BoxLayoutCellData<'a>), Repeater(Vec), } impl<'a> BoxLayoutCellTmpData<'a> { fn into_cells(cells: &'a [Self]) -> Vec> { let mut c = Vec::with_capacity(cells.len()); for x in cells.iter() { match x { BoxLayoutCellTmpData::Item(cell) => { c.push((*cell).clone()); } BoxLayoutCellTmpData::Repeater(vec) => { c.extend(vec.iter().map(|x| x.as_pin_ref().box_layout_data())) } } } c } } #[derive(derive_more::From)] enum LayoutTreeItem<'a> { GridLayout(LayoutWithCells<'a, GridLayoutCellData<'a>>), BoxLayout( LayoutWithCells<'a, BoxLayoutCellTmpData<'a>>, bool, sixtyfps_corelib::layout::LayoutAlignment, ), PathLayout(&'a PathLayout), } impl<'a> LayoutTreeItem<'a> { fn layout_info(&self) -> LayoutInfo { match self { LayoutTreeItem::GridLayout(grid_layout) => grid_layout_info( &Slice::from(grid_layout.cells.as_slice()), grid_layout.spacing, &grid_layout.padding, ), LayoutTreeItem::BoxLayout(box_layout, is_horizontal, alignment) => { let cells = BoxLayoutCellTmpData::into_cells(&box_layout.cells); box_layout_info( &Slice::from(cells.as_slice()), box_layout.spacing, &box_layout.padding, *alignment, *is_horizontal, ) } LayoutTreeItem::PathLayout(_) => todo!(), } } } fn get_layout_info<'a, 'b>( item: &'a LayoutItem, component: InstanceRef<'a, '_>, layout_tree: &'b mut Vec>, window: &ComponentWindow, ) -> LayoutInfo { let layout_info = item.layout.as_ref().map(|l| { let layout_tree_item = collect_layouts_recursively(layout_tree, l, component, window); layout_tree_item.layout_info() }); let elem_info = item.element.as_ref().map(|elem| { let item = &component .component_type .items .get(elem.borrow().id.as_str()) .unwrap_or_else(|| panic!("Internal error: Item {} not found", elem.borrow().id)); unsafe { item.item_from_component(component.as_ptr()).as_ref().layouting_info(window) } }); match (layout_info, elem_info) { (None, None) => Default::default(), (None, Some(x)) => x, (Some(x), None) => x, (Some(layout_info), Some(elem_info)) => layout_info.merge(&elem_info), } } fn fill_layout_info_constraints( layout_info: &mut LayoutInfo, constraints: &LayoutConstraints, expr_eval: &impl Fn(&NamedReference) -> f32, ) { constraints.minimum_width.as_ref().map(|e| layout_info.min_width = expr_eval(e)); constraints.maximum_width.as_ref().map(|e| layout_info.max_width = expr_eval(e)); constraints.minimum_height.as_ref().map(|e| layout_info.min_height = expr_eval(e)); constraints.maximum_height.as_ref().map(|e| layout_info.max_height = expr_eval(e)); constraints.horizontal_stretch.as_ref().map(|e| layout_info.horizontal_stretch = expr_eval(e)); constraints.vertical_stretch.as_ref().map(|e| layout_info.vertical_stretch = expr_eval(e)); } fn collect_layouts_recursively<'a, 'b>( layout_tree: &'b mut Vec>, layout: &'a Layout, component: InstanceRef<'a, '_>, window: &ComponentWindow, ) -> &'b LayoutTreeItem<'a> { let assume_property_f32 = |nr: &Option| { nr.as_ref().map(|nr| { let p = get_property_ptr(nr, component); unsafe { &*(p as *const Property) } }) }; let expr_eval = |nr: &NamedReference| -> f32 { eval::load_property(component, &nr.element.upgrade().unwrap(), nr.name.as_ref()) .unwrap() .try_into() .unwrap() }; match layout { Layout::GridLayout(grid_layout) => { let cells = grid_layout .elems .iter() .map(|cell| { let mut layout_info = get_layout_info(&cell.item, component, layout_tree, window); fill_layout_info_constraints( &mut layout_info, &cell.item.constraints, &expr_eval, ); let rect = cell.item.rect(); GridLayoutCellData { x: assume_property_f32(&rect.x_reference), y: assume_property_f32(&rect.y_reference), width: assume_property_f32(&rect.width_reference), height: assume_property_f32(&rect.height_reference), col: cell.col, row: cell.row, colspan: cell.colspan, rowspan: cell.rowspan, constraint: layout_info, } }) .collect(); let spacing = grid_layout.geometry.spacing.as_ref().map_or(0., expr_eval); let padding = Padding { left: grid_layout.geometry.padding.left.as_ref().map_or(0., expr_eval), right: grid_layout.geometry.padding.right.as_ref().map_or(0., expr_eval), top: grid_layout.geometry.padding.top.as_ref().map_or(0., expr_eval), bottom: grid_layout.geometry.padding.bottom.as_ref().map_or(0., expr_eval), }; layout_tree.push( LayoutWithCells { geometry: &grid_layout.geometry, cells, spacing, padding }.into(), ); } Layout::BoxLayout(box_layout) => { let mut make_box_layout_cell_data = |cell| { let mut layout_info = get_layout_info(cell, component, layout_tree, window); fill_layout_info_constraints(&mut layout_info, &cell.constraints, &expr_eval); let rect = cell.rect(); BoxLayoutCellData { x: assume_property_f32(&rect.x_reference), y: assume_property_f32(&rect.y_reference), width: assume_property_f32(&rect.width_reference), height: assume_property_f32(&rect.height_reference), constraint: layout_info, } }; let cells = box_layout .elems .iter() .map(|item| match &item.element { Some(elem) if elem.borrow().repeated.is_some() => { generativity::make_guard!(guard); let rep = get_repeater_by_name(component, elem.borrow().id.as_str(), guard); rep.0.as_ref().ensure_updated(|| { instantiate( rep.1.clone(), Some(component.borrow()), #[cfg(target_arch = "wasm32")] String::new(), ) }); BoxLayoutCellTmpData::Repeater( rep.0.as_ref().components_vec().into_iter().collect(), ) } _ => BoxLayoutCellTmpData::Item(make_box_layout_cell_data(item)), }) .collect::>(); let spacing = box_layout.geometry.spacing.as_ref().map_or(0., expr_eval); let padding = Padding { left: box_layout.geometry.padding.left.as_ref().map_or(0., expr_eval), right: box_layout.geometry.padding.right.as_ref().map_or(0., expr_eval), top: box_layout.geometry.padding.top.as_ref().map_or(0., expr_eval), bottom: box_layout.geometry.padding.bottom.as_ref().map_or(0., expr_eval), }; let alignment = box_layout .geometry .alignment .as_ref() .map(|nr| { eval::load_property(component, &nr.element.upgrade().unwrap(), &nr.name) .unwrap() .try_into() .unwrap_or_default() }) .unwrap_or_default(); layout_tree.push(LayoutTreeItem::BoxLayout( LayoutWithCells { geometry: &box_layout.geometry, cells, spacing, padding }, box_layout.is_horizontal, alignment, )); } Layout::PathLayout(layout) => layout_tree.push(layout.into()), } layout_tree.last().unwrap() } impl<'a> LayoutTreeItem<'a> { fn solve(&self, instance_ref: InstanceRef) { let resolve_prop_ref = |prop_ref: &Option| { prop_ref.as_ref().map_or(0., |nr| { eval::load_property(instance_ref, &nr.element.upgrade().unwrap(), &nr.name) .unwrap() .try_into() .unwrap_or(0.) }) }; match self { Self::GridLayout(grid_layout) => { solve_grid_layout(&GridLayoutData { width: resolve_prop_ref(&grid_layout.geometry.rect.width_reference), height: resolve_prop_ref(&grid_layout.geometry.rect.height_reference), x: resolve_prop_ref(&grid_layout.geometry.rect.x_reference), y: resolve_prop_ref(&grid_layout.geometry.rect.y_reference), spacing: grid_layout.spacing, padding: &grid_layout.padding, cells: Slice::from(grid_layout.cells.as_slice()), }); } Self::BoxLayout(box_layout, is_horizontal, alignment) => { let cells = BoxLayoutCellTmpData::into_cells(&box_layout.cells); solve_box_layout( &BoxLayoutData { width: resolve_prop_ref(&box_layout.geometry.rect.width_reference), height: resolve_prop_ref(&box_layout.geometry.rect.height_reference), x: resolve_prop_ref(&box_layout.geometry.rect.x_reference), y: resolve_prop_ref(&box_layout.geometry.rect.y_reference), spacing: box_layout.spacing, padding: &box_layout.padding, cells: Slice::from(cells.as_slice()), alignment: *alignment, }, *is_horizontal, ); } Self::PathLayout(path_layout) => { use sixtyfps_corelib::layout::*; let mut items = vec![]; for elem in &path_layout.elements { let mut push_layout_data = |elem: &ElementRc, instance_ref: InstanceRef| { let item_info = &instance_ref.component_type.items[elem.borrow().id.as_str()]; let get_prop = |name| { item_info.rtti.properties.get(name).map(|p| unsafe { &*(instance_ref.as_ptr().add(item_info.offset).add(p.offset()) as *const Property) }) }; let item = unsafe { item_info.item_from_component(instance_ref.as_ptr()) }; let get_prop_value = |name| { item_info .rtti .properties .get(name) .map(|p| p.get(item)) .unwrap_or_default() }; items.push(PathLayoutItemData { x: get_prop("x"), y: get_prop("y"), width: get_prop_value("width").try_into().unwrap_or_default(), height: get_prop_value("height").try_into().unwrap_or_default(), }); }; if elem.borrow().repeated.is_none() { push_layout_data(elem, instance_ref) } else { generativity::make_guard!(guard); let repeater = get_repeater_by_name(instance_ref, elem.borrow().id.as_str(), guard); let vec = repeater.0.components_vec(); for sub_comp in vec.iter() { generativity::make_guard!(guard); push_layout_data( &elem.borrow().base_type.as_component().root_element, sub_comp.unerase(guard).borrow_instance(), ) } } } let path_elements = eval::convert_path( &path_layout.path, &mut eval::EvalLocalContext::from_component_instance(instance_ref), ); solve_path_layout(&PathLayoutData { items: Slice::from(items.as_slice()), elements: &path_elements, x: resolve_prop_ref(&path_layout.rect.x_reference), y: resolve_prop_ref(&path_layout.rect.y_reference), width: resolve_prop_ref(&path_layout.rect.width_reference), height: resolve_prop_ref(&path_layout.rect.height_reference), offset: resolve_prop_ref(&Some(path_layout.offset_reference.clone())), }); } } } } pub fn get_repeater_by_name<'a, 'id>( instance_ref: InstanceRef<'a, '_>, name: &str, guard: generativity::Guard<'id>, ) -> (std::pin::Pin<&'a Repeater>, Rc>) { let rep_index = instance_ref.component_type.repeater_names[name]; let rep_in_comp = instance_ref.component_type.repeater[rep_index].unerase(guard); (rep_in_comp.offset.apply_pin(instance_ref.instance), rep_in_comp.component_to_repeat.clone()) } extern "C" fn layout_info(component: ComponentRefPin) -> LayoutInfo { generativity::make_guard!(guard); // This is fine since we can only be called with a component that with our vtable which is a ComponentDescription let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) }; let component_layouts = instance_ref.component_type.original.layouts.borrow(); let result = if let Some(idx) = component_layouts.main_layout { let mut inverse_layout_tree = Default::default(); collect_layouts_recursively( &mut inverse_layout_tree, &component_layouts[idx], instance_ref, &eval::window_ref(instance_ref).unwrap(), ) .layout_info() } else { instance_ref.root_item().as_ref().layouting_info(&eval::window_ref(instance_ref).unwrap()) }; if component_layouts.root_constraints.has_explicit_restrictions() { let mut info = LayoutInfo::default(); fill_layout_info_constraints( &mut info, &component_layouts.root_constraints, &|nr: &NamedReference| { eval::load_property(instance_ref, &nr.element.upgrade().unwrap(), nr.name.as_ref()) .unwrap() .try_into() .unwrap() }, ); result.merge(&info) } else { result } } extern "C" fn apply_layout(component: ComponentRefPin, _r: sixtyfps_corelib::graphics::Rect) { generativity::make_guard!(guard); // This is fine since we can only be called with a component that with our vtable which is a ComponentDescription let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) }; let window = eval::window_ref(instance_ref).unwrap(); instance_ref.component_type.original.layouts.borrow().iter().for_each(|layout| { let mut inverse_layout_tree = Vec::new(); collect_layouts_recursively(&mut inverse_layout_tree, &layout, instance_ref, &window); inverse_layout_tree.iter().rev().for_each(|layout| { layout.solve(instance_ref); }); }); for rep_in_comp in &instance_ref.component_type.repeater { generativity::make_guard!(g); let rep_in_comp = rep_in_comp.unerase(g); rep_in_comp.offset.apply_pin(instance_ref.instance).compute_layout(); } } unsafe extern "C" fn get_item_ref(component: ComponentRefPin, index: usize) -> Pin { generativity::make_guard!(guard); let instance_ref = InstanceRef::from_pin_ref(component, guard); match &instance_ref.component_type.item_tree.as_slice()[index] { ItemTreeNode::Item { item, .. } => core::mem::transmute::, Pin>( item.apply_pin(instance_ref.instance), ), ItemTreeNode::DynamicTree { .. } => panic!("get_item_ref called on dynamic tree"), } } unsafe extern "C" fn parent_item(component: ComponentRefPin, index: usize, result: &mut ItemWeak) { generativity::make_guard!(guard); let instance_ref = InstanceRef::from_pin_ref(component, guard); if index == 0 { let parent_item_index = instance_ref .component_type .original .parent_element .upgrade() .and_then(|e| e.borrow().item_index.get().map(|x| *x)); if let (Some(parent_offset), Some(parent_index)) = (instance_ref.component_type.parent_component_offset, parent_item_index) { if let Some(parent) = parent_offset.apply(instance_ref.as_ref()) { generativity::make_guard!(new_guard); let parent_instance = InstanceRef::from_pin_ref(*parent, new_guard); let parent_rc = parent_instance .self_weak() .get() .unwrap() .clone() .into_dyn() .upgrade() .unwrap(); *result = ItemRc::new(parent_rc, parent_index).parent_item(); }; } return; } let parent_index = match &instance_ref.component_type.item_tree.as_slice()[index] { ItemTreeNode::Item { parent_index, .. } => parent_index, ItemTreeNode::DynamicTree { parent_index, .. } => parent_index, }; let self_rc = instance_ref.self_weak().get().unwrap().clone().into_dyn().upgrade().unwrap(); *result = ItemRc::new(self_rc, *parent_index as _).downgrade(); } unsafe extern "C" fn drop_in_place(component: vtable::VRefMut) -> vtable::Layout { let instance_ptr = component.as_ptr() as *mut Instance<'static>; let layout = (*instance_ptr).type_info().layout(); dynamic_type::TypeInfo::drop_in_place(instance_ptr); layout.into() } unsafe extern "C" fn dealloc(_vtable: &ComponentVTable, ptr: *mut u8, layout: vtable::Layout) { std::alloc::dealloc(ptr, layout.try_into().unwrap()); } #[derive(Copy, Clone)] pub struct InstanceRef<'a, 'id> { pub instance: Pin<&'a Instance<'id>>, pub component_type: &'a ComponentDescription<'id>, } impl<'a, 'id> InstanceRef<'a, 'id> { pub unsafe fn from_pin_ref( component: ComponentRefPin<'a>, _guard: generativity::Guard<'id>, ) -> Self { Self { instance: Pin::new_unchecked(&*(component.as_ref().as_ptr() as *const Instance<'id>)), component_type: &*(Pin::into_inner_unchecked(component).get_vtable() as *const ComponentVTable as *const ComponentDescription<'id>), } } pub fn as_ptr(&self) -> *const u8 { (&*self.instance.as_ref()) as *const Instance as *const u8 } pub fn as_ref(&self) -> &Instance<'id> { &*self.instance } /// Borrow this component as a `Pin` pub fn borrow(self) -> ComponentRefPin<'a> { unsafe { Pin::new_unchecked(vtable::VRef::from_raw( NonNull::from(&self.component_type.ct).cast(), NonNull::from(self.instance.get_ref()).cast(), )) } } pub fn root_item(&self) -> Pin { let info = &self.component_type.items [self.component_type.original.root_element.borrow().id.as_str()]; unsafe { info.item_from_component(self.as_ptr()) } } pub fn self_weak( &self, ) -> &once_cell::unsync::OnceCell> { let extra_data = self.component_type.extra_data_offset.apply(self.as_ref()); &extra_data.self_weak } } /// Show the popup at the given location pub fn show_popup( popup: &object_tree::PopupWindow, x: f32, y: f32, parent_comp: ComponentRefPin, window: ComponentWindow, ) { generativity::make_guard!(guard); // FIXME: we should compile once and keep the cached compiled component let compiled = generate_component(&popup.component, guard); let inst = instantiate( compiled, Some(parent_comp), #[cfg(target_arch = "wasm32")] String::new(), ); window.show_popup(&vtable::VRc::into_dyn(inst), sixtyfps_corelib::graphics::Point::new(x, y)); }