/* 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 object_tree::{Element, ElementRc}; use sixtyfps_compilerlib::layout::{GridLayout, Layout, LayoutElement, LayoutItem, PathLayout}; use sixtyfps_compilerlib::typeregister::Type; use sixtyfps_compilerlib::*; use sixtyfps_corelib::component::{ComponentRefPin, ComponentVTable}; use sixtyfps_corelib::eventloop::ComponentWindow; use sixtyfps_corelib::graphics::Resource; use sixtyfps_corelib::item_tree::{ ItemTreeNode, ItemVisitorRefMut, TraversalOrder, VisitChildrenResult, }; use sixtyfps_corelib::items::{Flickable, ItemRef, ItemVTable, PropertyAnimation, Rectangle}; use sixtyfps_corelib::layout::{LayoutInfo, Padding}; use sixtyfps_corelib::properties::{InterpolatedPropertyValue, PropertyTracker}; use sixtyfps_corelib::rtti::{self, FieldOffset, PropertyInfo}; use sixtyfps_corelib::slice::Slice; use sixtyfps_corelib::{Color, Property, SharedString, Signal}; use std::collections::HashMap; use std::{cell::RefCell, 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(&self) -> ComponentRefPin { unsafe { Pin::new_unchecked(vtable::VRef::from_raw( NonNull::from(&self.component_type.ct).cast(), self.instance.as_ptr().cast(), )) } } /// Safety: the lifetime is not unique pub fn description(&self) -> Rc> { return self.component_type.clone(); } pub fn root_item(&self) -> Pin { let component = self.borrow(); let component_type = unsafe { &*(component.get_vtable() as *const ComponentVTable as *const ComponentDescription) }; let info = &component_type.items[component_type.original.root_element.borrow().id.as_str()]; unsafe { info.item_from_component(component.as_ptr()) } } 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) -> sixtyfps_corelib::eventloop::ComponentWindow { self.component_type .extra_data_offset .apply_pin(self.instance.as_pin_ref()) .window .borrow() .as_ref() .unwrap() .clone() } } impl<'id> Drop for ComponentBox<'id> { fn drop(&mut self) { match eval::window_ref(self.borrow_instance()) { Some(window) => { window.free_graphics_resources(self.borrow()); } 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) 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>, /// Offset of the `Vec` pub(crate) offset: FieldOffset, RepeaterVec<'sub_id>>, /// The model pub(crate) model: expression_tree::Expression, /// Offset of the PropertyTracker property_tracker: Option, PropertyTracker>>, } type RepeaterVec<'id> = core::cell::RefCell>>; pub(crate) struct ComponentExtraData { mouse_grabber: core::cell::Cell, pub(crate) window: RefCell>, } impl Default for ComponentExtraData { fn default() -> Self { Self { mouse_grabber: core::cell::Cell::new( sixtyfps_corelib::item_tree::VisitChildrenResult::CONTINUE, ), window: RefCell::new(None), } } } 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) } } } /// 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_signals: HashMap, Signal<[eval::Value]>>>, 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 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 InstanceRef { instance, component_type } = unsafe { InstanceRef::from_pin_ref(component, guard) }; sixtyfps_corelib::item_tree::visit_item_tree( instance, component, component_type.item_tree.as_slice().into(), index, order, v, |_, order, mut visitor, index| { generativity::make_guard!(guard); let rep_in_comp = component_type.repeater[index].unerase(guard); let mut vec = rep_in_comp.offset.apply(&*instance).borrow_mut(); if let Some(listener_offset) = rep_in_comp.property_tracker { let listener = listener_offset.apply_pin(instance); if listener.is_dirty() { match listener.evaluate(|| { eval::eval_expression( &rep_in_comp.model, InstanceRef { instance, component_type }, &mut Default::default(), ) }) { crate::Value::Number(count) => populate_model( &mut *vec, rep_in_comp, component, (0..count as i32).into_iter().map(|v| crate::Value::Number(v as f64)), ), crate::Value::Array(a) => { populate_model(&mut *vec, rep_in_comp, component, a.into_iter()) } crate::Value::Bool(b) => populate_model( &mut *vec, rep_in_comp, component, (if b { Some(crate::Value::Void) } else { None }).into_iter(), ), _ => panic!("Unsupported model"), } } } match order { TraversalOrder::FrontToBack => { for (i, x) in vec.iter().enumerate() { if x.borrow() .as_ref() .visit_children_item(-1, order, visitor.borrow_mut()) .has_aborted() { return VisitChildrenResult::abort(i, 0); } } } TraversalOrder::BackToFront => { for (i, x) in vec.iter().enumerate().rev() { if x.borrow() .as_ref() .visit_children_item(-1, order, visitor.borrow_mut()) .has_aborted() { return VisitChildrenResult::abort(i, 0); } } } } VisitChildrenResult::CONTINUE }, ) } /// 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>, /// The uszie is an offset within this item to the Signal. /// Ideally, we would need a vtable::VFieldOffset> pub(crate) signals: HashMap<&'static str, usize>, } 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(), signals: T::signals().into_iter().map(|(k, v)| (k, v.get_byte_offset())).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: Option, ) { (*self.0).set_binding(viewport(item), binding, animation).unwrap(); } fn offset(&self) -> usize { (*self.0).offset() + Flickable::FIELD_OFFSETS.viewport.get_byte_offset() } } 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 fn load<'id>( source: String, path: &std::path::Path, compiler_config: &CompilerConfiguration, guard: generativity::Guard<'id>, ) -> (Result>, ()>, sixtyfps_compilerlib::diagnostics::BuildDiagnostics) { let (syntax_node, diag) = parser::parse(source, Some(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); if diag.has_error() { return (Err(()), diag); } (Ok(generate_component(&doc.root_component, guard)), diag) } fn generate_component<'id>( root_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_flickable(), 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(root_component, |rc_item, child_offset, 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 _, }); } else if let Some(repeated) = &item.repeated { tree_array.push(ItemTreeNode::DynamicTree { index: repeater.len() }); 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(), property_tracker: if repeated.model.is_constant() { None } else { Some(builder.add_field_type::()) }, } .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 _ }, }); items_types.insert( item.id.clone(), ItemWithinComponent { offset, rtti: rt.clone(), elem: rc_item.clone() }, ); } }); let mut custom_properties = HashMap::new(); let mut custom_signals = 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 &root_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::Length => animated_property_info::(), Type::LogicalLength => animated_property_info::(), Type::Resource => property_info::(), Type::Bool => property_info::(), Type::Signal { .. } => { custom_signals .insert(name.clone(), builder.add_field_type::>()); continue; } Type::Object(_) => property_info::(), Type::Array(_) => property_info::(), Type::Component(ref c) if c.root_element.borrow().base_type == Type::Void => { property_info::() } _ => panic!("bad type"), }; custom_properties.insert( name.clone(), PropertiesWithinComponent { offset: builder.add_field(type_info), prop }, ); } if root_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 root_component.parent_element.upgrade().is_some() { Some(builder.add_field_type::>()) } else { None }; let extra_data_offset = builder.add_field_type::(); extern "C" fn layout_info(_: ComponentRefPin) -> LayoutInfo { todo!() } let t = ComponentVTable { visit_children_item, layout_info, compute_layout, input_event }; let t = ComponentDescription { ct: t, dynamic_type: builder.build(), item_tree: tree_array, items: items_types, custom_properties, custom_signals, original: root_component.clone(), repeater, repeater_names, parent_component_offset, extra_data_offset, }; Rc::new(t) } pub fn animation_for_property( component: InstanceRef, all_animations: &HashMap, property_name: &str, ) -> Option { match all_animations.get(property_name) { Some(anim_elem) => Some(eval::new_struct_with_bindings( &anim_elem.borrow().bindings, component, &mut Default::default(), )), None => None, } } fn animation_for_element_property( component: InstanceRef, element: &Element, property_name: &str, ) -> Option { animation_for_property(component, &element.property_animations, property_name) } fn populate_model<'par_id, 'sub_id>( vec: &mut Vec>, rep_in_comp: &RepeaterWithinComponent<'par_id, 'sub_id>, component: ComponentRefPin, model: impl Iterator + ExactSizeIterator, ) { vec.resize_with(model.size_hint().1.unwrap(), || { instantiate( rep_in_comp.component_to_repeat.clone(), Some(component), #[cfg(target_arch = "wasm32")] String::new(), ) }); for (i, (x, val)) in vec.iter().zip(model).enumerate() { rep_in_comp .component_to_repeat .set_property(x.borrow(), "index", i.try_into().unwrap()) .unwrap(); rep_in_comp.component_to_repeat.set_property(x.borrow(), "model_data", val).unwrap(); } } pub fn instantiate<'id>( component_type: Rc>, parent_ctx: Option, #[cfg(target_arch = "wasm32")] canvas_id: String, ) -> ComponentBox<'id> { let instance = component_type.dynamic_type.clone().create_instance(); let mem = instance.as_ptr().as_ptr() as *mut u8; let component_box = ComponentBox { instance, component_type: component_type.clone() }; let instance_ref = component_box.borrow_instance(); if let Some(parent) = parent_ctx { unsafe { *(mem.add(component_type.parent_component_offset.unwrap().get_byte_offset()) as *mut Option) = Some(parent); } } else { let extra_data = component_type.extra_data_offset.apply(instance_ref.as_ref()); #[cfg(not(target_arch = "wasm32"))] extra_data.window.replace(Some(sixtyfps_rendering_backend_default::create_window())); #[cfg(target_arch = "wasm32")] extra_data.window.replace(Some( sixtyfps_rendering_backend_gl::create_gl_window_with_canvas_id(canvas_id), )); } for item_within_component in component_type.items.values() { unsafe { let item = item_within_component.item_from_component(mem); let elem = item_within_component.elem.borrow(); for (prop, expr) in &elem.bindings { let ty = elem.lookup_property(prop.as_str()); if let Type::Signal { .. } = ty { 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(signal_offset) = item_within_component.rtti.signals.get(prop.as_str()) { let signal = &*(item.as_ptr().add(*signal_offset) as *const Signal<()>); signal.set_handler(move |_: &()| { generativity::make_guard!(guard); eval::eval_expression( &expr, InstanceRef::from_pin_ref(c, guard), &mut Default::default(), ); }) } else if let Some(signal_offset) = component_type.custom_signals.get(prop.as_str()) { let signal = signal_offset.apply(instance_ref.as_ref()); signal.set_handler(move |args| { generativity::make_guard!(guard); let mut local_context = eval::EvalLocalContext::from_function_arguments( args.iter().cloned().collect(), ); eval::eval_expression( &expr, InstanceRef::from_pin_ref(c, guard), &mut local_context, ); }) } else { panic!("unkown signal {}", prop) } } else { if let Some(prop_rtti) = item_within_component.rtti.properties.get(prop.as_str()) { let maybe_animation = animation_for_element_property(instance_ref, &elem, prop); if expr.is_constant() { prop_rtti.set( item, eval::eval_expression(expr, instance_ref, &mut Default::default()), maybe_animation, ); } else { 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(), )); prop_rtti.set_binding( item, Box::new(move || { generativity::make_guard!(guard); eval::eval_expression( &expr, InstanceRef::from_pin_ref(c, guard), &mut Default::default(), ) }), maybe_animation, ); } } else if let Some(PropertiesWithinComponent { offset, prop: prop_info, .. }) = component_type.custom_properties.get(prop.as_str()) { let maybe_animation = animation_for_property( instance_ref, &component_type.original.root_element.borrow().property_animations, prop, ); if expr.is_constant() { let v = eval::eval_expression(expr, instance_ref, &mut Default::default()); prop_info.set(Pin::new_unchecked(&*mem.add(*offset)), v, None).unwrap(); } else { 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(), )); prop_info .set_binding( Pin::new_unchecked(&*mem.add(*offset)), Box::new(move || { generativity::make_guard!(guard); eval::eval_expression( &expr, InstanceRef::from_pin_ref(c, guard), &mut Default::default(), ) }), maybe_animation, ) .unwrap(); } } else { panic!("unkown property {}", prop); } } } } } for rep_in_comp in &component_type.repeater { generativity::make_guard!(guard); let rep_in_comp = rep_in_comp.unerase(guard); if !rep_in_comp.model.is_constant() { continue; } let mut vec = rep_in_comp.offset.apply(instance_ref.as_ref()).borrow_mut(); match eval::eval_expression(&rep_in_comp.model, instance_ref, &mut Default::default()) { crate::Value::Number(count) => populate_model( &mut *vec, rep_in_comp, component_box.borrow(), (0..count as i32).into_iter().map(|v| crate::Value::Number(v as f64)), ), crate::Value::Array(a) => { populate_model(&mut *vec, rep_in_comp, component_box.borrow(), a.into_iter()) } crate::Value::Bool(b) => populate_model( &mut *vec, rep_in_comp, component_box.borrow(), (if b { Some(crate::Value::Void) } else { None }).into_iter(), ), _ => panic!("Unsupported model"), } } component_box } use sixtyfps_corelib::layout::*; pub struct GridLayoutWithCells<'a> { grid: &'a GridLayout, cells: Vec>, spacing: f32, padding: Padding, } #[derive(derive_more::From)] enum LayoutTreeItem<'a> { GridLayout(GridLayoutWithCells<'a>), 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::PathLayout(_) => todo!(), } } } trait LayoutItemCodeGen { fn get_property_ref<'a>( &'a self, component: InstanceRef, name: &str, ) -> Option<&'a Property>; fn get_layout_info<'a, 'b>( &'a self, component: InstanceRef, layout_tree: &'b mut Vec>, window: &ComponentWindow, ) -> LayoutInfo; } impl LayoutItemCodeGen for LayoutItem { fn get_property_ref<'a>( &'a self, component: InstanceRef, name: &str, ) -> Option<&'a Property> { match self { LayoutItem::Element(e) => e.get_property_ref(component, name), LayoutItem::Layout(l) => l.get_property_ref(component, name), } } fn get_layout_info<'a, 'b>( &'a self, component: InstanceRef, layout_tree: &'b mut Vec>, window: &ComponentWindow, ) -> LayoutInfo { match self { LayoutItem::Element(e) => e.get_layout_info(component, layout_tree, window), LayoutItem::Layout(l) => l.get_layout_info(component, layout_tree, window), } } } impl LayoutItemCodeGen for Layout { fn get_property_ref<'a>( &'a self, component: InstanceRef, name: &str, ) -> Option<&'a Property> { let moved_property_name = match self.rect().mapped_property_name(name) { Some(name) => name, None => return None, }; let prop = component.component_type.custom_properties.get(moved_property_name).unwrap(); Some(unsafe { &*(component.as_ptr().add(prop.offset) as *const Property) }) } fn get_layout_info<'a, 'b>( &'a self, component: InstanceRef, layout_tree: &'b mut Vec>, window: &ComponentWindow, ) -> LayoutInfo { let self_as_layout_tree_item = collect_layouts_recursively(layout_tree, &self, component, window); self_as_layout_tree_item.layout_info() } } impl LayoutItemCodeGen for LayoutElement { fn get_property_ref<'a>( &'a self, component: InstanceRef, name: &str, ) -> Option<&'a Property> { let item = &component.component_type.items.get(self.element.borrow().id.as_str()).unwrap_or_else( || panic!("Internal error: Item {} not found", self.element.borrow().id), ); unsafe { item.rtti.properties.get(name).map(|p| { &*(component.as_ptr().add(item.offset).add(p.offset()) as *const Property) }) } } fn get_layout_info<'a, 'b>( &'a self, component: InstanceRef, layout_tree: &'b mut Vec>, window: &ComponentWindow, ) -> LayoutInfo { let item = &component.component_type.items.get(self.element.borrow().id.as_str()).unwrap_or_else( || panic!("Internal error: Item {} not found", self.element.borrow().id), ); let element_info = unsafe { item.item_from_component(component.as_ptr()).as_ref().layouting_info(window) }; match &self.layout { Some(layout) => { let layout_info = layout.get_layout_info(component, layout_tree, window); layout_info.merge(&element_info) } None => element_info, } } } fn collect_layouts_recursively<'a, 'b>( layout_tree: &'b mut Vec>, layout: &'a Layout, component: InstanceRef, window: &ComponentWindow, ) -> &'b LayoutTreeItem<'a> { match layout { Layout::GridLayout(grid_layout) => { let expr_eval = |expr| { eval::eval_expression(expr, component, &mut Default::default()).try_into().unwrap() }; let cells = grid_layout .elems .iter() .map(|cell| { let get_prop = |name| cell.item.get_property_ref(component, name); let mut layout_info = cell.item.get_layout_info(component, layout_tree, window); cell.minimum_width.as_ref().map(|e| layout_info.min_width = expr_eval(e)); cell.maximum_width.as_ref().map(|e| layout_info.max_width = expr_eval(e)); cell.minimum_height.as_ref().map(|e| layout_info.min_height = expr_eval(e)); cell.maximum_height.as_ref().map(|e| layout_info.max_height = expr_eval(e)); GridLayoutCellData { x: get_prop("x"), y: get_prop("y"), width: get_prop("width"), height: get_prop("height"), col: cell.col, row: cell.row, colspan: cell.colspan, rowspan: cell.rowspan, constraint: layout_info, } }) .collect(); let spacing = grid_layout.spacing.as_ref().map_or(0., expr_eval); let padding = Padding { left: grid_layout.padding.left.as_ref().map_or(0., expr_eval), right: grid_layout.padding.right.as_ref().map_or(0., expr_eval), top: grid_layout.padding.top.as_ref().map_or(0., expr_eval), bottom: grid_layout.padding.bottom.as_ref().map_or(0., expr_eval), }; layout_tree .push(GridLayoutWithCells { grid: grid_layout, cells, spacing, padding }.into()); } 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: &expression_tree::Expression| { eval::eval_expression(&prop_ref, instance_ref, &mut Default::default()) .try_into() .unwrap_or_default() }; match self { Self::GridLayout(grid_layout) => { solve_grid_layout(&GridLayoutData { width: resolve_prop_ref(&grid_layout.grid.rect.width_reference), height: resolve_prop_ref(&grid_layout.grid.rect.height_reference), x: resolve_prop_ref(&grid_layout.grid.rect.x_reference), y: resolve_prop_ref(&grid_layout.grid.rect.y_reference), spacing: grid_layout.spacing, padding: &grid_layout.padding, cells: Slice::from(grid_layout.cells.as_slice()), }); } 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 { let rep_index = instance_ref.component_type.repeater_names[elem.borrow().id.as_str()]; generativity::make_guard!(guard); let rep_in_comp = instance_ref.component_type.repeater[rep_index].unerase(guard); let vec = rep_in_comp.offset.apply(&*instance_ref.instance).borrow(); for sub_comp in vec.iter() { push_layout_data( &elem.borrow().base_type.as_component().root_element, sub_comp.borrow_instance(), ) } } } let path_elements = eval::convert_path(&path_layout.path, instance_ref, &mut Default::default()); 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(&path_layout.offset_reference), }); } } } } extern "C" fn input_event( component: ComponentRefPin, mouse_event: sixtyfps_corelib::input::MouseEvent, window: &sixtyfps_corelib::eventloop::ComponentWindow, ) -> sixtyfps_corelib::input::InputEventResult { // This is fine since we can only be called with a component that with our vtable which is a ComponentDescription let component_type = unsafe { get_component_type(component) }; let instance = unsafe { Pin::new_unchecked(&*component.as_ptr().cast::()) }; let extra_data = component_type.extra_data_offset.apply(&*instance); let mouse_grabber = extra_data.mouse_grabber.get(); let (status, new_grab) = if let Some((item_index, rep_index)) = mouse_grabber.aborted_indexes() { let tree = &component_type.item_tree; let offset = sixtyfps_corelib::item_tree::item_offset(instance, 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(instance).as_ref().input_event(event, window) } ItemTreeNode::DynamicTree { index } => { generativity::make_guard!(guard); let rep_in_comp = &component_type.repeater[index].unerase(guard); let vec = rep_in_comp.offset.apply(&*instance).borrow(); vec[rep_index].borrow().as_ref().input_event(event, window) } }; match res { sixtyfps_corelib::input::InputEventResult::GrabMouse => (res, mouse_grabber), _ => (res, VisitChildrenResult::CONTINUE), } } else { sixtyfps_corelib::input::process_ungrabbed_mouse_event(component, mouse_event, window) }; extra_data.mouse_grabber.set(new_grab); status } extern "C" fn compute_layout(component: ComponentRefPin) { 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.layout_constraints.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!(guard); let rep_in_comp = rep_in_comp.unerase(guard); let vec = rep_in_comp.offset.apply(instance_ref.as_ref()).borrow(); for c in vec.iter() { c.borrow().as_ref().compute_layout(); } } } /// Get the component description from a ComponentRef /// /// Safety: the component must have been created by the interpreter pub unsafe fn get_component_type<'a>(component: ComponentRefPin<'a>) -> &'a ComponentDescription { &*(Pin::into_inner_unchecked(component).get_vtable() as *const ComponentVTable as *const ComponentDescription) } #[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 } }